Merge "Ensure that launched Views implement LaunchableView" into tm-qpr-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
49a61e54b4
@@ -41,6 +41,7 @@ import androidx.annotation.BinderThread
|
|||||||
import androidx.annotation.UiThread
|
import androidx.annotation.UiThread
|
||||||
import com.android.internal.annotations.VisibleForTesting
|
import com.android.internal.annotations.VisibleForTesting
|
||||||
import com.android.internal.policy.ScreenDecorationsUtils
|
import com.android.internal.policy.ScreenDecorationsUtils
|
||||||
|
import java.lang.IllegalArgumentException
|
||||||
import kotlin.math.roundToInt
|
import kotlin.math.roundToInt
|
||||||
|
|
||||||
private const val TAG = "ActivityLaunchAnimator"
|
private const val TAG = "ActivityLaunchAnimator"
|
||||||
@@ -338,13 +339,24 @@ class ActivityLaunchAnimator(
|
|||||||
* Return a [Controller] that will animate and expand [view] into the opening window.
|
* Return a [Controller] that will animate and expand [view] into the opening window.
|
||||||
*
|
*
|
||||||
* Important: The view must be attached to a [ViewGroup] when calling this function and
|
* Important: The view must be attached to a [ViewGroup] when calling this function and
|
||||||
* during the animation. For safety, this method will return null when it is not.
|
* during the animation. For safety, this method will return null when it is not. The
|
||||||
|
* view must also implement [LaunchableView], otherwise this method will throw.
|
||||||
*
|
*
|
||||||
* Note: The background of [view] should be a (rounded) rectangle so that it can be
|
* Note: The background of [view] should be a (rounded) rectangle so that it can be
|
||||||
* properly animated.
|
* properly animated.
|
||||||
*/
|
*/
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun fromView(view: View, cujType: Int? = null): Controller? {
|
fun fromView(view: View, cujType: Int? = null): Controller? {
|
||||||
|
// Make sure the View we launch from implements LaunchableView to avoid visibility
|
||||||
|
// issues.
|
||||||
|
if (view !is LaunchableView) {
|
||||||
|
throw IllegalArgumentException(
|
||||||
|
"An ActivityLaunchAnimator.Controller was created 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."
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
if (view.parent !is ViewGroup) {
|
if (view.parent !is ViewGroup) {
|
||||||
Log.e(
|
Log.e(
|
||||||
TAG,
|
TAG,
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ import com.android.systemui.animation.back.BackAnimationSpec
|
|||||||
import com.android.systemui.animation.back.applyTo
|
import com.android.systemui.animation.back.applyTo
|
||||||
import com.android.systemui.animation.back.floatingSystemSurfacesForSysUi
|
import com.android.systemui.animation.back.floatingSystemSurfacesForSysUi
|
||||||
import com.android.systemui.animation.back.onBackAnimationCallbackFrom
|
import com.android.systemui.animation.back.onBackAnimationCallbackFrom
|
||||||
|
import java.lang.IllegalArgumentException
|
||||||
import kotlin.math.roundToInt
|
import kotlin.math.roundToInt
|
||||||
|
|
||||||
private const val TAG = "DialogLaunchAnimator"
|
private const val TAG = "DialogLaunchAnimator"
|
||||||
@@ -157,12 +158,23 @@ constructor(
|
|||||||
* Create a [Controller] that can animate [source] to and from a dialog.
|
* Create a [Controller] that can animate [source] to and from a dialog.
|
||||||
*
|
*
|
||||||
* Important: The view must be attached to a [ViewGroup] when calling this function and
|
* Important: The view must be attached to a [ViewGroup] when calling this function and
|
||||||
* during the animation. For safety, this method will return null when it is not.
|
* during the animation. For safety, this method will return null when it is not. The
|
||||||
|
* view must also implement [LaunchableView], otherwise this method will throw.
|
||||||
*
|
*
|
||||||
* Note: The background of [view] should be a (rounded) rectangle so that it can be
|
* Note: The background of [view] should be a (rounded) rectangle so that it can be
|
||||||
* properly animated.
|
* properly animated.
|
||||||
*/
|
*/
|
||||||
fun fromView(source: View, cuj: DialogCuj? = null): Controller? {
|
fun fromView(source: View, cuj: DialogCuj? = null): Controller? {
|
||||||
|
// Make sure the View we launch from implements LaunchableView to avoid visibility
|
||||||
|
// issues.
|
||||||
|
if (source !is LaunchableView) {
|
||||||
|
throw IllegalArgumentException(
|
||||||
|
"A DialogLaunchAnimator.Controller was created 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."
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
if (source.parent !is ViewGroup) {
|
if (source.parent !is ViewGroup) {
|
||||||
Log.e(
|
Log.e(
|
||||||
TAG,
|
TAG,
|
||||||
@@ -249,23 +261,6 @@ constructor(
|
|||||||
}
|
}
|
||||||
?: controller
|
?: controller
|
||||||
|
|
||||||
if (
|
|
||||||
animatedParent == null &&
|
|
||||||
controller is ViewDialogLaunchAnimatorController &&
|
|
||||||
controller.source !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 source twice at the same time.
|
// Make sure we don't run the launch animation from the same source twice at the same time.
|
||||||
if (openedDialogs.any { it.controller.sourceIdentity == controller.sourceIdentity }) {
|
if (openedDialogs.any { it.controller.sourceIdentity == controller.sourceIdentity }) {
|
||||||
Log.e(
|
Log.e(
|
||||||
@@ -613,10 +608,16 @@ private class AnimatedDialog(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Animate that view with the background. Throw if we didn't find one, because
|
// Animate that view with the background. Throw if we didn't find one, because
|
||||||
// otherwise
|
// otherwise it's not clear what we should animate.
|
||||||
// it's not clear what we should animate.
|
if (viewGroupWithBackground == null) {
|
||||||
|
error("Unable to find ViewGroup with background")
|
||||||
|
}
|
||||||
|
|
||||||
|
if (viewGroupWithBackground !is LaunchableView) {
|
||||||
|
error("The animated ViewGroup with background must implement LaunchableView")
|
||||||
|
}
|
||||||
|
|
||||||
viewGroupWithBackground
|
viewGroupWithBackground
|
||||||
?: throw IllegalStateException("Unable to find ViewGroup with background")
|
|
||||||
} else {
|
} else {
|
||||||
// We will make the dialog window (and therefore its DecorView) fullscreen to make
|
// We will make the dialog window (and therefore its DecorView) fullscreen to make
|
||||||
// it possible to animate outside its bounds.
|
// it possible to animate outside its bounds.
|
||||||
@@ -639,7 +640,7 @@ private class AnimatedDialog(
|
|||||||
FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT)
|
FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT)
|
||||||
)
|
)
|
||||||
|
|
||||||
val dialogContentWithBackground = FrameLayout(dialog.context)
|
val dialogContentWithBackground = LaunchableFrameLayout(dialog.context)
|
||||||
dialogContentWithBackground.background = decorView.background
|
dialogContentWithBackground.background = decorView.background
|
||||||
|
|
||||||
// Make the window background transparent. Note that setting the window (or
|
// Make the window background transparent. Note that setting the window (or
|
||||||
@@ -720,7 +721,10 @@ private class AnimatedDialog(
|
|||||||
|
|
||||||
// Make the background view invisible until we start the animation. We use the transition
|
// Make the background view invisible until we start the animation. We use the transition
|
||||||
// visibility like GhostView does so that we don't mess up with the accessibility tree (see
|
// visibility like GhostView does so that we don't mess up with the accessibility tree (see
|
||||||
// b/204944038#comment17).
|
// b/204944038#comment17). Given that this background implements LaunchableView, we call
|
||||||
|
// setShouldBlockVisibilityChanges() early so that the current visibility (VISIBLE) is
|
||||||
|
// restored at the end of the animation.
|
||||||
|
dialogContentWithBackground.setShouldBlockVisibilityChanges(true)
|
||||||
dialogContentWithBackground.setTransitionVisibility(View.INVISIBLE)
|
dialogContentWithBackground.setTransitionVisibility(View.INVISIBLE)
|
||||||
|
|
||||||
// Make sure the dialog is visible instantly and does not do any window animation.
|
// Make sure the dialog is visible instantly and does not do any window animation.
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ import android.view.ViewGroup
|
|||||||
import android.view.ViewGroupOverlay
|
import android.view.ViewGroupOverlay
|
||||||
import android.widget.FrameLayout
|
import android.widget.FrameLayout
|
||||||
import com.android.internal.jank.InteractionJankMonitor
|
import com.android.internal.jank.InteractionJankMonitor
|
||||||
|
import java.lang.IllegalArgumentException
|
||||||
import java.util.LinkedList
|
import java.util.LinkedList
|
||||||
import kotlin.math.min
|
import kotlin.math.min
|
||||||
import kotlin.math.roundToInt
|
import kotlin.math.roundToInt
|
||||||
@@ -46,7 +47,8 @@ private const val TAG = "GhostedViewLaunchAnimatorController"
|
|||||||
* of the ghosted view.
|
* of the ghosted view.
|
||||||
*
|
*
|
||||||
* Important: [ghostedView] must be attached to a [ViewGroup] when calling this function and during
|
* Important: [ghostedView] must be attached to a [ViewGroup] when calling this function and during
|
||||||
* the animation.
|
* the animation. It must also implement [LaunchableView], otherwise an exception will be thrown
|
||||||
|
* during this controller instantiation.
|
||||||
*
|
*
|
||||||
* Note: Avoid instantiating this directly and call [ActivityLaunchAnimator.Controller.fromView]
|
* Note: Avoid instantiating this directly and call [ActivityLaunchAnimator.Controller.fromView]
|
||||||
* whenever possible instead.
|
* whenever possible instead.
|
||||||
@@ -101,6 +103,15 @@ constructor(
|
|||||||
private val background: Drawable?
|
private val background: Drawable?
|
||||||
|
|
||||||
init {
|
init {
|
||||||
|
// Make sure the View we launch from implements LaunchableView to avoid visibility issues.
|
||||||
|
if (ghostedView !is LaunchableView) {
|
||||||
|
throw IllegalArgumentException(
|
||||||
|
"A GhostedViewLaunchAnimatorController was created 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."
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
/** Find the first view with a background in [view] and its children. */
|
/** Find the first view with a background in [view] and its children. */
|
||||||
fun findBackground(view: View): Drawable? {
|
fun findBackground(view: View): Drawable? {
|
||||||
if (view.background != null) {
|
if (view.background != null) {
|
||||||
|
|||||||
@@ -0,0 +1,53 @@
|
|||||||
|
/*
|
||||||
|
* 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.animation
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.util.AttributeSet
|
||||||
|
import android.widget.FrameLayout
|
||||||
|
|
||||||
|
/** A [FrameLayout] that also implements [LaunchableView]. */
|
||||||
|
open class LaunchableFrameLayout : FrameLayout, LaunchableView {
|
||||||
|
private val delegate =
|
||||||
|
LaunchableViewDelegate(
|
||||||
|
this,
|
||||||
|
superSetVisibility = { super.setVisibility(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)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -25,7 +25,7 @@ import com.android.internal.jank.InteractionJankMonitor
|
|||||||
/** A [DialogLaunchAnimator.Controller] that can animate a [View] from/to a dialog. */
|
/** A [DialogLaunchAnimator.Controller] that can animate a [View] from/to a dialog. */
|
||||||
class ViewDialogLaunchAnimatorController
|
class ViewDialogLaunchAnimatorController
|
||||||
internal constructor(
|
internal constructor(
|
||||||
internal val source: View,
|
private val source: View,
|
||||||
override val cuj: DialogCuj?,
|
override val cuj: DialogCuj?,
|
||||||
) : DialogLaunchAnimator.Controller {
|
) : DialogLaunchAnimator.Controller {
|
||||||
override val viewRoot: ViewRootImpl?
|
override val viewRoot: ViewRootImpl?
|
||||||
|
|||||||
@@ -20,7 +20,7 @@
|
|||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:paddingVertical="@dimen/dream_overlay_complication_home_controls_padding">
|
android:paddingVertical="@dimen/dream_overlay_complication_home_controls_padding">
|
||||||
|
|
||||||
<ImageView
|
<com.android.systemui.common.ui.view.LaunchableImageView
|
||||||
android:id="@+id/home_controls_chip"
|
android:id="@+id/home_controls_chip"
|
||||||
android:layout_height="@dimen/keyguard_affordance_fixed_height"
|
android:layout_height="@dimen/keyguard_affordance_fixed_height"
|
||||||
android:layout_width="@dimen/keyguard_affordance_fixed_width"
|
android:layout_width="@dimen/keyguard_affordance_fixed_width"
|
||||||
|
|||||||
@@ -33,7 +33,7 @@
|
|||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
android:layout_weight="1">
|
android:layout_weight="1">
|
||||||
<androidx.constraintlayout.widget.ConstraintLayout
|
<com.android.systemui.common.ui.view.LaunchableConstraintLayout
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:id="@android:id/list"
|
android:id="@android:id/list"
|
||||||
@@ -55,6 +55,6 @@
|
|||||||
app:flow_horizontalGap="@dimen/global_actions_lite_padding"
|
app:flow_horizontalGap="@dimen/global_actions_lite_padding"
|
||||||
app:flow_verticalGap="@dimen/global_actions_lite_padding"
|
app:flow_verticalGap="@dimen/global_actions_lite_padding"
|
||||||
app:flow_horizontalStyle="packed"/>
|
app:flow_horizontalStyle="packed"/>
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
</com.android.systemui.common.ui.view.LaunchableConstraintLayout>
|
||||||
</com.android.systemui.globalactions.GlobalActionsLayoutLite>
|
</com.android.systemui.globalactions.GlobalActionsLayoutLite>
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|||||||
@@ -24,7 +24,7 @@
|
|||||||
android:layout_gravity="end">
|
android:layout_gravity="end">
|
||||||
<!-- We add a background behind the UserAvatarView with the same color and with a circular shape
|
<!-- We add a background behind the UserAvatarView with the same color and with a circular shape
|
||||||
so that this view can be expanded into a Dialog or an Activity. -->
|
so that this view can be expanded into a Dialog or an Activity. -->
|
||||||
<FrameLayout
|
<com.android.systemui.animation.LaunchableFrameLayout
|
||||||
android:id="@+id/kg_multi_user_avatar_with_background"
|
android:id="@+id/kg_multi_user_avatar_with_background"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
@@ -42,5 +42,5 @@
|
|||||||
systemui:framePadding="0dp"
|
systemui:framePadding="0dp"
|
||||||
systemui:frameWidth="0dp">
|
systemui:frameWidth="0dp">
|
||||||
</com.android.systemui.statusbar.phone.UserAvatarView>
|
</com.android.systemui.statusbar.phone.UserAvatarView>
|
||||||
</FrameLayout>
|
</com.android.systemui.animation.LaunchableFrameLayout>
|
||||||
</FrameLayout>
|
</FrameLayout>
|
||||||
@@ -106,7 +106,7 @@
|
|||||||
app:layout_constrainedWidth="true"
|
app:layout_constrainedWidth="true"
|
||||||
app:layout_constraintWidth_min="@dimen/min_clickable_item_size"
|
app:layout_constraintWidth_min="@dimen/min_clickable_item_size"
|
||||||
app:layout_constraintHeight_min="@dimen/min_clickable_item_size">
|
app:layout_constraintHeight_min="@dimen/min_clickable_item_size">
|
||||||
<LinearLayout
|
<com.android.systemui.common.ui.view.LaunchableLinearLayout
|
||||||
android:id="@+id/media_seamless_button"
|
android:id="@+id/media_seamless_button"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
@@ -135,7 +135,7 @@
|
|||||||
android:textDirection="locale"
|
android:textDirection="locale"
|
||||||
android:textSize="12sp"
|
android:textSize="12sp"
|
||||||
android:lineHeight="16sp" />
|
android:lineHeight="16sp" />
|
||||||
</LinearLayout>
|
</com.android.systemui.common.ui.view.LaunchableLinearLayout>
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
<!-- Song name -->
|
<!-- Song name -->
|
||||||
|
|||||||
@@ -23,7 +23,7 @@
|
|||||||
android:layout_gravity="center_vertical|start"
|
android:layout_gravity="center_vertical|start"
|
||||||
android:layout_marginStart="5dp"
|
android:layout_marginStart="5dp"
|
||||||
>
|
>
|
||||||
<LinearLayout
|
<com.android.systemui.common.ui.view.LaunchableLinearLayout
|
||||||
android:id="@+id/ongoing_call_chip_background"
|
android:id="@+id/ongoing_call_chip_background"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="@dimen/ongoing_appops_chip_height"
|
android:layout_height="@dimen/ongoing_appops_chip_height"
|
||||||
@@ -55,5 +55,5 @@
|
|||||||
android:textColor="?android:attr/colorPrimary"
|
android:textColor="?android:attr/colorPrimary"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
</LinearLayout>
|
</com.android.systemui.common.ui.view.LaunchableLinearLayout>
|
||||||
</FrameLayout>
|
</FrameLayout>
|
||||||
|
|||||||
@@ -0,0 +1,55 @@
|
|||||||
|
/*
|
||||||
|
* 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 androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
import com.android.systemui.animation.LaunchableView
|
||||||
|
import com.android.systemui.animation.LaunchableViewDelegate
|
||||||
|
|
||||||
|
/** A [ConstraintLayout] that also implements [LaunchableView]. */
|
||||||
|
open class LaunchableConstraintLayout : ConstraintLayout, LaunchableView {
|
||||||
|
private val delegate =
|
||||||
|
LaunchableViewDelegate(
|
||||||
|
this,
|
||||||
|
superSetVisibility = { super.setVisibility(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)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -23,7 +23,7 @@ import com.android.systemui.animation.LaunchableView
|
|||||||
import com.android.systemui.animation.LaunchableViewDelegate
|
import com.android.systemui.animation.LaunchableViewDelegate
|
||||||
|
|
||||||
/** A [LinearLayout] that also implements [LaunchableView]. */
|
/** A [LinearLayout] that also implements [LaunchableView]. */
|
||||||
class LaunchableLinearLayout : LinearLayout, LaunchableView {
|
open class LaunchableLinearLayout : LinearLayout, LaunchableView {
|
||||||
private val delegate =
|
private val delegate =
|
||||||
LaunchableViewDelegate(
|
LaunchableViewDelegate(
|
||||||
this,
|
this,
|
||||||
|
|||||||
@@ -17,11 +17,11 @@ package com.android.systemui.privacy
|
|||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.util.AttributeSet
|
import android.util.AttributeSet
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.widget.FrameLayout
|
|
||||||
import android.widget.ImageView
|
import android.widget.ImageView
|
||||||
import android.widget.LinearLayout
|
import android.widget.LinearLayout
|
||||||
import com.android.settingslib.Utils
|
import com.android.settingslib.Utils
|
||||||
import com.android.systemui.R
|
import com.android.systemui.R
|
||||||
|
import com.android.systemui.animation.LaunchableFrameLayout
|
||||||
import com.android.systemui.statusbar.events.BackgroundAnimatableView
|
import com.android.systemui.statusbar.events.BackgroundAnimatableView
|
||||||
|
|
||||||
class OngoingPrivacyChip @JvmOverloads constructor(
|
class OngoingPrivacyChip @JvmOverloads constructor(
|
||||||
@@ -29,7 +29,7 @@ class OngoingPrivacyChip @JvmOverloads constructor(
|
|||||||
attrs: AttributeSet? = null,
|
attrs: AttributeSet? = null,
|
||||||
defStyleAttrs: Int = 0,
|
defStyleAttrs: Int = 0,
|
||||||
defStyleRes: Int = 0
|
defStyleRes: Int = 0
|
||||||
) : FrameLayout(context, attrs, defStyleAttrs, defStyleRes), BackgroundAnimatableView {
|
) : LaunchableFrameLayout(context, attrs, defStyleAttrs, defStyleRes), BackgroundAnimatableView {
|
||||||
|
|
||||||
private var iconMargin = 0
|
private var iconMargin = 0
|
||||||
private var iconSize = 0
|
private var iconSize = 0
|
||||||
|
|||||||
@@ -20,10 +20,21 @@ import android.content.Context;
|
|||||||
import android.util.AttributeSet;
|
import android.util.AttributeSet;
|
||||||
import android.widget.Button;
|
import android.widget.Button;
|
||||||
|
|
||||||
|
import com.android.systemui.animation.LaunchableView;
|
||||||
|
import com.android.systemui.animation.LaunchableViewDelegate;
|
||||||
|
|
||||||
|
import kotlin.Unit;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A Button which doesn't have overlapping drawing commands
|
* A Button which doesn't have overlapping drawing commands
|
||||||
*/
|
*/
|
||||||
public class AlphaOptimizedButton extends Button {
|
public class AlphaOptimizedButton extends Button implements LaunchableView {
|
||||||
|
private LaunchableViewDelegate mDelegate = new LaunchableViewDelegate(this,
|
||||||
|
(visibility) -> {
|
||||||
|
super.setVisibility(visibility);
|
||||||
|
return Unit.INSTANCE;
|
||||||
|
});
|
||||||
|
|
||||||
public AlphaOptimizedButton(Context context) {
|
public AlphaOptimizedButton(Context context) {
|
||||||
super(context);
|
super(context);
|
||||||
}
|
}
|
||||||
@@ -45,4 +56,14 @@ public class AlphaOptimizedButton extends Button {
|
|||||||
public boolean hasOverlappingRendering() {
|
public boolean hasOverlappingRendering() {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setShouldBlockVisibilityChanges(boolean block) {
|
||||||
|
mDelegate.setShouldBlockVisibilityChanges(block);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setVisibility(int visibility) {
|
||||||
|
mDelegate.setVisibility(visibility);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,14 +19,14 @@ package com.android.systemui.statusbar.phone.userswitcher
|
|||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.util.AttributeSet
|
import android.util.AttributeSet
|
||||||
import android.widget.ImageView
|
import android.widget.ImageView
|
||||||
import android.widget.LinearLayout
|
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import com.android.systemui.R
|
import com.android.systemui.R
|
||||||
|
import com.android.systemui.common.ui.view.LaunchableLinearLayout
|
||||||
|
|
||||||
class StatusBarUserSwitcherContainer(
|
class StatusBarUserSwitcherContainer(
|
||||||
context: Context?,
|
context: Context?,
|
||||||
attrs: AttributeSet?
|
attrs: AttributeSet?
|
||||||
) : LinearLayout(context, attrs) {
|
) : LaunchableLinearLayout(context, attrs) {
|
||||||
lateinit var text: TextView
|
lateinit var text: TextView
|
||||||
private set
|
private set
|
||||||
lateinit var avatar: ImageView
|
lateinit var avatar: ImageView
|
||||||
|
|||||||
@@ -27,6 +27,8 @@ import android.view.ViewTreeObserver
|
|||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import androidx.constraintlayout.widget.ConstraintLayout
|
import androidx.constraintlayout.widget.ConstraintLayout
|
||||||
import androidx.constraintlayout.widget.ConstraintSet
|
import androidx.constraintlayout.widget.ConstraintSet
|
||||||
|
import com.android.systemui.animation.LaunchableView
|
||||||
|
import com.android.systemui.animation.LaunchableViewDelegate
|
||||||
import com.android.systemui.statusbar.CrossFadeHelper
|
import com.android.systemui.statusbar.CrossFadeHelper
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -38,7 +40,7 @@ class TransitionLayout @JvmOverloads constructor(
|
|||||||
context: Context,
|
context: Context,
|
||||||
attrs: AttributeSet? = null,
|
attrs: AttributeSet? = null,
|
||||||
defStyleAttr: Int = 0
|
defStyleAttr: Int = 0
|
||||||
) : ConstraintLayout(context, attrs, defStyleAttr) {
|
) : ConstraintLayout(context, attrs, defStyleAttr), LaunchableView {
|
||||||
|
|
||||||
private val boundsRect = Rect()
|
private val boundsRect = Rect()
|
||||||
private val originalGoneChildrenSet: MutableSet<Int> = mutableSetOf()
|
private val originalGoneChildrenSet: MutableSet<Int> = mutableSetOf()
|
||||||
@@ -50,7 +52,11 @@ class TransitionLayout @JvmOverloads constructor(
|
|||||||
|
|
||||||
private var desiredMeasureWidth = 0
|
private var desiredMeasureWidth = 0
|
||||||
private var desiredMeasureHeight = 0
|
private var desiredMeasureHeight = 0
|
||||||
private var transitionVisibility = View.VISIBLE
|
private val delegate =
|
||||||
|
LaunchableViewDelegate(
|
||||||
|
this,
|
||||||
|
superSetVisibility = { super.setVisibility(it) },
|
||||||
|
)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The measured state of this view which is the one we will lay ourselves out with. This
|
* The measured state of this view which is the one we will lay ourselves out with. This
|
||||||
@@ -83,11 +89,12 @@ class TransitionLayout @JvmOverloads constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun setTransitionVisibility(visibility: Int) {
|
override fun setShouldBlockVisibilityChanges(block: Boolean) {
|
||||||
// We store the last transition visibility assigned to this view to restore it later if
|
delegate.setShouldBlockVisibilityChanges(block)
|
||||||
// necessary.
|
}
|
||||||
super.setTransitionVisibility(visibility)
|
|
||||||
transitionVisibility = visibility
|
override fun setVisibility(visibility: Int) {
|
||||||
|
delegate.setVisibility(visibility)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onFinishInflate() {
|
override fun onFinishInflate() {
|
||||||
@@ -173,14 +180,6 @@ class TransitionLayout @JvmOverloads constructor(
|
|||||||
translationY = currentState.translation.y
|
translationY = currentState.translation.y
|
||||||
|
|
||||||
CrossFadeHelper.fadeIn(this, currentState.alpha)
|
CrossFadeHelper.fadeIn(this, currentState.alpha)
|
||||||
|
|
||||||
// CrossFadeHelper#fadeIn will change this view visibility, which overrides the transition
|
|
||||||
// visibility. We set the transition visibility again to make sure that this view plays well
|
|
||||||
// with GhostView, which sets the transition visibility and is used for activity launch
|
|
||||||
// animations.
|
|
||||||
if (transitionVisibility != View.VISIBLE) {
|
|
||||||
setTransitionVisibility(transitionVisibility)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun applyCurrentStateOnPredraw() {
|
private fun applyCurrentStateOnPredraw() {
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import android.view.RemoteAnimationAdapter
|
|||||||
import android.view.RemoteAnimationTarget
|
import android.view.RemoteAnimationTarget
|
||||||
import android.view.SurfaceControl
|
import android.view.SurfaceControl
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
|
import android.widget.FrameLayout
|
||||||
import android.widget.LinearLayout
|
import android.widget.LinearLayout
|
||||||
import androidx.test.filters.SmallTest
|
import androidx.test.filters.SmallTest
|
||||||
import com.android.systemui.SysuiTestCase
|
import com.android.systemui.SysuiTestCase
|
||||||
@@ -26,6 +27,7 @@ import junit.framework.Assert.assertTrue
|
|||||||
import junit.framework.AssertionFailedError
|
import junit.framework.AssertionFailedError
|
||||||
import kotlin.concurrent.thread
|
import kotlin.concurrent.thread
|
||||||
import org.junit.After
|
import org.junit.After
|
||||||
|
import org.junit.Assert.assertThrows
|
||||||
import org.junit.Before
|
import org.junit.Before
|
||||||
import org.junit.Rule
|
import org.junit.Rule
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
@@ -195,6 +197,13 @@ class ActivityLaunchAnimatorTest : SysuiTestCase() {
|
|||||||
verify(controller).onLaunchAnimationStart(anyBoolean())
|
verify(controller).onLaunchAnimationStart(anyBoolean())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun creatingControllerFromNormalViewThrows() {
|
||||||
|
assertThrows(IllegalArgumentException::class.java) {
|
||||||
|
ActivityLaunchAnimator.Controller.fromView(FrameLayout(mContext))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun fakeWindow(): RemoteAnimationTarget {
|
private fun fakeWindow(): RemoteAnimationTarget {
|
||||||
val bounds = Rect(10 /* left */, 20 /* top */, 30 /* right */, 40 /* bottom */)
|
val bounds = Rect(10 /* left */, 20 /* top */, 30 /* right */, 40 /* bottom */)
|
||||||
val taskInfo = ActivityManager.RunningTaskInfo()
|
val taskInfo = ActivityManager.RunningTaskInfo()
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ import junit.framework.Assert.assertNull
|
|||||||
import junit.framework.Assert.assertTrue
|
import junit.framework.Assert.assertTrue
|
||||||
import org.junit.After
|
import org.junit.After
|
||||||
import org.junit.Assert.assertNotEquals
|
import org.junit.Assert.assertNotEquals
|
||||||
|
import org.junit.Assert.assertThrows
|
||||||
import org.junit.Before
|
import org.junit.Before
|
||||||
import org.junit.Rule
|
import org.junit.Rule
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
@@ -260,6 +261,13 @@ class DialogLaunchAnimatorTest : SysuiTestCase() {
|
|||||||
assertThat(touchSurface.visibility).isEqualTo(View.GONE)
|
assertThat(touchSurface.visibility).isEqualTo(View.GONE)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun creatingControllerFromNormalViewThrows() {
|
||||||
|
assertThrows(IllegalArgumentException::class.java) {
|
||||||
|
DialogLaunchAnimator.Controller.fromView(FrameLayout(mContext))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun createAndShowDialog(
|
private fun createAndShowDialog(
|
||||||
animator: DialogLaunchAnimator = dialogLaunchAnimator,
|
animator: DialogLaunchAnimator = dialogLaunchAnimator,
|
||||||
): TestDialog {
|
): TestDialog {
|
||||||
|
|||||||
@@ -16,58 +16,34 @@
|
|||||||
|
|
||||||
package com.android.systemui.animation
|
package com.android.systemui.animation
|
||||||
|
|
||||||
import android.graphics.drawable.Drawable
|
|
||||||
import android.testing.AndroidTestingRunner
|
import android.testing.AndroidTestingRunner
|
||||||
import android.testing.TestableLooper
|
import android.testing.TestableLooper
|
||||||
import android.view.View
|
import android.widget.FrameLayout
|
||||||
import android.view.ViewGroup
|
|
||||||
import android.view.ViewParent
|
|
||||||
import androidx.test.filters.SmallTest
|
import androidx.test.filters.SmallTest
|
||||||
import com.android.internal.jank.InteractionJankMonitor
|
|
||||||
import com.android.systemui.SysuiTestCase
|
import com.android.systemui.SysuiTestCase
|
||||||
import org.junit.Before
|
import org.junit.Assert.assertThrows
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.junit.runner.RunWith
|
import org.junit.runner.RunWith
|
||||||
import org.mockito.ArgumentMatchers.any
|
|
||||||
import org.mockito.ArgumentMatchers.anyInt
|
|
||||||
import org.mockito.Mock
|
|
||||||
import org.mockito.Mockito.`when` as whenever
|
|
||||||
import org.mockito.MockitoAnnotations
|
|
||||||
|
|
||||||
@SmallTest
|
@SmallTest
|
||||||
@RunWith(AndroidTestingRunner::class)
|
@RunWith(AndroidTestingRunner::class)
|
||||||
@TestableLooper.RunWithLooper
|
@TestableLooper.RunWithLooper
|
||||||
class GhostedViewLaunchAnimatorControllerTest : SysuiTestCase() {
|
class GhostedViewLaunchAnimatorControllerTest : SysuiTestCase() {
|
||||||
@Mock lateinit var interactionJankMonitor: InteractionJankMonitor
|
|
||||||
@Mock lateinit var view: View
|
|
||||||
@Mock lateinit var rootView: ViewGroup
|
|
||||||
@Mock lateinit var viewParent: ViewParent
|
|
||||||
@Mock lateinit var drawable: Drawable
|
|
||||||
lateinit var controller: GhostedViewLaunchAnimatorController
|
|
||||||
|
|
||||||
@Before
|
|
||||||
fun setup() {
|
|
||||||
MockitoAnnotations.initMocks(this)
|
|
||||||
whenever(view.rootView).thenReturn(rootView)
|
|
||||||
whenever(view.background).thenReturn(drawable)
|
|
||||||
whenever(view.height).thenReturn(0)
|
|
||||||
whenever(view.width).thenReturn(0)
|
|
||||||
whenever(view.parent).thenReturn(viewParent)
|
|
||||||
whenever(view.visibility).thenReturn(View.VISIBLE)
|
|
||||||
whenever(view.invalidate()).then { /* NO-OP */ }
|
|
||||||
whenever(view.getLocationOnScreen(any())).then { /* NO-OP */ }
|
|
||||||
whenever(interactionJankMonitor.begin(any(), anyInt())).thenReturn(true)
|
|
||||||
whenever(interactionJankMonitor.end(anyInt())).thenReturn(true)
|
|
||||||
controller = GhostedViewLaunchAnimatorController(view, 0, interactionJankMonitor)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun animatingOrphanViewDoesNotCrash() {
|
fun animatingOrphanViewDoesNotCrash() {
|
||||||
val state = LaunchAnimator.State(top = 0, bottom = 0, left = 0, right = 0)
|
val state = LaunchAnimator.State(top = 0, bottom = 0, left = 0, right = 0)
|
||||||
|
|
||||||
|
val controller = GhostedViewLaunchAnimatorController(LaunchableFrameLayout(mContext))
|
||||||
controller.onIntentStarted(willAnimate = true)
|
controller.onIntentStarted(willAnimate = true)
|
||||||
controller.onLaunchAnimationStart(isExpandingFullyAbove = true)
|
controller.onLaunchAnimationStart(isExpandingFullyAbove = true)
|
||||||
controller.onLaunchAnimationProgress(state, progress = 0f, linearProgress = 0f)
|
controller.onLaunchAnimationProgress(state, progress = 0f, linearProgress = 0f)
|
||||||
controller.onLaunchAnimationEnd(isExpandingFullyAbove = true)
|
controller.onLaunchAnimationEnd(isExpandingFullyAbove = true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun creatingControllerFromNormalViewThrows() {
|
||||||
|
assertThrows(IllegalArgumentException::class.java) {
|
||||||
|
GhostedViewLaunchAnimatorController(FrameLayout(mContext))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -31,13 +31,13 @@ import android.content.ComponentName;
|
|||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.testing.AndroidTestingRunner;
|
import android.testing.AndroidTestingRunner;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.widget.ImageView;
|
|
||||||
|
|
||||||
import androidx.test.filters.SmallTest;
|
import androidx.test.filters.SmallTest;
|
||||||
|
|
||||||
import com.android.internal.logging.UiEventLogger;
|
import com.android.internal.logging.UiEventLogger;
|
||||||
import com.android.systemui.R;
|
import com.android.systemui.R;
|
||||||
import com.android.systemui.SysuiTestCase;
|
import com.android.systemui.SysuiTestCase;
|
||||||
|
import com.android.systemui.common.ui.view.LaunchableImageView;
|
||||||
import com.android.systemui.controls.ControlsServiceInfo;
|
import com.android.systemui.controls.ControlsServiceInfo;
|
||||||
import com.android.systemui.controls.controller.ControlsController;
|
import com.android.systemui.controls.controller.ControlsController;
|
||||||
import com.android.systemui.controls.controller.StructureInfo;
|
import com.android.systemui.controls.controller.StructureInfo;
|
||||||
@@ -90,7 +90,7 @@ public class DreamHomeControlsComplicationTest extends SysuiTestCase {
|
|||||||
private View mView;
|
private View mView;
|
||||||
|
|
||||||
@Mock
|
@Mock
|
||||||
private ImageView mHomeControlsView;
|
private LaunchableImageView mHomeControlsView;
|
||||||
|
|
||||||
@Mock
|
@Mock
|
||||||
private ActivityStarter mActivityStarter;
|
private ActivityStarter mActivityStarter;
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ import android.view.View
|
|||||||
import com.android.internal.logging.MetricsLogger
|
import com.android.internal.logging.MetricsLogger
|
||||||
import com.android.systemui.SysuiTestCase
|
import com.android.systemui.SysuiTestCase
|
||||||
import com.android.systemui.animation.ActivityLaunchAnimator
|
import com.android.systemui.animation.ActivityLaunchAnimator
|
||||||
|
import com.android.systemui.animation.LaunchableFrameLayout
|
||||||
import com.android.systemui.classifier.FalsingManagerFake
|
import com.android.systemui.classifier.FalsingManagerFake
|
||||||
import com.android.systemui.plugins.ActivityStarter
|
import com.android.systemui.plugins.ActivityStarter
|
||||||
import com.android.systemui.plugins.qs.QSTile
|
import com.android.systemui.plugins.qs.QSTile
|
||||||
@@ -342,7 +343,7 @@ class CustomTileTest : SysuiTestCase() {
|
|||||||
val tile = CustomTile.create(customTileBuilder, TILE_SPEC, mContext)
|
val tile = CustomTile.create(customTileBuilder, TILE_SPEC, mContext)
|
||||||
tile.qsTile.activityLaunchForClick = pi
|
tile.qsTile.activityLaunchForClick = pi
|
||||||
|
|
||||||
tile.handleClick(mock(View::class.java))
|
tile.handleClick(mock(LaunchableFrameLayout::class.java))
|
||||||
|
|
||||||
testableLooper.processAllMessages()
|
testableLooper.processAllMessages()
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user