• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2024 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.platform.systemui_tapl.ui
18 
19 import android.graphics.Point
20 import android.os.SystemClock
21 import android.platform.helpers.CommonUtils
22 import android.platform.systemui_tapl.utils.DeviceUtils.launcherResSelector
23 import android.platform.systemui_tapl.utils.DeviceUtils.sysuiResSelector
24 import android.platform.uiautomatorhelpers.BetterSwipe
25 import android.platform.uiautomatorhelpers.DeviceHelpers.context
26 import android.platform.uiautomatorhelpers.DeviceHelpers.hasObject
27 import android.platform.uiautomatorhelpers.DeviceHelpers.uiDevice
28 import android.platform.uiautomatorhelpers.DeviceHelpers.waitForPossibleEmpty
29 import android.platform.uiautomatorhelpers.PRECISE_GESTURE_INTERPOLATOR
30 import android.view.WindowInsets
31 import android.view.WindowManager
32 import androidx.test.uiautomator.UiObject2
33 import com.android.wm.shell.Flags
34 import com.google.common.truth.Truth.assertWithMessage
35 import java.time.Duration
36 import java.time.temporal.ChronoUnit
37 
38 /**
39  * System UI test automation object representing a notification bubble, specifically the view
40  * representing the bubble, shown when the stack is collapsed or expanded.
41  */
42 class Bubble internal constructor(private val bubbleView: UiObject2) {
43     /** Expands the bubble into the stack. */
expandnull44     fun expand(): ExpandedBubbleStack {
45         bubbleView.click()
46         // check if bubble stack education is visible
47         // education visibility can be checked only after interacting with the bubble (click)
48         // it might be invoked by user interaction, if it wasn't presented yet
49         if (isEducationVisible) {
50             // click bubble again to expand
51             // if education is visible, the previous interaction didn't expand the bubble stack
52             bubbleView.click()
53         }
54         // let the stack expand animation to finish
55         SystemClock.sleep(STACK_EXPAND_TIMEOUT.toMillis())
56         uiDevice.waitForIdle()
57         return ExpandedBubbleStack().apply {
58             // dismiss manage education if it's visible
59             dismissManageEducation()
60         }
61     }
62 
63     /** Clicks on the bubble. */
clicknull64     fun click() {
65         bubbleView.click()
66     }
67 
68     /** Returns the content description of the bubble. */
contentDescriptionnull69     fun contentDescription(): String = bubbleView.contentDescription
70 
71     /** Dismisses the bubble by dragging it to the Dismiss target. */
72     fun dismiss() {
73         dragBubbleToDismiss()
74         // check if bubble stack education is visible and blocked interaction
75         // education visibility can be checked only after interacting with the bubble (swipe)
76         // it might be invoked by user interaction, if it wasn't presented yet
77         if (isEducationVisible) {
78             // retry drag interaction
79             // if education is visible, the previous interaction was blocked and didn't drag bubble
80             dragBubbleToDismiss()
81         }
82     }
83 
84     /** Returns the flyout if it's visible, or fails. */
85     val flyout: BubbleFlyout
86         get() = BubbleFlyout()
87 
88     /** Is Welcome education visible and stopped bubbles expand */
89     private val isEducationVisible: Boolean
90         get() = hasObject(BUBBLE_STACK_EDUCATION)
91 
92     /** Drag bubble to Dismiss target */
dragBubbleToDismissnull93     private fun dragBubbleToDismiss() {
94         val windowMetrics =
95             context.getSystemService(WindowManager::class.java)!!.currentWindowMetrics
96         val insets =
97             windowMetrics.windowInsets.getInsetsIgnoringVisibility(
98                 WindowInsets.Type.mandatorySystemGestures() or
99                     WindowInsets.Type.navigationBars() or
100                     WindowInsets.Type.displayCutout()
101             )
102         val destination =
103             Point(windowMetrics.bounds.width() / 2, (windowMetrics.bounds.height() - insets.bottom))
104         BetterSwipe.swipe(
105             bubbleView.visibleCenter,
106             destination,
107             duration = Duration.of(700, ChronoUnit.MILLIS),
108             interpolator = PRECISE_GESTURE_INTERPOLATOR,
109         )
110     }
111 
equalsnull112     override fun equals(other: Any?): Boolean {
113         if (this === other) return true
114         if (other !is Bubble) return false
115         // Bubbles are equal if the backing bubbleView is the same.
116         // This ensures that Bubble objects can be used as targets for waitForValueToSettle.
117         return this.bubbleView == other.bubbleView
118     }
119 
120     companion object {
121         val FIND_OBJECT_TIMEOUT = Duration.ofSeconds(20)
122         val BUBBLE_VIEW = sysuiResSelector("bubble_view")
123         val BUBBLE_BAR_VIEWS = launcherResSelector("bubble_view")
124         private val STACK_EXPAND_TIMEOUT = Duration.ofSeconds(1)
125         private val BUBBLE_STACK_EDUCATION = sysuiResSelector("stack_education_layout")
126 
127         @JvmStatic
128         internal val bubbleViews: List<UiObject2>
129             get() {
130                 val bubbleViews =
131                     if (CommonUtils.isLargeScreen() && Flags.enableBubbleBar()) {
132                         // Check bubble bar first if we're large screen
133                         waitForPossibleEmpty(BUBBLE_BAR_VIEWS, timeout = FIND_OBJECT_TIMEOUT)
<lambda>null134                             .ifEmpty {
135                                 // Check floating in case bubble bar wasn't active
136                                 waitForPossibleEmpty(BUBBLE_VIEW, timeout = FIND_OBJECT_TIMEOUT)
137                             }
138                     } else {
139                         waitForPossibleEmpty(BUBBLE_VIEW, timeout = FIND_OBJECT_TIMEOUT)
140                     }
141                 assertWithMessage("No bubbles visible").that(bubbleViews.size).isAtLeast(1)
142                 return bubbleViews
143             }
144     }
145 }
146