From a78092767c95fec9d5b0fadf4dc4d72fc6464144 Mon Sep 17 00:00:00 2001 From: Luca Zuccarini Date: Tue, 20 Aug 2024 11:09:26 +0000 Subject: 3/3 Move some Shell utils to the Shared package. Bug: 322791067 Flag: EXEMPT move only Test: NA Change-Id: Ib2b4f516635e8c44f08a2562aedb191be0ca4bdc --- libs/WindowManager/Shell/Android.bp | 14 +- .../bubbles/BubbleEducationViewScreenshotTest.kt | 2 +- .../wm/shell/bubbles/BubblePositionerTest.kt | 2 +- .../wm/shell/bubbles/BubbleStackViewTest.kt | 2 +- .../bar/BubbleExpandedViewPinControllerTest.kt | 12 +- .../res/layout/bubble_bar_manage_education.xml | 4 +- .../res/layout/bubble_bar_stack_education.xml | 4 +- .../shared/bubbles/BaseBubblePinController.kt | 210 +++++++++++++++++++ .../wm/shell/shared/bubbles/BubbleBarLocation.aidl | 19 ++ .../wm/shell/shared/bubbles/BubbleBarLocation.kt | 63 ++++++ .../wm/shell/shared/bubbles/BubbleBarUpdate.java | 186 +++++++++++++++++ .../wm/shell/shared/bubbles/BubbleConstants.java | 26 +++ .../wm/shell/shared/bubbles/BubbleInfo.java | 196 +++++++++++++++++ .../wm/shell/shared/bubbles/BubblePopupDrawable.kt | 232 +++++++++++++++++++++ .../wm/shell/shared/bubbles/BubblePopupView.kt | 66 ++++++ .../wm/shell/shared/bubbles/DismissCircleView.java | 76 +++++++ .../android/wm/shell/shared/bubbles/DismissView.kt | 229 ++++++++++++++++++++ .../src/com/android/wm/shell/shared/bubbles/OWNERS | 6 + .../shell/shared/bubbles/RelativeTouchListener.kt | 180 ++++++++++++++++ .../wm/shell/shared/bubbles/RemovedBubble.java | 70 +++++++ .../src/com/android/wm/shell/bubbles/Bubble.java | 2 +- .../android/wm/shell/bubbles/BubbleController.java | 4 +- .../com/android/wm/shell/bubbles/BubbleData.java | 4 +- .../wm/shell/bubbles/BubbleExpandedViewManager.kt | 2 +- .../android/wm/shell/bubbles/BubblePopupViewExt.kt | 4 +- .../android/wm/shell/bubbles/BubblePositioner.java | 2 +- .../android/wm/shell/bubbles/BubbleStackView.java | 6 +- .../src/com/android/wm/shell/bubbles/Bubbles.java | 4 +- .../com/android/wm/shell/bubbles/DismissViewExt.kt | 2 +- .../src/com/android/wm/shell/bubbles/IBubbles.aidl | 2 +- .../android/wm/shell/bubbles/IBubblesListener.aidl | 2 +- .../shell/bubbles/bar/BubbleBarExpandedView.java | 2 +- .../bar/BubbleBarExpandedViewDragController.kt | 4 +- .../wm/shell/bubbles/bar/BubbleBarLayerView.java | 6 +- .../bubbles/bar/BubbleEducationViewController.kt | 4 +- .../bubbles/bar/BubbleExpandedViewPinController.kt | 4 +- .../common/bubbles/BaseBubblePinController.kt | 210 ------------------- .../wm/shell/common/bubbles/BubbleBarLocation.aidl | 19 -- .../wm/shell/common/bubbles/BubbleBarLocation.kt | 63 ------ .../wm/shell/common/bubbles/BubbleBarUpdate.java | 186 ----------------- .../wm/shell/common/bubbles/BubbleConstants.java | 26 --- .../wm/shell/common/bubbles/BubbleInfo.java | 196 ----------------- .../wm/shell/common/bubbles/BubblePopupDrawable.kt | 232 --------------------- .../wm/shell/common/bubbles/BubblePopupView.kt | 66 ------ .../wm/shell/common/bubbles/DismissCircleView.java | 76 ------- .../android/wm/shell/common/bubbles/DismissView.kt | 229 -------------------- .../src/com/android/wm/shell/common/bubbles/OWNERS | 6 - .../shell/common/bubbles/RelativeTouchListener.kt | 180 ---------------- .../wm/shell/common/bubbles/RemovedBubble.java | 70 ------- .../Shell/src/com/android/wm/shell/docs/changes.md | 7 +- .../shell/pip/phone/PipDismissTargetHandler.java | 4 +- .../shell/pip2/phone/PipDismissTargetHandler.java | 4 +- .../android/wm/shell/bubbles/BubbleDataTest.java | 4 +- .../com/android/wm/shell/bubbles/BubbleTest.java | 2 +- .../shell/common/bubbles/BubbleBarLocationTest.kt | 53 ----- .../wm/shell/common/bubbles/BubbleInfoTest.kt | 64 ------ .../shell/shared/bubbles/BubbleBarLocationTest.kt | 53 +++++ .../wm/shell/shared/bubbles/BubbleInfoTest.kt | 64 ++++++ 58 files changed, 1728 insertions(+), 1739 deletions(-) create mode 100644 libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/BaseBubblePinController.kt create mode 100644 libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/BubbleBarLocation.aidl create mode 100644 libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/BubbleBarLocation.kt create mode 100644 libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/BubbleBarUpdate.java create mode 100644 libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/BubbleConstants.java create mode 100644 libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/BubbleInfo.java create mode 100644 libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/BubblePopupDrawable.kt create mode 100644 libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/BubblePopupView.kt create mode 100644 libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/DismissCircleView.java create mode 100644 libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/DismissView.kt create mode 100644 libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/OWNERS create mode 100644 libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/RelativeTouchListener.kt create mode 100644 libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/RemovedBubble.java delete mode 100644 libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BaseBubblePinController.kt delete mode 100644 libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubbleBarLocation.aidl delete mode 100644 libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubbleBarLocation.kt delete mode 100644 libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubbleBarUpdate.java delete mode 100644 libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubbleConstants.java delete mode 100644 libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubbleInfo.java delete mode 100644 libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubblePopupDrawable.kt delete mode 100644 libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubblePopupView.kt delete mode 100644 libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/DismissCircleView.java delete mode 100644 libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/DismissView.kt delete mode 100644 libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/OWNERS delete mode 100644 libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/RelativeTouchListener.kt delete mode 100644 libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/RemovedBubble.java delete mode 100644 libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/bubbles/BubbleBarLocationTest.kt delete mode 100644 libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/bubbles/BubbleInfoTest.kt create mode 100644 libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/bubbles/BubbleBarLocationTest.kt create mode 100644 libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/bubbles/BubbleInfoTest.kt (limited to 'libs') diff --git a/libs/WindowManager/Shell/Android.bp b/libs/WindowManager/Shell/Android.bp index b338a2ae2b79..a79bc97c440c 100644 --- a/libs/WindowManager/Shell/Android.bp +++ b/libs/WindowManager/Shell/Android.bp @@ -39,17 +39,6 @@ filegroup { path: "src", } -// Sources that have no dependencies that can be used directly downstream of this library -// TODO(b/322791067): move these sources to WindowManager-Shell-shared -filegroup { - name: "wm_shell_util-sources", - srcs: [ - "src/com/android/wm/shell/common/bubbles/*.kt", - "src/com/android/wm/shell/common/bubbles/*.java", - ], - path: "src", -} - // Aidls which can be used directly downstream of this library filegroup { name: "wm_shell-aidls", @@ -184,9 +173,11 @@ java_library { ":wm_shell-shared-aidls", ], static_libs: [ + "androidx.core_core-animation", "androidx.dynamicanimation_dynamicanimation", "jsr330", ], + kotlincflags: ["-Xjvm-default=all"], } java_library { @@ -212,7 +203,6 @@ android_library { ], static_libs: [ "androidx.appcompat_appcompat", - "androidx.core_core-animation", "androidx.core_core-ktx", "androidx.arch.core_core-runtime", "androidx.datastore_datastore", diff --git a/libs/WindowManager/Shell/multivalentScreenshotTests/src/com/android/wm/shell/bubbles/BubbleEducationViewScreenshotTest.kt b/libs/WindowManager/Shell/multivalentScreenshotTests/src/com/android/wm/shell/bubbles/BubbleEducationViewScreenshotTest.kt index d35f493a8f60..f09969d253d3 100644 --- a/libs/WindowManager/Shell/multivalentScreenshotTests/src/com/android/wm/shell/bubbles/BubbleEducationViewScreenshotTest.kt +++ b/libs/WindowManager/Shell/multivalentScreenshotTests/src/com/android/wm/shell/bubbles/BubbleEducationViewScreenshotTest.kt @@ -16,7 +16,7 @@ package com.android.wm.shell.bubbles import android.view.LayoutInflater -import com.android.wm.shell.common.bubbles.BubblePopupView +import com.android.wm.shell.shared.bubbles.BubblePopupView import com.android.wm.shell.testing.goldenpathmanager.WMShellGoldenPathManager import com.android.wm.shell.R import org.junit.Rule diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubblePositionerTest.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubblePositionerTest.kt index 4b97451a0c41..b38d00da6dfa 100644 --- a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubblePositionerTest.kt +++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubblePositionerTest.kt @@ -30,7 +30,7 @@ import androidx.test.filters.SmallTest import com.android.internal.protolog.ProtoLog import com.android.wm.shell.R import com.android.wm.shell.bubbles.BubblePositioner.MAX_HEIGHT -import com.android.wm.shell.common.bubbles.BubbleBarLocation +import com.android.wm.shell.shared.bubbles.BubbleBarLocation import com.google.common.truth.Truth.assertThat import com.google.common.util.concurrent.MoreExecutors.directExecutor import org.junit.Before diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleStackViewTest.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleStackViewTest.kt index faadf1d623c9..96ffa03a1f65 100644 --- a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleStackViewTest.kt +++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleStackViewTest.kt @@ -53,7 +53,7 @@ import org.junit.runner.RunWith import org.mockito.kotlin.mock import android.platform.test.annotations.DisableFlags import android.platform.test.annotations.EnableFlags -import com.android.wm.shell.common.bubbles.BubbleBarLocation +import com.android.wm.shell.shared.bubbles.BubbleBarLocation import java.util.concurrent.Semaphore import java.util.concurrent.TimeUnit import java.util.function.Consumer diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleExpandedViewPinControllerTest.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleExpandedViewPinControllerTest.kt index 935d12916f56..ecb2b25a02f1 100644 --- a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleExpandedViewPinControllerTest.kt +++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleExpandedViewPinControllerTest.kt @@ -31,12 +31,12 @@ import com.android.internal.protolog.ProtoLog import com.android.wm.shell.R import com.android.wm.shell.bubbles.BubblePositioner import com.android.wm.shell.bubbles.DeviceConfig -import com.android.wm.shell.common.bubbles.BaseBubblePinController -import com.android.wm.shell.common.bubbles.BaseBubblePinController.Companion.DROP_TARGET_ALPHA_IN_DURATION -import com.android.wm.shell.common.bubbles.BaseBubblePinController.Companion.DROP_TARGET_ALPHA_OUT_DURATION -import com.android.wm.shell.common.bubbles.BubbleBarLocation -import com.android.wm.shell.common.bubbles.BubbleBarLocation.LEFT -import com.android.wm.shell.common.bubbles.BubbleBarLocation.RIGHT +import com.android.wm.shell.shared.bubbles.BaseBubblePinController +import com.android.wm.shell.shared.bubbles.BaseBubblePinController.Companion.DROP_TARGET_ALPHA_IN_DURATION +import com.android.wm.shell.shared.bubbles.BaseBubblePinController.Companion.DROP_TARGET_ALPHA_OUT_DURATION +import com.android.wm.shell.shared.bubbles.BubbleBarLocation +import com.android.wm.shell.shared.bubbles.BubbleBarLocation.LEFT +import com.android.wm.shell.shared.bubbles.BubbleBarLocation.RIGHT import com.google.common.truth.Truth.assertThat import org.junit.After import org.junit.Before diff --git a/libs/WindowManager/Shell/res/layout/bubble_bar_manage_education.xml b/libs/WindowManager/Shell/res/layout/bubble_bar_manage_education.xml index a0a06f1b3721..806d026a7e7c 100644 --- a/libs/WindowManager/Shell/res/layout/bubble_bar_manage_education.xml +++ b/libs/WindowManager/Shell/res/layout/bubble_bar_manage_education.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/libs/WindowManager/Shell/res/layout/bubble_bar_stack_education.xml b/libs/WindowManager/Shell/res/layout/bubble_bar_stack_education.xml index b489a5c1acd0..7fa586c626be 100644 --- a/libs/WindowManager/Shell/res/layout/bubble_bar_stack_education.xml +++ b/libs/WindowManager/Shell/res/layout/bubble_bar_stack_education.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/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/BaseBubblePinController.kt b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/BaseBubblePinController.kt new file mode 100644 index 000000000000..7086691e7431 --- /dev/null +++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/BaseBubblePinController.kt @@ -0,0 +1,210 @@ +/* + * Copyright (C) 2024 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.wm.shell.shared.bubbles + +import android.graphics.Point +import android.graphics.RectF +import android.view.View +import androidx.annotation.VisibleForTesting +import androidx.core.animation.Animator +import androidx.core.animation.AnimatorListenerAdapter +import androidx.core.animation.ObjectAnimator +import com.android.wm.shell.shared.bubbles.BaseBubblePinController.LocationChangeListener +import com.android.wm.shell.shared.bubbles.BubbleBarLocation.LEFT +import com.android.wm.shell.shared.bubbles.BubbleBarLocation.RIGHT + +/** + * Base class for common logic shared between different bubble views to support pinning bubble bar + * to left or right edge of screen. + * + * Handles drag events and allows a [LocationChangeListener] to be registered that is notified when + * location of the bubble bar should change. + * + * Shows a drop target when releasing a view would update the [BubbleBarLocation]. + */ +abstract class BaseBubblePinController(private val screenSizeProvider: () -> Point) { + + private var initialLocationOnLeft = false + private var onLeft = false + private var dismissZone: RectF? = null + private var stuckToDismissTarget = false + private var screenCenterX = 0 + private var listener: LocationChangeListener? = null + private var dropTargetAnimator: ObjectAnimator? = null + + /** + * Signal the controller that dragging interaction has started. + * + * @param initialLocationOnLeft side of the screen where bubble bar is pinned to + */ + fun onDragStart(initialLocationOnLeft: Boolean) { + this.initialLocationOnLeft = initialLocationOnLeft + onLeft = initialLocationOnLeft + screenCenterX = screenSizeProvider.invoke().x / 2 + dismissZone = getExclusionRect() + } + + /** View has moved to [x] and [y] screen coordinates */ + fun onDragUpdate(x: Float, y: Float) { + if (dismissZone?.contains(x, y) == true) return + + val wasOnLeft = onLeft + onLeft = x < screenCenterX + if (wasOnLeft != onLeft) { + onLocationChange(if (onLeft) LEFT else RIGHT) + } else if (stuckToDismissTarget) { + // Moved out of the dismiss view back to initial side, if we have a drop target, show it + getDropTargetView()?.apply { animateIn() } + } + // Make sure this gets cleared + stuckToDismissTarget = false + } + + /** Signal the controller that view has been dragged to dismiss view. */ + fun onStuckToDismissTarget() { + stuckToDismissTarget = true + // Notify that location may be reset + val shouldResetLocation = onLeft != initialLocationOnLeft + if (shouldResetLocation) { + onLeft = initialLocationOnLeft + listener?.onChange(if (onLeft) LEFT else RIGHT) + } + getDropTargetView()?.apply { + animateOut { + if (shouldResetLocation) { + updateLocation(if (onLeft) LEFT else RIGHT) + } + } + } + } + + /** Signal the controller that dragging interaction has finished. */ + fun onDragEnd() { + getDropTargetView()?.let { view -> view.animateOut { removeDropTargetView(view) } } + dismissZone = null + listener?.onRelease(if (onLeft) LEFT else RIGHT) + } + + /** + * [LocationChangeListener] that is notified when dragging interaction has resulted in bubble + * bar to be pinned on the other edge + */ + fun setListener(listener: LocationChangeListener?) { + this.listener = listener + } + + /** Get width for exclusion rect where dismiss takes over drag */ + protected abstract fun getExclusionRectWidth(): Float + /** Get height for exclusion rect where dismiss takes over drag */ + protected abstract fun getExclusionRectHeight(): Float + + /** Create the drop target view and attach it to the parent */ + protected abstract fun createDropTargetView(): View + + /** Get the drop target view if it exists */ + protected abstract fun getDropTargetView(): View? + + /** Remove the drop target view */ + protected abstract fun removeDropTargetView(view: View) + + /** Update size and location of the drop target view */ + protected abstract fun updateLocation(location: BubbleBarLocation) + + private fun onLocationChange(location: BubbleBarLocation) { + showDropTarget(location) + listener?.onChange(location) + } + + private fun getExclusionRect(): RectF { + val rect = RectF(0f, 0f, getExclusionRectWidth(), getExclusionRectHeight()) + // Center it around the bottom center of the screen + val screenBottom = screenSizeProvider.invoke().y + rect.offsetTo(screenCenterX - rect.width() / 2, screenBottom - rect.height()) + return rect + } + + private fun showDropTarget(location: BubbleBarLocation) { + val targetView = getDropTargetView() ?: createDropTargetView().apply { alpha = 0f } + if (targetView.alpha > 0) { + targetView.animateOut { + updateLocation(location) + targetView.animateIn() + } + } else { + updateLocation(location) + targetView.animateIn() + } + } + + private fun View.animateIn() { + dropTargetAnimator?.cancel() + dropTargetAnimator = + ObjectAnimator.ofFloat(this, View.ALPHA, 1f) + .setDuration(DROP_TARGET_ALPHA_IN_DURATION) + .addEndAction { dropTargetAnimator = null } + dropTargetAnimator?.start() + } + + private fun View.animateOut(endAction: Runnable? = null) { + dropTargetAnimator?.cancel() + dropTargetAnimator = + ObjectAnimator.ofFloat(this, View.ALPHA, 0f) + .setDuration(DROP_TARGET_ALPHA_OUT_DURATION) + .addEndAction { + endAction?.run() + dropTargetAnimator = null + } + dropTargetAnimator?.start() + } + + private fun T.addEndAction(runnable: Runnable): T { + addListener( + object : AnimatorListenerAdapter() { + override fun onAnimationEnd(animation: Animator) { + runnable.run() + } + } + ) + return this + } + + /** Receive updates on location changes */ + interface LocationChangeListener { + /** + * Bubble bar has been dragged to a new [BubbleBarLocation]. And the drag is still in + * progress. + * + * Triggered when drag gesture passes the middle of the screen and before touch up. Can be + * triggered multiple times per gesture. + * + * @param location new location as a result of the ongoing drag operation + */ + fun onChange(location: BubbleBarLocation) {} + + /** + * Bubble bar has been released in the [BubbleBarLocation]. + * + * @param location final location of the bubble bar once drag is released + */ + fun onRelease(location: BubbleBarLocation) + } + + companion object { + @VisibleForTesting const val DROP_TARGET_ALPHA_IN_DURATION = 150L + @VisibleForTesting const val DROP_TARGET_ALPHA_OUT_DURATION = 100L + } +} diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/BubbleBarLocation.aidl b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/BubbleBarLocation.aidl new file mode 100644 index 000000000000..4fe76115fa0b --- /dev/null +++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/BubbleBarLocation.aidl @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2024 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.wm.shell.shared.bubbles; + +parcelable BubbleBarLocation; \ No newline at end of file diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/BubbleBarLocation.kt b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/BubbleBarLocation.kt new file mode 100644 index 000000000000..191875d38daf --- /dev/null +++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/BubbleBarLocation.kt @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2024 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.wm.shell.shared.bubbles + +import android.os.Parcel +import android.os.Parcelable + +/** + * The location of the bubble bar. + */ +enum class BubbleBarLocation : Parcelable { + /** + * Place bubble bar at the default location for the chosen system language. + * If an RTL language is used, it is on the left. Otherwise on the right. + */ + DEFAULT, + /** Default bubble bar location is overridden. Place bubble bar on the left. */ + LEFT, + /** Default bubble bar location is overridden. Place bubble bar on the right. */ + RIGHT; + + /** + * Returns whether bubble bar is pinned to the left edge or right edge. + */ + fun isOnLeft(isRtl: Boolean): Boolean { + if (this == DEFAULT) { + return isRtl + } + return this == LEFT + } + + override fun describeContents(): Int { + return 0 + } + + override fun writeToParcel(dest: Parcel, flags: Int) { + dest.writeString(name) + } + + companion object { + @JvmField + val CREATOR = object : Parcelable.Creator { + override fun createFromParcel(parcel: Parcel): BubbleBarLocation { + return parcel.readString()?.let { valueOf(it) } ?: DEFAULT + } + + override fun newArray(size: Int) = arrayOfNulls(size) + } + } +} diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/BubbleBarUpdate.java b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/BubbleBarUpdate.java new file mode 100644 index 000000000000..5bde1e8fae3b --- /dev/null +++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/BubbleBarUpdate.java @@ -0,0 +1,186 @@ +/* + * Copyright (C) 2024 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.wm.shell.shared.bubbles; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.graphics.Point; +import android.os.Parcel; +import android.os.Parcelable; + +import java.util.ArrayList; +import java.util.List; + +/** + * Represents an update to bubbles state. This is passed through + * {@link com.android.wm.shell.bubbles.IBubblesListener} to launcher so that taskbar may render + * bubbles. This should be kept this as minimal as possible in terms of data. + */ +public class BubbleBarUpdate implements Parcelable { + + public static final String BUNDLE_KEY = "update"; + + public final boolean initialState; + public boolean expandedChanged; + public boolean expanded; + public boolean shouldShowEducation; + @Nullable + public String selectedBubbleKey; + @Nullable + public BubbleInfo addedBubble; + @Nullable + public BubbleInfo updatedBubble; + @Nullable + public String suppressedBubbleKey; + @Nullable + public String unsupressedBubbleKey; + @Nullable + public BubbleBarLocation bubbleBarLocation; + @Nullable + public Point expandedViewDropTargetSize; + public boolean showOverflowChanged; + public boolean showOverflow; + + // This is only populated if bubbles have been removed. + public List removedBubbles = new ArrayList<>(); + + // This is only populated if the order of the bubbles has changed. + public List bubbleKeysInOrder = new ArrayList<>(); + + // This is only populated the first time a listener is connected so it gets the current state. + public List currentBubbleList = new ArrayList<>(); + + + public BubbleBarUpdate() { + this(false); + } + + private BubbleBarUpdate(boolean initialState) { + this.initialState = initialState; + } + + public BubbleBarUpdate(Parcel parcel) { + initialState = parcel.readBoolean(); + expandedChanged = parcel.readBoolean(); + expanded = parcel.readBoolean(); + shouldShowEducation = parcel.readBoolean(); + selectedBubbleKey = parcel.readString(); + addedBubble = parcel.readParcelable(BubbleInfo.class.getClassLoader(), + BubbleInfo.class); + updatedBubble = parcel.readParcelable(BubbleInfo.class.getClassLoader(), + BubbleInfo.class); + suppressedBubbleKey = parcel.readString(); + unsupressedBubbleKey = parcel.readString(); + removedBubbles = parcel.readParcelableList(new ArrayList<>(), + RemovedBubble.class.getClassLoader(), RemovedBubble.class); + parcel.readStringList(bubbleKeysInOrder); + currentBubbleList = parcel.readParcelableList(new ArrayList<>(), + BubbleInfo.class.getClassLoader(), BubbleInfo.class); + bubbleBarLocation = parcel.readParcelable(BubbleBarLocation.class.getClassLoader(), + BubbleBarLocation.class); + expandedViewDropTargetSize = parcel.readParcelable(Point.class.getClassLoader(), + Point.class); + showOverflowChanged = parcel.readBoolean(); + showOverflow = parcel.readBoolean(); + } + + /** + * Returns whether anything has changed in this update. + */ + public boolean anythingChanged() { + return expandedChanged + || selectedBubbleKey != null + || addedBubble != null + || updatedBubble != null + || !removedBubbles.isEmpty() + || !bubbleKeysInOrder.isEmpty() + || suppressedBubbleKey != null + || unsupressedBubbleKey != null + || !currentBubbleList.isEmpty() + || bubbleBarLocation != null + || showOverflowChanged; + } + + @NonNull + @Override + public String toString() { + return "BubbleBarUpdate{" + + " initialState=" + initialState + + " expandedChanged=" + expandedChanged + + " expanded=" + expanded + + " selectedBubbleKey=" + selectedBubbleKey + + " shouldShowEducation=" + shouldShowEducation + + " addedBubble=" + addedBubble + + " updatedBubble=" + updatedBubble + + " suppressedBubbleKey=" + suppressedBubbleKey + + " unsuppressedBubbleKey=" + unsupressedBubbleKey + + " removedBubbles=" + removedBubbles + + " bubbles=" + bubbleKeysInOrder + + " currentBubbleList=" + currentBubbleList + + " bubbleBarLocation=" + bubbleBarLocation + + " expandedViewDropTargetSize=" + expandedViewDropTargetSize + + " showOverflowChanged=" + showOverflowChanged + + " showOverflow=" + showOverflow + + " }"; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel parcel, int flags) { + parcel.writeBoolean(initialState); + parcel.writeBoolean(expandedChanged); + parcel.writeBoolean(expanded); + parcel.writeBoolean(shouldShowEducation); + parcel.writeString(selectedBubbleKey); + parcel.writeParcelable(addedBubble, flags); + parcel.writeParcelable(updatedBubble, flags); + parcel.writeString(suppressedBubbleKey); + parcel.writeString(unsupressedBubbleKey); + parcel.writeParcelableList(removedBubbles, flags); + parcel.writeStringList(bubbleKeysInOrder); + parcel.writeParcelableList(currentBubbleList, flags); + parcel.writeParcelable(bubbleBarLocation, flags); + parcel.writeParcelable(expandedViewDropTargetSize, flags); + parcel.writeBoolean(showOverflowChanged); + parcel.writeBoolean(showOverflow); + } + + /** + * Create update for initial set of values. + *

+ * Used when bubble bar is newly created. + */ + public static BubbleBarUpdate createInitialState() { + return new BubbleBarUpdate(true); + } + + @NonNull + public static final Creator CREATOR = + new Creator<>() { + public BubbleBarUpdate createFromParcel(Parcel source) { + return new BubbleBarUpdate(source); + } + + public BubbleBarUpdate[] newArray(int size) { + return new BubbleBarUpdate[size]; + } + }; +} diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/BubbleConstants.java b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/BubbleConstants.java new file mode 100644 index 000000000000..3396bc441467 --- /dev/null +++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/BubbleConstants.java @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2024 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.wm.shell.shared.bubbles; + +/** + * Constants shared between bubbles in shell & things we have to do for bubbles in launcher. + */ +public class BubbleConstants { + + /** The alpha for the scrim shown when bubbles are expanded. */ + public static float BUBBLE_EXPANDED_SCRIM_ALPHA = .32f; +} diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/BubbleInfo.java b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/BubbleInfo.java new file mode 100644 index 000000000000..58766826bd3b --- /dev/null +++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/BubbleInfo.java @@ -0,0 +1,196 @@ +/* + * Copyright (C) 2024 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.wm.shell.shared.bubbles; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.app.Notification; +import android.graphics.drawable.Icon; +import android.os.Parcel; +import android.os.Parcelable; + +import java.util.Objects; + +/** + * Contains information necessary to present a bubble. + */ +public class BubbleInfo implements Parcelable { + + private String mKey; // Same key as the Notification + private int mFlags; // Flags from BubbleMetadata + @Nullable + private String mShortcutId; + private int mUserId; + private String mPackageName; + /** + * All notification bubbles require a shortcut to be set on the notification, however, the + * app could still specify an Icon and PendingIntent to use for the bubble. In that case + * this icon will be populated. If the bubble is entirely shortcut based, this will be null. + */ + @Nullable + private Icon mIcon; + @Nullable + private String mTitle; + @Nullable + private String mAppName; + private boolean mIsImportantConversation; + private boolean mShowAppBadge; + + public BubbleInfo(String key, int flags, @Nullable String shortcutId, @Nullable Icon icon, + int userId, String packageName, @Nullable String title, @Nullable String appName, + boolean isImportantConversation, boolean showAppBadge) { + mKey = key; + mFlags = flags; + mShortcutId = shortcutId; + mIcon = icon; + mUserId = userId; + mPackageName = packageName; + mTitle = title; + mAppName = appName; + mIsImportantConversation = isImportantConversation; + mShowAppBadge = showAppBadge; + } + + private BubbleInfo(Parcel source) { + mKey = source.readString(); + mFlags = source.readInt(); + mShortcutId = source.readString(); + mIcon = source.readTypedObject(Icon.CREATOR); + mUserId = source.readInt(); + mPackageName = source.readString(); + mTitle = source.readString(); + mAppName = source.readString(); + mIsImportantConversation = source.readBoolean(); + mShowAppBadge = source.readBoolean(); + } + + public String getKey() { + return mKey; + } + + @Nullable + public String getShortcutId() { + return mShortcutId; + } + + @Nullable + public Icon getIcon() { + return mIcon; + } + + public int getFlags() { + return mFlags; + } + + public int getUserId() { + return mUserId; + } + + public String getPackageName() { + return mPackageName; + } + + @Nullable + public String getTitle() { + return mTitle; + } + + @Nullable + public String getAppName() { + return mAppName; + } + + public boolean isImportantConversation() { + return mIsImportantConversation; + } + + public boolean showAppBadge() { + return mShowAppBadge; + } + + /** + * Whether this bubble is currently being hidden from the stack. + */ + public boolean isBubbleSuppressed() { + return (mFlags & Notification.BubbleMetadata.FLAG_SUPPRESS_BUBBLE) != 0; + } + + /** + * Whether this bubble is able to be suppressed (i.e. has the developer opted into the API + * to + * hide the bubble when in the same content). + */ + public boolean isBubbleSuppressable() { + return (mFlags & Notification.BubbleMetadata.FLAG_SUPPRESSABLE_BUBBLE) != 0; + } + + /** + * Whether the notification for this bubble is hidden from the shade. + */ + public boolean isNotificationSuppressed() { + return (mFlags & Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION) != 0; + } + + /** Sets the flags for this bubble. */ + public void setFlags(int flags) { + mFlags = flags; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof BubbleInfo)) return false; + BubbleInfo bubble = (BubbleInfo) o; + return Objects.equals(mKey, bubble.mKey); + } + + @Override + public int hashCode() { + return mKey.hashCode(); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel parcel, int flags) { + parcel.writeString(mKey); + parcel.writeInt(mFlags); + parcel.writeString(mShortcutId); + parcel.writeTypedObject(mIcon, flags); + parcel.writeInt(mUserId); + parcel.writeString(mPackageName); + parcel.writeString(mTitle); + parcel.writeString(mAppName); + parcel.writeBoolean(mIsImportantConversation); + parcel.writeBoolean(mShowAppBadge); + } + + @NonNull + public static final Creator CREATOR = + new Creator<>() { + public BubbleInfo createFromParcel(Parcel source) { + return new BubbleInfo(source); + } + + public BubbleInfo[] newArray(int size) { + return new BubbleInfo[size]; + } + }; +} diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/BubblePopupDrawable.kt b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/BubblePopupDrawable.kt new file mode 100644 index 000000000000..8681acf93ab3 --- /dev/null +++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/BubblePopupDrawable.kt @@ -0,0 +1,232 @@ +/* + * Copyright (C) 2024 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.wm.shell.shared.bubbles + +import android.annotation.ColorInt +import android.graphics.Canvas +import android.graphics.ColorFilter +import android.graphics.Matrix +import android.graphics.Outline +import android.graphics.Paint +import android.graphics.Path +import android.graphics.Rect +import android.graphics.RectF +import android.graphics.drawable.Drawable +import kotlin.math.atan +import kotlin.math.cos +import kotlin.math.sin +import kotlin.properties.Delegates + +/** A drawable for the [BubblePopupView] that draws a popup background with a directional arrow */ +class BubblePopupDrawable(val config: Config) : Drawable() { + /** The direction of the arrow in the popup drawable */ + enum class ArrowDirection { + UP, + DOWN + } + + /** The arrow position on the side of the popup bubble */ + sealed class ArrowPosition { + object Start : ArrowPosition() + object Center : ArrowPosition() + object End : ArrowPosition() + class Custom(val value: Float) : ArrowPosition() + } + + /** The configuration for drawable features */ + data class Config( + @ColorInt val color: Int, + val cornerRadius: Float, + val contentPadding: Int, + val arrowWidth: Float, + val arrowHeight: Float, + val arrowRadius: Float + ) + + /** + * The direction of the arrow in the popup drawable. It affects the content padding and requires + * it to be updated in the view. + */ + var arrowDirection: ArrowDirection by + Delegates.observable(ArrowDirection.UP) { _, _, _ -> requestPathUpdate() } + + /** + * Arrow position along the X axis and its direction. The position is adjusted to the content + * corner radius when applied so it doesn't go into rounded corner area + */ + var arrowPosition: ArrowPosition by + Delegates.observable(ArrowPosition.Center) { _, _, _ -> requestPathUpdate() } + + private val path = Path() + private val paint = Paint() + private var shouldUpdatePath = true + + init { + paint.color = config.color + paint.style = Paint.Style.FILL + paint.isAntiAlias = true + } + + override fun draw(canvas: Canvas) { + updatePathIfNeeded() + canvas.drawPath(path, paint) + } + + override fun onBoundsChange(bounds: Rect) { + requestPathUpdate() + } + + /** Should be applied to the view padding if arrow direction changes */ + override fun getPadding(padding: Rect): Boolean { + padding.set( + config.contentPadding, + config.contentPadding, + config.contentPadding, + config.contentPadding + ) + when (arrowDirection) { + ArrowDirection.UP -> padding.top += config.arrowHeight.toInt() + ArrowDirection.DOWN -> padding.bottom += config.arrowHeight.toInt() + } + return true + } + + override fun getOutline(outline: Outline) { + updatePathIfNeeded() + outline.setPath(path) + } + + override fun getOpacity(): Int { + return paint.alpha + } + + override fun setAlpha(alpha: Int) { + paint.alpha = alpha + } + + override fun setColorFilter(colorFilter: ColorFilter?) { + paint.colorFilter = colorFilter + } + + /** Schedules path update for the next redraw */ + private fun requestPathUpdate() { + shouldUpdatePath = true + } + + /** Updates the path if required, when bounds or arrow direction/position changes */ + private fun updatePathIfNeeded() { + if (shouldUpdatePath) { + updatePath() + shouldUpdatePath = false + } + } + + /** Updates the path value using the current bounds, config, arrow direction and position */ + private fun updatePath() { + if (bounds.isEmpty) return + // Reset the path state + path.reset() + // The content rect where the filled rounded rect will be drawn + val contentRect = RectF(bounds) + when (arrowDirection) { + ArrowDirection.UP -> { + // Add rounded arrow pointing up to the path + addRoundedArrowPositioned(path, arrowPosition) + // Inset content rect by the arrow size from the top + contentRect.top += config.arrowHeight + } + ArrowDirection.DOWN -> { + val matrix = Matrix() + // Flip the path with the matrix to draw arrow pointing down + matrix.setScale(1f, -1f, bounds.width() / 2f, bounds.height() / 2f) + path.transform(matrix) + // Add rounded arrow with the flipped matrix applied, will point down + addRoundedArrowPositioned(path, arrowPosition) + // Restore the path matrix to the original state with inverted matrix + matrix.invert(matrix) + path.transform(matrix) + // Inset content rect by the arrow size from the bottom + contentRect.bottom -= config.arrowHeight + } + } + // Add the content area rounded rect + path.addRoundRect(contentRect, config.cornerRadius, config.cornerRadius, Path.Direction.CW) + } + + /** Add a rounded arrow pointing up in the horizontal position on the canvas */ + private fun addRoundedArrowPositioned(path: Path, position: ArrowPosition) { + val matrix = Matrix() + var translationX = positionValue(position) - config.arrowWidth / 2 + // Offset to position between rounded corners of the content view + translationX = translationX.coerceIn(config.cornerRadius, + bounds.width() - config.cornerRadius - config.arrowWidth) + // Translate to add the arrow in the center horizontally + matrix.setTranslate(-translationX, 0f) + path.transform(matrix) + // Add rounded arrow + addRoundedArrow(path) + // Restore the path matrix to the original state with inverted matrix + matrix.invert(matrix) + path.transform(matrix) + } + + /** Adds a rounded arrow pointing up to the path, can be flipped if needed */ + private fun addRoundedArrow(path: Path) { + // Theta is half of the angle inside the triangle tip + val thetaTan = config.arrowWidth / (config.arrowHeight * 2f) + val theta = atan(thetaTan) + val thetaDeg = Math.toDegrees(theta.toDouble()).toFloat() + // The center Y value of the circle for the triangle tip + val tipCircleCenterY = config.arrowRadius / sin(theta) + // The length from triangle tip to intersection point with the circle + val tipIntersectionSideLength = config.arrowRadius / thetaTan + // The offset from the top to the point of intersection + val intersectionTopOffset = tipIntersectionSideLength * cos(theta) + // The offset from the center to the point of intersection + val intersectionCenterOffset = tipIntersectionSideLength * sin(theta) + // The center X of the triangle + val arrowCenterX = config.arrowWidth / 2f + + // Set initial position in bottom left of the arrow + path.moveTo(0f, config.arrowHeight) + // Add the left side of the triangle + path.lineTo(arrowCenterX - intersectionCenterOffset, intersectionTopOffset) + // Add the arc from the left to the right side of the triangle + path.arcTo( + /* left = */ arrowCenterX - config.arrowRadius, + /* top = */ tipCircleCenterY - config.arrowRadius, + /* right = */ arrowCenterX + config.arrowRadius, + /* bottom = */ tipCircleCenterY + config.arrowRadius, + /* startAngle = */ 180 + thetaDeg, + /* sweepAngle = */ 180 - (2 * thetaDeg), + /* forceMoveTo = */ false + ) + // Add the right side of the triangle + path.lineTo(config.arrowWidth, config.arrowHeight) + // Close the path + path.close() + } + + /** The value of the arrow position provided the position and current bounds */ + private fun positionValue(position: ArrowPosition): Float { + return when (position) { + is ArrowPosition.Start -> 0f + is ArrowPosition.Center -> bounds.width().toFloat() / 2f + is ArrowPosition.End -> bounds.width().toFloat() + is ArrowPosition.Custom -> position.value + } + } +} diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/BubblePopupView.kt b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/BubblePopupView.kt new file mode 100644 index 000000000000..802d7d131d95 --- /dev/null +++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/BubblePopupView.kt @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2024 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.wm.shell.shared.bubbles + +import android.content.Context +import android.graphics.Rect +import android.util.AttributeSet +import android.widget.LinearLayout + +/** A popup container view that uses [BubblePopupDrawable] as a background */ +open class BubblePopupView +@JvmOverloads +constructor( + context: Context, + attrs: AttributeSet? = null, + defStyleAttr: Int = 0, + defStyleRes: Int = 0 +) : LinearLayout(context, attrs, defStyleAttr, defStyleRes) { + var popupDrawable: BubblePopupDrawable? = null + private set + + /** + * Sets up the popup drawable with the config provided. Required to remove dependency on local + * resources + */ + fun setupBackground(config: BubblePopupDrawable.Config) { + popupDrawable = BubblePopupDrawable(config) + background = popupDrawable + forceLayout() + } + + /** + * Sets the arrow direction for the background drawable and updates the padding to fit the + * content inside of the popup drawable + */ + fun setArrowDirection(direction: BubblePopupDrawable.ArrowDirection) { + popupDrawable?.let { + it.arrowDirection = direction + val padding = Rect() + if (it.getPadding(padding)) { + setPadding(padding.left, padding.top, padding.right, padding.bottom) + } + } + } + + /** Sets the arrow position for the background drawable and triggers redraw */ + fun setArrowPosition(position: BubblePopupDrawable.ArrowPosition) { + popupDrawable?.let { + it.arrowPosition = position + invalidate() + } + } +} diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/DismissCircleView.java b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/DismissCircleView.java new file mode 100644 index 000000000000..0c051560f714 --- /dev/null +++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/DismissCircleView.java @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2024 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.wm.shell.shared.bubbles; + +import android.content.Context; +import android.content.res.Configuration; +import android.view.Gravity; +import android.widget.FrameLayout; +import android.widget.ImageView; + +import androidx.annotation.DimenRes; +import androidx.annotation.DrawableRes; +import androidx.core.content.ContextCompat; + +/** + * Circular view with a semitransparent, circular background with an 'X' inside it. + * + * This is used by both Bubbles and PIP as the dismiss target. + */ +public class DismissCircleView extends FrameLayout { + @DrawableRes int mBackgroundResId; + @DimenRes int mIconSizeResId; + + private final ImageView mIconView = new ImageView(getContext()); + + public DismissCircleView(Context context) { + super(context); + addView(mIconView); + } + + @Override + protected void onConfigurationChanged(Configuration newConfig) { + super.onConfigurationChanged(newConfig); + setBackground(ContextCompat.getDrawable(getContext(), mBackgroundResId)); + setViewSizes(); + } + + /** + * Sets up view with the provided resource ids. + * Decouples resource dependency in order to be used externally (e.g. Launcher) + * + * @param backgroundResId drawable resource id of the circle background + * @param iconResId drawable resource id of the icon for the dismiss view + * @param iconSizeResId dimen resource id of the icon size + */ + public void setup(@DrawableRes int backgroundResId, @DrawableRes int iconResId, + @DimenRes int iconSizeResId) { + mBackgroundResId = backgroundResId; + mIconSizeResId = iconSizeResId; + + setBackground(ContextCompat.getDrawable(getContext(), backgroundResId)); + mIconView.setImageDrawable(ContextCompat.getDrawable(getContext(), iconResId)); + setViewSizes(); + } + + /** Retrieves the current dimensions for the icon and circle and applies them. */ + private void setViewSizes() { + final int iconSize = getResources().getDimensionPixelSize(mIconSizeResId); + mIconView.setLayoutParams( + new FrameLayout.LayoutParams(iconSize, iconSize, Gravity.CENTER)); + } +} diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/DismissView.kt b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/DismissView.kt new file mode 100644 index 000000000000..2bb66b0bbcd3 --- /dev/null +++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/DismissView.kt @@ -0,0 +1,229 @@ +/* + * Copyright (C) 2024 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.wm.shell.shared.bubbles + +import android.animation.ObjectAnimator +import android.content.Context +import android.graphics.Color +import android.graphics.drawable.GradientDrawable +import android.util.IntProperty +import android.util.Log +import android.view.Gravity +import android.view.View +import android.view.ViewGroup +import android.view.WindowInsets +import android.view.WindowManager +import android.widget.FrameLayout +import androidx.annotation.ColorRes +import androidx.annotation.DimenRes +import androidx.annotation.DrawableRes +import androidx.core.content.ContextCompat +import androidx.dynamicanimation.animation.DynamicAnimation +import androidx.dynamicanimation.animation.SpringForce.DAMPING_RATIO_LOW_BOUNCY +import androidx.dynamicanimation.animation.SpringForce.STIFFNESS_LOW +import com.android.wm.shell.shared.animation.PhysicsAnimator + +/** + * View that handles interactions between DismissCircleView and BubbleStackView. + * + * @note [setup] method should be called after initialisation + */ +class DismissView(context: Context) : FrameLayout(context) { + /** + * The configuration is used to provide module specific resource ids + * + * @see [setup] method + */ + data class Config( + /** The resource id to set on the dismiss target circle view */ + val dismissViewResId: Int, + /** dimen resource id of the dismiss target circle view size */ + @DimenRes val targetSizeResId: Int, + /** dimen resource id of the icon size in the dismiss target */ + @DimenRes val iconSizeResId: Int, + /** dimen resource id of the bottom margin for the dismiss target */ + @DimenRes var bottomMarginResId: Int, + /** dimen resource id of the height for dismiss area gradient */ + @DimenRes val floatingGradientHeightResId: Int, + /** color resource id of the dismiss area gradient color */ + @ColorRes val floatingGradientColorResId: Int, + /** drawable resource id of the dismiss target background */ + @DrawableRes val backgroundResId: Int, + /** drawable resource id of the icon for the dismiss target */ + @DrawableRes val iconResId: Int + ) + + companion object { + private const val SHOULD_SETUP = + "The view isn't ready. Should be called after `setup`" + private val TAG = DismissView::class.simpleName + } + + var circle = DismissCircleView(context) + var isShowing = false + var config: Config? = null + + private val animator = PhysicsAnimator.getInstance(circle) + private val spring = PhysicsAnimator.SpringConfig(STIFFNESS_LOW, DAMPING_RATIO_LOW_BOUNCY) + private val DISMISS_SCRIM_FADE_MS = 200L + private var wm: WindowManager = + context.getSystemService(Context.WINDOW_SERVICE) as WindowManager + private var gradientDrawable: GradientDrawable? = null + + private val GRADIENT_ALPHA: IntProperty = + object : IntProperty("alpha") { + override fun setValue(d: GradientDrawable, percent: Int) { + d.alpha = percent + } + override fun get(d: GradientDrawable): Int { + return d.alpha + } + } + + init { + setClipToPadding(false) + setClipChildren(false) + setVisibility(View.INVISIBLE) + addView(circle) + } + + /** + * Sets up view with the provided resource ids. + * + * Decouples resource dependency in order to be used externally (e.g. Launcher). Usually called + * with default params in module specific extension: + * @see [DismissView.setup] in DismissViewExt.kt + */ + fun setup(config: Config) { + this.config = config + + // Setup layout + layoutParams = LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + resources.getDimensionPixelSize(config.floatingGradientHeightResId), + Gravity.BOTTOM) + updatePadding() + + // Setup gradient + gradientDrawable = createGradient(color = config.floatingGradientColorResId) + setBackgroundDrawable(gradientDrawable) + + // Setup DismissCircleView + circle.id = config.dismissViewResId + circle.setup(config.backgroundResId, config.iconResId, config.iconSizeResId) + val targetSize: Int = resources.getDimensionPixelSize(config.targetSizeResId) + circle.layoutParams = LayoutParams(targetSize, targetSize, + Gravity.BOTTOM or Gravity.CENTER_HORIZONTAL) + // Initial position with circle offscreen so it's animated up + circle.translationY = resources.getDimensionPixelSize(config.floatingGradientHeightResId) + .toFloat() + } + + /** + * Animates this view in. + */ + fun show() { + if (isShowing) return + val gradientDrawable = checkExists(gradientDrawable) ?: return + isShowing = true + setVisibility(View.VISIBLE) + val alphaAnim = ObjectAnimator.ofInt(gradientDrawable, GRADIENT_ALPHA, + gradientDrawable.alpha, 255) + alphaAnim.setDuration(DISMISS_SCRIM_FADE_MS) + alphaAnim.start() + + animator.cancel() + animator + .spring(DynamicAnimation.TRANSLATION_Y, 0f, spring) + .start() + } + + /** + * Animates this view out, as well as the circle that encircles the bubbles, if they + * were dragged into the target and encircled. + */ + fun hide() { + if (!isShowing) return + val gradientDrawable = checkExists(gradientDrawable) ?: return + isShowing = false + val alphaAnim = ObjectAnimator.ofInt(gradientDrawable, GRADIENT_ALPHA, + gradientDrawable.alpha, 0) + alphaAnim.setDuration(DISMISS_SCRIM_FADE_MS) + alphaAnim.start() + animator + .spring(DynamicAnimation.TRANSLATION_Y, height.toFloat(), + spring) + .withEndActions({ + visibility = View.INVISIBLE + circle.scaleX = 1f + circle.scaleY = 1f + }) + .start() + } + + /** + * Cancels the animator for the dismiss target. + */ + fun cancelAnimators() { + animator.cancel() + } + + fun updateResources() { + val config = checkExists(config) ?: return + updatePadding() + layoutParams.height = resources.getDimensionPixelSize(config.floatingGradientHeightResId) + val targetSize = resources.getDimensionPixelSize(config.targetSizeResId) + circle.layoutParams.width = targetSize + circle.layoutParams.height = targetSize + circle.requestLayout() + } + + private fun createGradient(@ColorRes color: Int): GradientDrawable { + val gradientColor = ContextCompat.getColor(context, color) + val alpha = 0.7f * 255 + val gradientColorWithAlpha = Color.argb(alpha.toInt(), + Color.red(gradientColor), + Color.green(gradientColor), + Color.blue(gradientColor)) + val gd = GradientDrawable( + GradientDrawable.Orientation.BOTTOM_TOP, + intArrayOf(gradientColorWithAlpha, Color.TRANSPARENT)) + gd.setDither(true) + gd.setAlpha(0) + return gd + } + + private fun updatePadding() { + val config = checkExists(config) ?: return + val insets: WindowInsets = wm.getCurrentWindowMetrics().getWindowInsets() + val navInset = insets.getInsetsIgnoringVisibility( + WindowInsets.Type.navigationBars()) + setPadding(0, 0, 0, navInset.bottom + + resources.getDimensionPixelSize(config.bottomMarginResId)) + } + + /** + * Checks if the value is set up and exists, if not logs an exception. + * Used for convenient logging in case `setup` wasn't called before + * + * @return value provided as argument + */ + private fun checkExists(value: T?): T? { + if (value == null) Log.e(TAG, SHOULD_SETUP) + return value + } +} diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/OWNERS b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/OWNERS new file mode 100644 index 000000000000..08c70314973e --- /dev/null +++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/OWNERS @@ -0,0 +1,6 @@ +# WM shell sub-module bubble owner +madym@google.com +atsjenk@google.com +liranb@google.com +sukeshram@google.com +mpodolian@google.com diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/RelativeTouchListener.kt b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/RelativeTouchListener.kt new file mode 100644 index 000000000000..b1f4e331a98d --- /dev/null +++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/RelativeTouchListener.kt @@ -0,0 +1,180 @@ +/* + * Copyright (C) 2020 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.wm.shell.shared.bubbles + +import android.graphics.PointF +import android.view.MotionEvent +import android.view.VelocityTracker +import android.view.View +import android.view.ViewConfiguration +import kotlin.math.hypot + +/** + * Listener which receives [onDown], [onMove], and [onUp] events, with relevant information about + * the coordinates of the touch and the view relative to the initial ACTION_DOWN event and the + * view's initial position. + */ +abstract class RelativeTouchListener : View.OnTouchListener { + + /** + * Called when an ACTION_DOWN event is received for the given view. + * + * @return False if the object is not interested in MotionEvents at this time, or true if we + * should consume this event and subsequent events, and begin calling [onMove]. + */ + abstract fun onDown(v: View, ev: MotionEvent): Boolean + + /** + * Called when an ACTION_MOVE event is received for the given view. This signals that the view + * is being dragged. + * + * @param viewInitialX The view's translationX value when this touch gesture started. + * @param viewInitialY The view's translationY value when this touch gesture started. + * @param dx Horizontal distance covered since the initial ACTION_DOWN event, in pixels. + * @param dy Vertical distance covered since the initial ACTION_DOWN event, in pixels. + */ + abstract fun onMove( + v: View, + ev: MotionEvent, + viewInitialX: Float, + viewInitialY: Float, + dx: Float, + dy: Float + ) + + /** + * Called when an ACTION_UP event is received for the given view. This signals that a drag or + * fling gesture has completed. + * + * @param viewInitialX The view's translationX value when this touch gesture started. + * @param viewInitialY The view's translationY value when this touch gesture started. + * @param dx Horizontal distance covered, in pixels. + * @param dy Vertical distance covered, in pixels. + * @param velX The final horizontal velocity of the gesture, in pixels/second. + * @param velY The final vertical velocity of the gesture, in pixels/second. + */ + abstract fun onUp( + v: View, + ev: MotionEvent, + viewInitialX: Float, + viewInitialY: Float, + dx: Float, + dy: Float, + velX: Float, + velY: Float + ) + + open fun onCancel( + v: View, + ev: MotionEvent, + viewInitialX: Float, + viewInitialY: Float + ) {} + + /** The raw coordinates of the last ACTION_DOWN event. */ + private var touchDown: PointF? = null + + /** The coordinates of the view, at the time of the last ACTION_DOWN event. */ + private val viewPositionOnTouchDown = PointF() + + private val velocityTracker = VelocityTracker.obtain() + + private var touchSlop: Int = -1 + private var movedEnough = false + + private var performedLongClick = false + + override fun onTouch(v: View, ev: MotionEvent): Boolean { + addMovement(ev) + + val dx = touchDown?.let { ev.rawX - it.x } ?: 0f + val dy = touchDown?.let { ev.rawY - it.y } ?: 0f + + when (ev.action) { + MotionEvent.ACTION_DOWN -> { + if (!onDown(v, ev)) { + return false + } + + // Grab the touch slop, it might have changed if the config changed since the + // last gesture. + touchSlop = ViewConfiguration.get(v.context).scaledTouchSlop + + touchDown = PointF(ev.rawX, ev.rawY) + viewPositionOnTouchDown.set(v.translationX, v.translationY) + + performedLongClick = false + v.handler?.postDelayed({ + if (v.isLongClickable) { + performedLongClick = v.performLongClick() + } + }, ViewConfiguration.getLongPressTimeout().toLong()) + } + + MotionEvent.ACTION_MOVE -> { + if (touchDown == null) return false + if (!movedEnough && hypot(dx, dy) > touchSlop && !performedLongClick) { + movedEnough = true + v.handler?.removeCallbacksAndMessages(null) + } + + if (movedEnough) { + onMove(v, ev, viewPositionOnTouchDown.x, viewPositionOnTouchDown.y, dx, dy) + } + } + + MotionEvent.ACTION_UP -> { + if (touchDown == null) return false + if (movedEnough) { + velocityTracker.computeCurrentVelocity(1000 /* units */) + onUp(v, ev, viewPositionOnTouchDown.x, viewPositionOnTouchDown.y, dx, dy, + velocityTracker.xVelocity, velocityTracker.yVelocity) + } else if (!performedLongClick) { + v.performClick() + } else { + v.handler?.removeCallbacksAndMessages(null) + } + + velocityTracker.clear() + movedEnough = false + touchDown = null + } + + MotionEvent.ACTION_CANCEL -> { + if (touchDown == null) return false + v.handler?.removeCallbacksAndMessages(null) + velocityTracker.clear() + movedEnough = false + touchDown = null + onCancel(v, ev, viewPositionOnTouchDown.x, viewPositionOnTouchDown.y) + } + } + + return true + } + + /** + * Adds a movement to the velocity tracker using raw screen coordinates. + */ + private fun addMovement(event: MotionEvent) { + val deltaX = event.rawX - event.x + val deltaY = event.rawY - event.y + event.offsetLocation(deltaX, deltaY) + velocityTracker.addMovement(event) + event.offsetLocation(-deltaX, -deltaY) + } +} \ No newline at end of file diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/RemovedBubble.java b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/RemovedBubble.java new file mode 100644 index 000000000000..c83696c01613 --- /dev/null +++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/RemovedBubble.java @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2024 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.wm.shell.shared.bubbles; + +import android.annotation.NonNull; +import android.os.Parcel; +import android.os.Parcelable; + +/** + * Represents a removed bubble, defining the key and reason the bubble was removed. + */ +public class RemovedBubble implements Parcelable { + + private final String mKey; + private final int mRemovalReason; + + public RemovedBubble(String key, int removalReason) { + mKey = key; + mRemovalReason = removalReason; + } + + public RemovedBubble(Parcel parcel) { + mKey = parcel.readString(); + mRemovalReason = parcel.readInt(); + } + + public String getKey() { + return mKey; + } + + public int getRemovalReason() { + return mRemovalReason; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(mKey); + dest.writeInt(mRemovalReason); + } + + @NonNull + public static final Creator CREATOR = + new Creator() { + public RemovedBubble createFromParcel(Parcel source) { + return new RemovedBubble(source); + } + public RemovedBubble[] newArray(int size) { + return new RemovedBubble[size]; + } + }; +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java index 4622dcffb3cc..0c95934abf93 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java @@ -53,9 +53,9 @@ import com.android.launcher3.icons.BubbleIconFactory; import com.android.wm.shell.Flags; import com.android.wm.shell.bubbles.bar.BubbleBarExpandedView; import com.android.wm.shell.bubbles.bar.BubbleBarLayerView; -import com.android.wm.shell.common.bubbles.BubbleInfo; import com.android.wm.shell.shared.annotations.ShellBackgroundThread; import com.android.wm.shell.shared.annotations.ShellMainThread; +import com.android.wm.shell.shared.bubbles.BubbleInfo; import java.io.PrintWriter; import java.util.List; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java index b508c1ba7fe4..c545d73734f0 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java @@ -104,14 +104,14 @@ import com.android.wm.shell.common.SingleInstanceRemoteListener; import com.android.wm.shell.common.SyncTransactionQueue; import com.android.wm.shell.common.TaskStackListenerCallback; import com.android.wm.shell.common.TaskStackListenerImpl; -import com.android.wm.shell.common.bubbles.BubbleBarLocation; -import com.android.wm.shell.common.bubbles.BubbleBarUpdate; import com.android.wm.shell.draganddrop.DragAndDropController; import com.android.wm.shell.onehanded.OneHandedController; import com.android.wm.shell.onehanded.OneHandedTransitionCallback; import com.android.wm.shell.pip.PinnedStackListenerForwarder; import com.android.wm.shell.shared.annotations.ShellBackgroundThread; import com.android.wm.shell.shared.annotations.ShellMainThread; +import com.android.wm.shell.shared.bubbles.BubbleBarLocation; +import com.android.wm.shell.shared.bubbles.BubbleBarUpdate; import com.android.wm.shell.sysui.ConfigurationChangeListener; import com.android.wm.shell.sysui.ShellCommandHandler; import com.android.wm.shell.sysui.ShellController; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java index 4ad1802cba7f..709a7bdc61f2 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java @@ -41,10 +41,10 @@ import com.android.internal.protolog.ProtoLog; import com.android.internal.util.FrameworkStatsLog; import com.android.wm.shell.R; import com.android.wm.shell.bubbles.Bubbles.DismissReason; -import com.android.wm.shell.common.bubbles.BubbleBarUpdate; -import com.android.wm.shell.common.bubbles.RemovedBubble; import com.android.wm.shell.shared.annotations.ShellBackgroundThread; import com.android.wm.shell.shared.annotations.ShellMainThread; +import com.android.wm.shell.shared.bubbles.BubbleBarUpdate; +import com.android.wm.shell.shared.bubbles.RemovedBubble; import java.io.PrintWriter; import java.util.ArrayList; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedViewManager.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedViewManager.kt index 4e80e903b522..ec4854b47aff 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedViewManager.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedViewManager.kt @@ -16,7 +16,7 @@ package com.android.wm.shell.bubbles -import com.android.wm.shell.common.bubbles.BubbleBarLocation +import com.android.wm.shell.shared.bubbles.BubbleBarLocation /** Manager interface for bubble expanded views. */ interface BubbleExpandedViewManager { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePopupViewExt.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePopupViewExt.kt index bdb09e11d5ad..fd110a276826 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePopupViewExt.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePopupViewExt.kt @@ -17,8 +17,8 @@ package com.android.wm.shell.bubbles import android.graphics.Color import com.android.wm.shell.R -import com.android.wm.shell.common.bubbles.BubblePopupDrawable -import com.android.wm.shell.common.bubbles.BubblePopupView +import com.android.wm.shell.shared.bubbles.BubblePopupDrawable +import com.android.wm.shell.shared.bubbles.BubblePopupView /** * A convenience method to setup the [BubblePopupView] with the correct config using local resources diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java index 0cf187bd9c0f..c386c9398624 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java @@ -32,7 +32,7 @@ import androidx.annotation.VisibleForTesting; import com.android.internal.protolog.ProtoLog; import com.android.launcher3.icons.IconNormalizer; import com.android.wm.shell.R; -import com.android.wm.shell.common.bubbles.BubbleBarLocation; +import com.android.wm.shell.shared.bubbles.BubbleBarLocation; /** * Keeps track of display size, configuration, and specific bubble sizes. One place for all diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java index 53bbf888df5a..2795881f0938 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java @@ -24,10 +24,10 @@ import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME import static com.android.wm.shell.bubbles.BubblePositioner.NUM_VISIBLE_WHEN_RESTING; import static com.android.wm.shell.bubbles.BubblePositioner.StackPinnedEdge.LEFT; import static com.android.wm.shell.bubbles.BubblePositioner.StackPinnedEdge.RIGHT; -import static com.android.wm.shell.common.bubbles.BubbleConstants.BUBBLE_EXPANDED_SCRIM_ALPHA; import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_BUBBLES; import static com.android.wm.shell.shared.animation.Interpolators.ALPHA_IN; import static com.android.wm.shell.shared.animation.Interpolators.ALPHA_OUT; +import static com.android.wm.shell.shared.bubbles.BubbleConstants.BUBBLE_EXPANDED_SCRIM_ALPHA; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; @@ -91,8 +91,8 @@ import com.android.wm.shell.bubbles.animation.PhysicsAnimationLayout; import com.android.wm.shell.bubbles.animation.StackAnimationController; import com.android.wm.shell.common.FloatingContentCoordinator; import com.android.wm.shell.common.ShellExecutor; -import com.android.wm.shell.common.bubbles.DismissView; -import com.android.wm.shell.common.bubbles.RelativeTouchListener; +import com.android.wm.shell.shared.bubbles.DismissView; +import com.android.wm.shell.shared.bubbles.RelativeTouchListener; import com.android.wm.shell.shared.animation.Interpolators; import com.android.wm.shell.shared.animation.PhysicsAnimator; import com.android.wm.shell.shared.magnetictarget.MagnetizedObject; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java index 9a27fb65ac2c..62895fe7c7cc 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java @@ -38,9 +38,9 @@ import android.window.ScreenCapture.SynchronousScreenCaptureListener; import androidx.annotation.IntDef; import androidx.annotation.Nullable; -import com.android.wm.shell.common.bubbles.BubbleBarLocation; -import com.android.wm.shell.common.bubbles.BubbleBarUpdate; import com.android.wm.shell.shared.annotations.ExternalThread; +import com.android.wm.shell.shared.bubbles.BubbleBarLocation; +import com.android.wm.shell.shared.bubbles.BubbleBarUpdate; import java.lang.annotation.Retention; import java.lang.annotation.Target; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/DismissViewExt.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/DismissViewExt.kt index 48692d41016e..00a81727a9ac 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/DismissViewExt.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/DismissViewExt.kt @@ -18,7 +18,7 @@ package com.android.wm.shell.bubbles import com.android.wm.shell.R -import com.android.wm.shell.common.bubbles.DismissView +import com.android.wm.shell.shared.bubbles.DismissView fun DismissView.setup() { setup(DismissView.Config( diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubbles.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubbles.aidl index 5779a8f7bcc4..1855b938f48e 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubbles.aidl +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubbles.aidl @@ -20,7 +20,7 @@ import android.content.Intent; import android.graphics.Rect; import android.content.pm.ShortcutInfo; import com.android.wm.shell.bubbles.IBubblesListener; -import com.android.wm.shell.common.bubbles.BubbleBarLocation; +import com.android.wm.shell.shared.bubbles.BubbleBarLocation; /** * Interface that is exposed to remote callers (launcher) to manipulate the bubbles feature when diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubblesListener.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubblesListener.aidl index 14d29cd887bb..eb907dbb6597 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubblesListener.aidl +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubblesListener.aidl @@ -17,7 +17,7 @@ package com.android.wm.shell.bubbles; import android.os.Bundle; -import com.android.wm.shell.common.bubbles.BubbleBarLocation; +import com.android.wm.shell.shared.bubbles.BubbleBarLocation; /** * Listener interface that Launcher attaches to SystemUI to get bubbles callbacks. */ diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java index 6d868d215482..694b1b0c2532 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java @@ -46,7 +46,7 @@ import com.android.wm.shell.bubbles.BubblePositioner; import com.android.wm.shell.bubbles.BubbleTaskView; import com.android.wm.shell.bubbles.BubbleTaskViewHelper; import com.android.wm.shell.bubbles.Bubbles; -import com.android.wm.shell.common.bubbles.BubbleBarLocation; +import com.android.wm.shell.shared.bubbles.BubbleBarLocation; import com.android.wm.shell.taskview.TaskView; import java.util.function.Supplier; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewDragController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewDragController.kt index eeb5c94c8f81..07463bb024a2 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewDragController.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewDragController.kt @@ -20,8 +20,8 @@ import android.annotation.SuppressLint import android.view.MotionEvent import android.view.View import com.android.wm.shell.bubbles.BubblePositioner -import com.android.wm.shell.common.bubbles.DismissView -import com.android.wm.shell.common.bubbles.RelativeTouchListener +import com.android.wm.shell.shared.bubbles.DismissView +import com.android.wm.shell.shared.bubbles.RelativeTouchListener import com.android.wm.shell.shared.magnetictarget.MagnetizedObject /** Controller for handling drag interactions with [BubbleBarExpandedView] */ diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java index ac424532e87b..1c9c195cf718 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java @@ -44,9 +44,9 @@ import com.android.wm.shell.bubbles.BubbleViewProvider; import com.android.wm.shell.bubbles.DeviceConfig; import com.android.wm.shell.bubbles.DismissViewUtils; import com.android.wm.shell.bubbles.bar.BubbleBarExpandedViewDragController.DragListener; -import com.android.wm.shell.common.bubbles.BaseBubblePinController; -import com.android.wm.shell.common.bubbles.BubbleBarLocation; -import com.android.wm.shell.common.bubbles.DismissView; +import com.android.wm.shell.shared.bubbles.BaseBubblePinController; +import com.android.wm.shell.shared.bubbles.BubbleBarLocation; +import com.android.wm.shell.shared.bubbles.DismissView; import kotlin.Unit; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleEducationViewController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleEducationViewController.kt index e108f7be48c7..9fd255ded0ad 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleEducationViewController.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleEducationViewController.kt @@ -34,9 +34,9 @@ import com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME import com.android.wm.shell.bubbles.BubbleEducationController import com.android.wm.shell.bubbles.BubbleViewProvider import com.android.wm.shell.bubbles.setup -import com.android.wm.shell.common.bubbles.BubblePopupDrawable -import com.android.wm.shell.common.bubbles.BubblePopupView import com.android.wm.shell.shared.animation.PhysicsAnimator +import com.android.wm.shell.shared.bubbles.BubblePopupDrawable +import com.android.wm.shell.shared.bubbles.BubblePopupView import kotlin.math.roundToInt /** Manages bubble education presentation and animation */ diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleExpandedViewPinController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleExpandedViewPinController.kt index 651bf022e07d..23ba2bff5ebc 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleExpandedViewPinController.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleExpandedViewPinController.kt @@ -25,8 +25,8 @@ import android.widget.FrameLayout import androidx.core.view.updateLayoutParams import com.android.wm.shell.R import com.android.wm.shell.bubbles.BubblePositioner -import com.android.wm.shell.common.bubbles.BaseBubblePinController -import com.android.wm.shell.common.bubbles.BubbleBarLocation +import com.android.wm.shell.shared.bubbles.BaseBubblePinController +import com.android.wm.shell.shared.bubbles.BubbleBarLocation /** * Controller to manage pinning bubble bar to left or right when dragging starts from the bubble bar diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BaseBubblePinController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BaseBubblePinController.kt deleted file mode 100644 index eec24683db8a..000000000000 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BaseBubblePinController.kt +++ /dev/null @@ -1,210 +0,0 @@ -/* - * Copyright (C) 2024 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.wm.shell.common.bubbles - -import android.graphics.Point -import android.graphics.RectF -import android.view.View -import androidx.annotation.VisibleForTesting -import androidx.core.animation.Animator -import androidx.core.animation.AnimatorListenerAdapter -import androidx.core.animation.ObjectAnimator -import com.android.wm.shell.common.bubbles.BaseBubblePinController.LocationChangeListener -import com.android.wm.shell.common.bubbles.BubbleBarLocation.LEFT -import com.android.wm.shell.common.bubbles.BubbleBarLocation.RIGHT - -/** - * Base class for common logic shared between different bubble views to support pinning bubble bar - * to left or right edge of screen. - * - * Handles drag events and allows a [LocationChangeListener] to be registered that is notified when - * location of the bubble bar should change. - * - * Shows a drop target when releasing a view would update the [BubbleBarLocation]. - */ -abstract class BaseBubblePinController(private val screenSizeProvider: () -> Point) { - - private var initialLocationOnLeft = false - private var onLeft = false - private var dismissZone: RectF? = null - private var stuckToDismissTarget = false - private var screenCenterX = 0 - private var listener: LocationChangeListener? = null - private var dropTargetAnimator: ObjectAnimator? = null - - /** - * Signal the controller that dragging interaction has started. - * - * @param initialLocationOnLeft side of the screen where bubble bar is pinned to - */ - fun onDragStart(initialLocationOnLeft: Boolean) { - this.initialLocationOnLeft = initialLocationOnLeft - onLeft = initialLocationOnLeft - screenCenterX = screenSizeProvider.invoke().x / 2 - dismissZone = getExclusionRect() - } - - /** View has moved to [x] and [y] screen coordinates */ - fun onDragUpdate(x: Float, y: Float) { - if (dismissZone?.contains(x, y) == true) return - - val wasOnLeft = onLeft - onLeft = x < screenCenterX - if (wasOnLeft != onLeft) { - onLocationChange(if (onLeft) LEFT else RIGHT) - } else if (stuckToDismissTarget) { - // Moved out of the dismiss view back to initial side, if we have a drop target, show it - getDropTargetView()?.apply { animateIn() } - } - // Make sure this gets cleared - stuckToDismissTarget = false - } - - /** Signal the controller that view has been dragged to dismiss view. */ - fun onStuckToDismissTarget() { - stuckToDismissTarget = true - // Notify that location may be reset - val shouldResetLocation = onLeft != initialLocationOnLeft - if (shouldResetLocation) { - onLeft = initialLocationOnLeft - listener?.onChange(if (onLeft) LEFT else RIGHT) - } - getDropTargetView()?.apply { - animateOut { - if (shouldResetLocation) { - updateLocation(if (onLeft) LEFT else RIGHT) - } - } - } - } - - /** Signal the controller that dragging interaction has finished. */ - fun onDragEnd() { - getDropTargetView()?.let { view -> view.animateOut { removeDropTargetView(view) } } - dismissZone = null - listener?.onRelease(if (onLeft) LEFT else RIGHT) - } - - /** - * [LocationChangeListener] that is notified when dragging interaction has resulted in bubble - * bar to be pinned on the other edge - */ - fun setListener(listener: LocationChangeListener?) { - this.listener = listener - } - - /** Get width for exclusion rect where dismiss takes over drag */ - protected abstract fun getExclusionRectWidth(): Float - /** Get height for exclusion rect where dismiss takes over drag */ - protected abstract fun getExclusionRectHeight(): Float - - /** Create the drop target view and attach it to the parent */ - protected abstract fun createDropTargetView(): View - - /** Get the drop target view if it exists */ - protected abstract fun getDropTargetView(): View? - - /** Remove the drop target view */ - protected abstract fun removeDropTargetView(view: View) - - /** Update size and location of the drop target view */ - protected abstract fun updateLocation(location: BubbleBarLocation) - - private fun onLocationChange(location: BubbleBarLocation) { - showDropTarget(location) - listener?.onChange(location) - } - - private fun getExclusionRect(): RectF { - val rect = RectF(0f, 0f, getExclusionRectWidth(), getExclusionRectHeight()) - // Center it around the bottom center of the screen - val screenBottom = screenSizeProvider.invoke().y - rect.offsetTo(screenCenterX - rect.width() / 2, screenBottom - rect.height()) - return rect - } - - private fun showDropTarget(location: BubbleBarLocation) { - val targetView = getDropTargetView() ?: createDropTargetView().apply { alpha = 0f } - if (targetView.alpha > 0) { - targetView.animateOut { - updateLocation(location) - targetView.animateIn() - } - } else { - updateLocation(location) - targetView.animateIn() - } - } - - private fun View.animateIn() { - dropTargetAnimator?.cancel() - dropTargetAnimator = - ObjectAnimator.ofFloat(this, View.ALPHA, 1f) - .setDuration(DROP_TARGET_ALPHA_IN_DURATION) - .addEndAction { dropTargetAnimator = null } - dropTargetAnimator?.start() - } - - private fun View.animateOut(endAction: Runnable? = null) { - dropTargetAnimator?.cancel() - dropTargetAnimator = - ObjectAnimator.ofFloat(this, View.ALPHA, 0f) - .setDuration(DROP_TARGET_ALPHA_OUT_DURATION) - .addEndAction { - endAction?.run() - dropTargetAnimator = null - } - dropTargetAnimator?.start() - } - - private fun T.addEndAction(runnable: Runnable): T { - addListener( - object : AnimatorListenerAdapter() { - override fun onAnimationEnd(animation: Animator) { - runnable.run() - } - } - ) - return this - } - - /** Receive updates on location changes */ - interface LocationChangeListener { - /** - * Bubble bar has been dragged to a new [BubbleBarLocation]. And the drag is still in - * progress. - * - * Triggered when drag gesture passes the middle of the screen and before touch up. Can be - * triggered multiple times per gesture. - * - * @param location new location as a result of the ongoing drag operation - */ - fun onChange(location: BubbleBarLocation) {} - - /** - * Bubble bar has been released in the [BubbleBarLocation]. - * - * @param location final location of the bubble bar once drag is released - */ - fun onRelease(location: BubbleBarLocation) - } - - companion object { - @VisibleForTesting const val DROP_TARGET_ALPHA_IN_DURATION = 150L - @VisibleForTesting const val DROP_TARGET_ALPHA_OUT_DURATION = 100L - } -} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubbleBarLocation.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubbleBarLocation.aidl deleted file mode 100644 index 3c5beeb48806..000000000000 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubbleBarLocation.aidl +++ /dev/null @@ -1,19 +0,0 @@ -/* - * Copyright (C) 2024 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.wm.shell.common.bubbles; - -parcelable BubbleBarLocation; \ No newline at end of file diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubbleBarLocation.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubbleBarLocation.kt deleted file mode 100644 index f0bdfdef1073..000000000000 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubbleBarLocation.kt +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright (C) 2024 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.wm.shell.common.bubbles - -import android.os.Parcel -import android.os.Parcelable - -/** - * The location of the bubble bar. - */ -enum class BubbleBarLocation : Parcelable { - /** - * Place bubble bar at the default location for the chosen system language. - * If an RTL language is used, it is on the left. Otherwise on the right. - */ - DEFAULT, - /** Default bubble bar location is overridden. Place bubble bar on the left. */ - LEFT, - /** Default bubble bar location is overridden. Place bubble bar on the right. */ - RIGHT; - - /** - * Returns whether bubble bar is pinned to the left edge or right edge. - */ - fun isOnLeft(isRtl: Boolean): Boolean { - if (this == DEFAULT) { - return isRtl - } - return this == LEFT - } - - override fun describeContents(): Int { - return 0 - } - - override fun writeToParcel(dest: Parcel, flags: Int) { - dest.writeString(name) - } - - companion object { - @JvmField - val CREATOR = object : Parcelable.Creator { - override fun createFromParcel(parcel: Parcel): BubbleBarLocation { - return parcel.readString()?.let { valueOf(it) } ?: DEFAULT - } - - override fun newArray(size: Int) = arrayOfNulls(size) - } - } -} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubbleBarUpdate.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubbleBarUpdate.java deleted file mode 100644 index ec3c6013e544..000000000000 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubbleBarUpdate.java +++ /dev/null @@ -1,186 +0,0 @@ -/* - * Copyright (C) 2023 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.wm.shell.common.bubbles; - -import android.annotation.NonNull; -import android.annotation.Nullable; -import android.graphics.Point; -import android.os.Parcel; -import android.os.Parcelable; - -import java.util.ArrayList; -import java.util.List; - -/** - * Represents an update to bubbles state. This is passed through - * {@link com.android.wm.shell.bubbles.IBubblesListener} to launcher so that taskbar may render - * bubbles. This should be kept this as minimal as possible in terms of data. - */ -public class BubbleBarUpdate implements Parcelable { - - public static final String BUNDLE_KEY = "update"; - - public final boolean initialState; - public boolean expandedChanged; - public boolean expanded; - public boolean shouldShowEducation; - @Nullable - public String selectedBubbleKey; - @Nullable - public BubbleInfo addedBubble; - @Nullable - public BubbleInfo updatedBubble; - @Nullable - public String suppressedBubbleKey; - @Nullable - public String unsupressedBubbleKey; - @Nullable - public BubbleBarLocation bubbleBarLocation; - @Nullable - public Point expandedViewDropTargetSize; - public boolean showOverflowChanged; - public boolean showOverflow; - - // This is only populated if bubbles have been removed. - public List removedBubbles = new ArrayList<>(); - - // This is only populated if the order of the bubbles has changed. - public List bubbleKeysInOrder = new ArrayList<>(); - - // This is only populated the first time a listener is connected so it gets the current state. - public List currentBubbleList = new ArrayList<>(); - - - public BubbleBarUpdate() { - this(false); - } - - private BubbleBarUpdate(boolean initialState) { - this.initialState = initialState; - } - - public BubbleBarUpdate(Parcel parcel) { - initialState = parcel.readBoolean(); - expandedChanged = parcel.readBoolean(); - expanded = parcel.readBoolean(); - shouldShowEducation = parcel.readBoolean(); - selectedBubbleKey = parcel.readString(); - addedBubble = parcel.readParcelable(BubbleInfo.class.getClassLoader(), - BubbleInfo.class); - updatedBubble = parcel.readParcelable(BubbleInfo.class.getClassLoader(), - BubbleInfo.class); - suppressedBubbleKey = parcel.readString(); - unsupressedBubbleKey = parcel.readString(); - removedBubbles = parcel.readParcelableList(new ArrayList<>(), - RemovedBubble.class.getClassLoader(), RemovedBubble.class); - parcel.readStringList(bubbleKeysInOrder); - currentBubbleList = parcel.readParcelableList(new ArrayList<>(), - BubbleInfo.class.getClassLoader(), BubbleInfo.class); - bubbleBarLocation = parcel.readParcelable(BubbleBarLocation.class.getClassLoader(), - BubbleBarLocation.class); - expandedViewDropTargetSize = parcel.readParcelable(Point.class.getClassLoader(), - Point.class); - showOverflowChanged = parcel.readBoolean(); - showOverflow = parcel.readBoolean(); - } - - /** - * Returns whether anything has changed in this update. - */ - public boolean anythingChanged() { - return expandedChanged - || selectedBubbleKey != null - || addedBubble != null - || updatedBubble != null - || !removedBubbles.isEmpty() - || !bubbleKeysInOrder.isEmpty() - || suppressedBubbleKey != null - || unsupressedBubbleKey != null - || !currentBubbleList.isEmpty() - || bubbleBarLocation != null - || showOverflowChanged; - } - - @NonNull - @Override - public String toString() { - return "BubbleBarUpdate{" - + " initialState=" + initialState - + " expandedChanged=" + expandedChanged - + " expanded=" + expanded - + " selectedBubbleKey=" + selectedBubbleKey - + " shouldShowEducation=" + shouldShowEducation - + " addedBubble=" + addedBubble - + " updatedBubble=" + updatedBubble - + " suppressedBubbleKey=" + suppressedBubbleKey - + " unsuppressedBubbleKey=" + unsupressedBubbleKey - + " removedBubbles=" + removedBubbles - + " bubbles=" + bubbleKeysInOrder - + " currentBubbleList=" + currentBubbleList - + " bubbleBarLocation=" + bubbleBarLocation - + " expandedViewDropTargetSize=" + expandedViewDropTargetSize - + " showOverflowChanged=" + showOverflowChanged - + " showOverflow=" + showOverflow - + " }"; - } - - @Override - public int describeContents() { - return 0; - } - - @Override - public void writeToParcel(Parcel parcel, int flags) { - parcel.writeBoolean(initialState); - parcel.writeBoolean(expandedChanged); - parcel.writeBoolean(expanded); - parcel.writeBoolean(shouldShowEducation); - parcel.writeString(selectedBubbleKey); - parcel.writeParcelable(addedBubble, flags); - parcel.writeParcelable(updatedBubble, flags); - parcel.writeString(suppressedBubbleKey); - parcel.writeString(unsupressedBubbleKey); - parcel.writeParcelableList(removedBubbles, flags); - parcel.writeStringList(bubbleKeysInOrder); - parcel.writeParcelableList(currentBubbleList, flags); - parcel.writeParcelable(bubbleBarLocation, flags); - parcel.writeParcelable(expandedViewDropTargetSize, flags); - parcel.writeBoolean(showOverflowChanged); - parcel.writeBoolean(showOverflow); - } - - /** - * Create update for initial set of values. - *

- * Used when bubble bar is newly created. - */ - public static BubbleBarUpdate createInitialState() { - return new BubbleBarUpdate(true); - } - - @NonNull - public static final Creator CREATOR = - new Creator<>() { - public BubbleBarUpdate createFromParcel(Parcel source) { - return new BubbleBarUpdate(source); - } - - public BubbleBarUpdate[] newArray(int size) { - return new BubbleBarUpdate[size]; - } - }; -} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubbleConstants.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubbleConstants.java deleted file mode 100644 index 0329b8df7544..000000000000 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubbleConstants.java +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright (C) 2023 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.wm.shell.common.bubbles; - -/** - * Constants shared between bubbles in shell & things we have to do for bubbles in launcher. - */ -public class BubbleConstants { - - /** The alpha for the scrim shown when bubbles are expanded. */ - public static float BUBBLE_EXPANDED_SCRIM_ALPHA = .32f; -} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubbleInfo.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubbleInfo.java deleted file mode 100644 index e873cbd6341d..000000000000 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubbleInfo.java +++ /dev/null @@ -1,196 +0,0 @@ -/* - * Copyright (C) 2023 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.wm.shell.common.bubbles; - -import android.annotation.NonNull; -import android.annotation.Nullable; -import android.app.Notification; -import android.graphics.drawable.Icon; -import android.os.Parcel; -import android.os.Parcelable; - -import java.util.Objects; - -/** - * Contains information necessary to present a bubble. - */ -public class BubbleInfo implements Parcelable { - - private String mKey; // Same key as the Notification - private int mFlags; // Flags from BubbleMetadata - @Nullable - private String mShortcutId; - private int mUserId; - private String mPackageName; - /** - * All notification bubbles require a shortcut to be set on the notification, however, the - * app could still specify an Icon and PendingIntent to use for the bubble. In that case - * this icon will be populated. If the bubble is entirely shortcut based, this will be null. - */ - @Nullable - private Icon mIcon; - @Nullable - private String mTitle; - @Nullable - private String mAppName; - private boolean mIsImportantConversation; - private boolean mShowAppBadge; - - public BubbleInfo(String key, int flags, @Nullable String shortcutId, @Nullable Icon icon, - int userId, String packageName, @Nullable String title, @Nullable String appName, - boolean isImportantConversation, boolean showAppBadge) { - mKey = key; - mFlags = flags; - mShortcutId = shortcutId; - mIcon = icon; - mUserId = userId; - mPackageName = packageName; - mTitle = title; - mAppName = appName; - mIsImportantConversation = isImportantConversation; - mShowAppBadge = showAppBadge; - } - - private BubbleInfo(Parcel source) { - mKey = source.readString(); - mFlags = source.readInt(); - mShortcutId = source.readString(); - mIcon = source.readTypedObject(Icon.CREATOR); - mUserId = source.readInt(); - mPackageName = source.readString(); - mTitle = source.readString(); - mAppName = source.readString(); - mIsImportantConversation = source.readBoolean(); - mShowAppBadge = source.readBoolean(); - } - - public String getKey() { - return mKey; - } - - @Nullable - public String getShortcutId() { - return mShortcutId; - } - - @Nullable - public Icon getIcon() { - return mIcon; - } - - public int getFlags() { - return mFlags; - } - - public int getUserId() { - return mUserId; - } - - public String getPackageName() { - return mPackageName; - } - - @Nullable - public String getTitle() { - return mTitle; - } - - @Nullable - public String getAppName() { - return mAppName; - } - - public boolean isImportantConversation() { - return mIsImportantConversation; - } - - public boolean showAppBadge() { - return mShowAppBadge; - } - - /** - * Whether this bubble is currently being hidden from the stack. - */ - public boolean isBubbleSuppressed() { - return (mFlags & Notification.BubbleMetadata.FLAG_SUPPRESS_BUBBLE) != 0; - } - - /** - * Whether this bubble is able to be suppressed (i.e. has the developer opted into the API - * to - * hide the bubble when in the same content). - */ - public boolean isBubbleSuppressable() { - return (mFlags & Notification.BubbleMetadata.FLAG_SUPPRESSABLE_BUBBLE) != 0; - } - - /** - * Whether the notification for this bubble is hidden from the shade. - */ - public boolean isNotificationSuppressed() { - return (mFlags & Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION) != 0; - } - - /** Sets the flags for this bubble. */ - public void setFlags(int flags) { - mFlags = flags; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (!(o instanceof BubbleInfo)) return false; - BubbleInfo bubble = (BubbleInfo) o; - return Objects.equals(mKey, bubble.mKey); - } - - @Override - public int hashCode() { - return mKey.hashCode(); - } - - @Override - public int describeContents() { - return 0; - } - - @Override - public void writeToParcel(Parcel parcel, int flags) { - parcel.writeString(mKey); - parcel.writeInt(mFlags); - parcel.writeString(mShortcutId); - parcel.writeTypedObject(mIcon, flags); - parcel.writeInt(mUserId); - parcel.writeString(mPackageName); - parcel.writeString(mTitle); - parcel.writeString(mAppName); - parcel.writeBoolean(mIsImportantConversation); - parcel.writeBoolean(mShowAppBadge); - } - - @NonNull - public static final Creator CREATOR = - new Creator<>() { - public BubbleInfo createFromParcel(Parcel source) { - return new BubbleInfo(source); - } - - public BubbleInfo[] newArray(int size) { - return new BubbleInfo[size]; - } - }; -} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubblePopupDrawable.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubblePopupDrawable.kt deleted file mode 100644 index 887af17c9653..000000000000 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubblePopupDrawable.kt +++ /dev/null @@ -1,232 +0,0 @@ -/* - * Copyright (C) 2023 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.wm.shell.common.bubbles - -import android.annotation.ColorInt -import android.graphics.Canvas -import android.graphics.ColorFilter -import android.graphics.Matrix -import android.graphics.Outline -import android.graphics.Paint -import android.graphics.Path -import android.graphics.Rect -import android.graphics.RectF -import android.graphics.drawable.Drawable -import kotlin.math.atan -import kotlin.math.cos -import kotlin.math.sin -import kotlin.properties.Delegates - -/** A drawable for the [BubblePopupView] that draws a popup background with a directional arrow */ -class BubblePopupDrawable(val config: Config) : Drawable() { - /** The direction of the arrow in the popup drawable */ - enum class ArrowDirection { - UP, - DOWN - } - - /** The arrow position on the side of the popup bubble */ - sealed class ArrowPosition { - object Start : ArrowPosition() - object Center : ArrowPosition() - object End : ArrowPosition() - class Custom(val value: Float) : ArrowPosition() - } - - /** The configuration for drawable features */ - data class Config( - @ColorInt val color: Int, - val cornerRadius: Float, - val contentPadding: Int, - val arrowWidth: Float, - val arrowHeight: Float, - val arrowRadius: Float - ) - - /** - * The direction of the arrow in the popup drawable. It affects the content padding and requires - * it to be updated in the view. - */ - var arrowDirection: ArrowDirection by - Delegates.observable(ArrowDirection.UP) { _, _, _ -> requestPathUpdate() } - - /** - * Arrow position along the X axis and its direction. The position is adjusted to the content - * corner radius when applied so it doesn't go into rounded corner area - */ - var arrowPosition: ArrowPosition by - Delegates.observable(ArrowPosition.Center) { _, _, _ -> requestPathUpdate() } - - private val path = Path() - private val paint = Paint() - private var shouldUpdatePath = true - - init { - paint.color = config.color - paint.style = Paint.Style.FILL - paint.isAntiAlias = true - } - - override fun draw(canvas: Canvas) { - updatePathIfNeeded() - canvas.drawPath(path, paint) - } - - override fun onBoundsChange(bounds: Rect) { - requestPathUpdate() - } - - /** Should be applied to the view padding if arrow direction changes */ - override fun getPadding(padding: Rect): Boolean { - padding.set( - config.contentPadding, - config.contentPadding, - config.contentPadding, - config.contentPadding - ) - when (arrowDirection) { - ArrowDirection.UP -> padding.top += config.arrowHeight.toInt() - ArrowDirection.DOWN -> padding.bottom += config.arrowHeight.toInt() - } - return true - } - - override fun getOutline(outline: Outline) { - updatePathIfNeeded() - outline.setPath(path) - } - - override fun getOpacity(): Int { - return paint.alpha - } - - override fun setAlpha(alpha: Int) { - paint.alpha = alpha - } - - override fun setColorFilter(colorFilter: ColorFilter?) { - paint.colorFilter = colorFilter - } - - /** Schedules path update for the next redraw */ - private fun requestPathUpdate() { - shouldUpdatePath = true - } - - /** Updates the path if required, when bounds or arrow direction/position changes */ - private fun updatePathIfNeeded() { - if (shouldUpdatePath) { - updatePath() - shouldUpdatePath = false - } - } - - /** Updates the path value using the current bounds, config, arrow direction and position */ - private fun updatePath() { - if (bounds.isEmpty) return - // Reset the path state - path.reset() - // The content rect where the filled rounded rect will be drawn - val contentRect = RectF(bounds) - when (arrowDirection) { - ArrowDirection.UP -> { - // Add rounded arrow pointing up to the path - addRoundedArrowPositioned(path, arrowPosition) - // Inset content rect by the arrow size from the top - contentRect.top += config.arrowHeight - } - ArrowDirection.DOWN -> { - val matrix = Matrix() - // Flip the path with the matrix to draw arrow pointing down - matrix.setScale(1f, -1f, bounds.width() / 2f, bounds.height() / 2f) - path.transform(matrix) - // Add rounded arrow with the flipped matrix applied, will point down - addRoundedArrowPositioned(path, arrowPosition) - // Restore the path matrix to the original state with inverted matrix - matrix.invert(matrix) - path.transform(matrix) - // Inset content rect by the arrow size from the bottom - contentRect.bottom -= config.arrowHeight - } - } - // Add the content area rounded rect - path.addRoundRect(contentRect, config.cornerRadius, config.cornerRadius, Path.Direction.CW) - } - - /** Add a rounded arrow pointing up in the horizontal position on the canvas */ - private fun addRoundedArrowPositioned(path: Path, position: ArrowPosition) { - val matrix = Matrix() - var translationX = positionValue(position) - config.arrowWidth / 2 - // Offset to position between rounded corners of the content view - translationX = translationX.coerceIn(config.cornerRadius, - bounds.width() - config.cornerRadius - config.arrowWidth) - // Translate to add the arrow in the center horizontally - matrix.setTranslate(-translationX, 0f) - path.transform(matrix) - // Add rounded arrow - addRoundedArrow(path) - // Restore the path matrix to the original state with inverted matrix - matrix.invert(matrix) - path.transform(matrix) - } - - /** Adds a rounded arrow pointing up to the path, can be flipped if needed */ - private fun addRoundedArrow(path: Path) { - // Theta is half of the angle inside the triangle tip - val thetaTan = config.arrowWidth / (config.arrowHeight * 2f) - val theta = atan(thetaTan) - val thetaDeg = Math.toDegrees(theta.toDouble()).toFloat() - // The center Y value of the circle for the triangle tip - val tipCircleCenterY = config.arrowRadius / sin(theta) - // The length from triangle tip to intersection point with the circle - val tipIntersectionSideLength = config.arrowRadius / thetaTan - // The offset from the top to the point of intersection - val intersectionTopOffset = tipIntersectionSideLength * cos(theta) - // The offset from the center to the point of intersection - val intersectionCenterOffset = tipIntersectionSideLength * sin(theta) - // The center X of the triangle - val arrowCenterX = config.arrowWidth / 2f - - // Set initial position in bottom left of the arrow - path.moveTo(0f, config.arrowHeight) - // Add the left side of the triangle - path.lineTo(arrowCenterX - intersectionCenterOffset, intersectionTopOffset) - // Add the arc from the left to the right side of the triangle - path.arcTo( - /* left = */ arrowCenterX - config.arrowRadius, - /* top = */ tipCircleCenterY - config.arrowRadius, - /* right = */ arrowCenterX + config.arrowRadius, - /* bottom = */ tipCircleCenterY + config.arrowRadius, - /* startAngle = */ 180 + thetaDeg, - /* sweepAngle = */ 180 - (2 * thetaDeg), - /* forceMoveTo = */ false - ) - // Add the right side of the triangle - path.lineTo(config.arrowWidth, config.arrowHeight) - // Close the path - path.close() - } - - /** The value of the arrow position provided the position and current bounds */ - private fun positionValue(position: ArrowPosition): Float { - return when (position) { - is ArrowPosition.Start -> 0f - is ArrowPosition.Center -> bounds.width().toFloat() / 2f - is ArrowPosition.End -> bounds.width().toFloat() - is ArrowPosition.Custom -> position.value - } - } -} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubblePopupView.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubblePopupView.kt deleted file mode 100644 index 444fbf7884be..000000000000 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubblePopupView.kt +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright (C) 2023 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.wm.shell.common.bubbles - -import android.content.Context -import android.graphics.Rect -import android.util.AttributeSet -import android.widget.LinearLayout - -/** A popup container view that uses [BubblePopupDrawable] as a background */ -open class BubblePopupView -@JvmOverloads -constructor( - context: Context, - attrs: AttributeSet? = null, - defStyleAttr: Int = 0, - defStyleRes: Int = 0 -) : LinearLayout(context, attrs, defStyleAttr, defStyleRes) { - var popupDrawable: BubblePopupDrawable? = null - private set - - /** - * Sets up the popup drawable with the config provided. Required to remove dependency on local - * resources - */ - fun setupBackground(config: BubblePopupDrawable.Config) { - popupDrawable = BubblePopupDrawable(config) - background = popupDrawable - forceLayout() - } - - /** - * Sets the arrow direction for the background drawable and updates the padding to fit the - * content inside of the popup drawable - */ - fun setArrowDirection(direction: BubblePopupDrawable.ArrowDirection) { - popupDrawable?.let { - it.arrowDirection = direction - val padding = Rect() - if (it.getPadding(padding)) { - setPadding(padding.left, padding.top, padding.right, padding.bottom) - } - } - } - - /** Sets the arrow position for the background drawable and triggers redraw */ - fun setArrowPosition(position: BubblePopupDrawable.ArrowPosition) { - popupDrawable?.let { - it.arrowPosition = position - invalidate() - } - } -} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/DismissCircleView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/DismissCircleView.java deleted file mode 100644 index 7c5bb211a4cc..000000000000 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/DismissCircleView.java +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright (C) 2020 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.wm.shell.common.bubbles; - -import android.content.Context; -import android.content.res.Configuration; -import android.view.Gravity; -import android.widget.FrameLayout; -import android.widget.ImageView; - -import androidx.annotation.DimenRes; -import androidx.annotation.DrawableRes; -import androidx.core.content.ContextCompat; - -/** - * Circular view with a semitransparent, circular background with an 'X' inside it. - * - * This is used by both Bubbles and PIP as the dismiss target. - */ -public class DismissCircleView extends FrameLayout { - @DrawableRes int mBackgroundResId; - @DimenRes int mIconSizeResId; - - private final ImageView mIconView = new ImageView(getContext()); - - public DismissCircleView(Context context) { - super(context); - addView(mIconView); - } - - @Override - protected void onConfigurationChanged(Configuration newConfig) { - super.onConfigurationChanged(newConfig); - setBackground(ContextCompat.getDrawable(getContext(), mBackgroundResId)); - setViewSizes(); - } - - /** - * Sets up view with the provided resource ids. - * Decouples resource dependency in order to be used externally (e.g. Launcher) - * - * @param backgroundResId drawable resource id of the circle background - * @param iconResId drawable resource id of the icon for the dismiss view - * @param iconSizeResId dimen resource id of the icon size - */ - public void setup(@DrawableRes int backgroundResId, @DrawableRes int iconResId, - @DimenRes int iconSizeResId) { - mBackgroundResId = backgroundResId; - mIconSizeResId = iconSizeResId; - - setBackground(ContextCompat.getDrawable(getContext(), backgroundResId)); - mIconView.setImageDrawable(ContextCompat.getDrawable(getContext(), iconResId)); - setViewSizes(); - } - - /** Retrieves the current dimensions for the icon and circle and applies them. */ - private void setViewSizes() { - final int iconSize = getResources().getDimensionPixelSize(mIconSizeResId); - mIconView.setLayoutParams( - new FrameLayout.LayoutParams(iconSize, iconSize, Gravity.CENTER)); - } -} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/DismissView.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/DismissView.kt deleted file mode 100644 index e06de9e9353c..000000000000 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/DismissView.kt +++ /dev/null @@ -1,229 +0,0 @@ -/* - * Copyright (C) 2020 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.wm.shell.common.bubbles - -import android.animation.ObjectAnimator -import android.content.Context -import android.graphics.Color -import android.graphics.drawable.GradientDrawable -import android.util.IntProperty -import android.util.Log -import android.view.Gravity -import android.view.View -import android.view.ViewGroup -import android.view.WindowInsets -import android.view.WindowManager -import android.widget.FrameLayout -import androidx.annotation.ColorRes -import androidx.annotation.DimenRes -import androidx.annotation.DrawableRes -import androidx.core.content.ContextCompat -import androidx.dynamicanimation.animation.DynamicAnimation -import androidx.dynamicanimation.animation.SpringForce.DAMPING_RATIO_LOW_BOUNCY -import androidx.dynamicanimation.animation.SpringForce.STIFFNESS_LOW -import com.android.wm.shell.shared.animation.PhysicsAnimator - -/** - * View that handles interactions between DismissCircleView and BubbleStackView. - * - * @note [setup] method should be called after initialisation - */ -class DismissView(context: Context) : FrameLayout(context) { - /** - * The configuration is used to provide module specific resource ids - * - * @see [setup] method - */ - data class Config( - /** The resource id to set on the dismiss target circle view */ - val dismissViewResId: Int, - /** dimen resource id of the dismiss target circle view size */ - @DimenRes val targetSizeResId: Int, - /** dimen resource id of the icon size in the dismiss target */ - @DimenRes val iconSizeResId: Int, - /** dimen resource id of the bottom margin for the dismiss target */ - @DimenRes var bottomMarginResId: Int, - /** dimen resource id of the height for dismiss area gradient */ - @DimenRes val floatingGradientHeightResId: Int, - /** color resource id of the dismiss area gradient color */ - @ColorRes val floatingGradientColorResId: Int, - /** drawable resource id of the dismiss target background */ - @DrawableRes val backgroundResId: Int, - /** drawable resource id of the icon for the dismiss target */ - @DrawableRes val iconResId: Int - ) - - companion object { - private const val SHOULD_SETUP = - "The view isn't ready. Should be called after `setup`" - private val TAG = DismissView::class.simpleName - } - - var circle = DismissCircleView(context) - var isShowing = false - var config: Config? = null - - private val animator = PhysicsAnimator.getInstance(circle) - private val spring = PhysicsAnimator.SpringConfig(STIFFNESS_LOW, DAMPING_RATIO_LOW_BOUNCY) - private val DISMISS_SCRIM_FADE_MS = 200L - private var wm: WindowManager = - context.getSystemService(Context.WINDOW_SERVICE) as WindowManager - private var gradientDrawable: GradientDrawable? = null - - private val GRADIENT_ALPHA: IntProperty = - object : IntProperty("alpha") { - override fun setValue(d: GradientDrawable, percent: Int) { - d.alpha = percent - } - override fun get(d: GradientDrawable): Int { - return d.alpha - } - } - - init { - setClipToPadding(false) - setClipChildren(false) - setVisibility(View.INVISIBLE) - addView(circle) - } - - /** - * Sets up view with the provided resource ids. - * - * Decouples resource dependency in order to be used externally (e.g. Launcher). Usually called - * with default params in module specific extension: - * @see [DismissView.setup] in DismissViewExt.kt - */ - fun setup(config: Config) { - this.config = config - - // Setup layout - layoutParams = LayoutParams( - ViewGroup.LayoutParams.MATCH_PARENT, - resources.getDimensionPixelSize(config.floatingGradientHeightResId), - Gravity.BOTTOM) - updatePadding() - - // Setup gradient - gradientDrawable = createGradient(color = config.floatingGradientColorResId) - setBackgroundDrawable(gradientDrawable) - - // Setup DismissCircleView - circle.id = config.dismissViewResId - circle.setup(config.backgroundResId, config.iconResId, config.iconSizeResId) - val targetSize: Int = resources.getDimensionPixelSize(config.targetSizeResId) - circle.layoutParams = LayoutParams(targetSize, targetSize, - Gravity.BOTTOM or Gravity.CENTER_HORIZONTAL) - // Initial position with circle offscreen so it's animated up - circle.translationY = resources.getDimensionPixelSize(config.floatingGradientHeightResId) - .toFloat() - } - - /** - * Animates this view in. - */ - fun show() { - if (isShowing) return - val gradientDrawable = checkExists(gradientDrawable) ?: return - isShowing = true - setVisibility(View.VISIBLE) - val alphaAnim = ObjectAnimator.ofInt(gradientDrawable, GRADIENT_ALPHA, - gradientDrawable.alpha, 255) - alphaAnim.setDuration(DISMISS_SCRIM_FADE_MS) - alphaAnim.start() - - animator.cancel() - animator - .spring(DynamicAnimation.TRANSLATION_Y, 0f, spring) - .start() - } - - /** - * Animates this view out, as well as the circle that encircles the bubbles, if they - * were dragged into the target and encircled. - */ - fun hide() { - if (!isShowing) return - val gradientDrawable = checkExists(gradientDrawable) ?: return - isShowing = false - val alphaAnim = ObjectAnimator.ofInt(gradientDrawable, GRADIENT_ALPHA, - gradientDrawable.alpha, 0) - alphaAnim.setDuration(DISMISS_SCRIM_FADE_MS) - alphaAnim.start() - animator - .spring(DynamicAnimation.TRANSLATION_Y, height.toFloat(), - spring) - .withEndActions({ - visibility = View.INVISIBLE - circle.scaleX = 1f - circle.scaleY = 1f - }) - .start() - } - - /** - * Cancels the animator for the dismiss target. - */ - fun cancelAnimators() { - animator.cancel() - } - - fun updateResources() { - val config = checkExists(config) ?: return - updatePadding() - layoutParams.height = resources.getDimensionPixelSize(config.floatingGradientHeightResId) - val targetSize = resources.getDimensionPixelSize(config.targetSizeResId) - circle.layoutParams.width = targetSize - circle.layoutParams.height = targetSize - circle.requestLayout() - } - - private fun createGradient(@ColorRes color: Int): GradientDrawable { - val gradientColor = ContextCompat.getColor(context, color) - val alpha = 0.7f * 255 - val gradientColorWithAlpha = Color.argb(alpha.toInt(), - Color.red(gradientColor), - Color.green(gradientColor), - Color.blue(gradientColor)) - val gd = GradientDrawable( - GradientDrawable.Orientation.BOTTOM_TOP, - intArrayOf(gradientColorWithAlpha, Color.TRANSPARENT)) - gd.setDither(true) - gd.setAlpha(0) - return gd - } - - private fun updatePadding() { - val config = checkExists(config) ?: return - val insets: WindowInsets = wm.getCurrentWindowMetrics().getWindowInsets() - val navInset = insets.getInsetsIgnoringVisibility( - WindowInsets.Type.navigationBars()) - setPadding(0, 0, 0, navInset.bottom + - resources.getDimensionPixelSize(config.bottomMarginResId)) - } - - /** - * Checks if the value is set up and exists, if not logs an exception. - * Used for convenient logging in case `setup` wasn't called before - * - * @return value provided as argument - */ - private fun checkExists(value: T?): T? { - if (value == null) Log.e(TAG, SHOULD_SETUP) - return value - } -} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/OWNERS b/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/OWNERS deleted file mode 100644 index 08c70314973e..000000000000 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/OWNERS +++ /dev/null @@ -1,6 +0,0 @@ -# WM shell sub-module bubble owner -madym@google.com -atsjenk@google.com -liranb@google.com -sukeshram@google.com -mpodolian@google.com diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/RelativeTouchListener.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/RelativeTouchListener.kt deleted file mode 100644 index 4e55ba23407b..000000000000 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/RelativeTouchListener.kt +++ /dev/null @@ -1,180 +0,0 @@ -/* - * Copyright (C) 2020 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.wm.shell.common.bubbles - -import android.graphics.PointF -import android.view.MotionEvent -import android.view.VelocityTracker -import android.view.View -import android.view.ViewConfiguration -import kotlin.math.hypot - -/** - * Listener which receives [onDown], [onMove], and [onUp] events, with relevant information about - * the coordinates of the touch and the view relative to the initial ACTION_DOWN event and the - * view's initial position. - */ -abstract class RelativeTouchListener : View.OnTouchListener { - - /** - * Called when an ACTION_DOWN event is received for the given view. - * - * @return False if the object is not interested in MotionEvents at this time, or true if we - * should consume this event and subsequent events, and begin calling [onMove]. - */ - abstract fun onDown(v: View, ev: MotionEvent): Boolean - - /** - * Called when an ACTION_MOVE event is received for the given view. This signals that the view - * is being dragged. - * - * @param viewInitialX The view's translationX value when this touch gesture started. - * @param viewInitialY The view's translationY value when this touch gesture started. - * @param dx Horizontal distance covered since the initial ACTION_DOWN event, in pixels. - * @param dy Vertical distance covered since the initial ACTION_DOWN event, in pixels. - */ - abstract fun onMove( - v: View, - ev: MotionEvent, - viewInitialX: Float, - viewInitialY: Float, - dx: Float, - dy: Float - ) - - /** - * Called when an ACTION_UP event is received for the given view. This signals that a drag or - * fling gesture has completed. - * - * @param viewInitialX The view's translationX value when this touch gesture started. - * @param viewInitialY The view's translationY value when this touch gesture started. - * @param dx Horizontal distance covered, in pixels. - * @param dy Vertical distance covered, in pixels. - * @param velX The final horizontal velocity of the gesture, in pixels/second. - * @param velY The final vertical velocity of the gesture, in pixels/second. - */ - abstract fun onUp( - v: View, - ev: MotionEvent, - viewInitialX: Float, - viewInitialY: Float, - dx: Float, - dy: Float, - velX: Float, - velY: Float - ) - - open fun onCancel( - v: View, - ev: MotionEvent, - viewInitialX: Float, - viewInitialY: Float - ) {} - - /** The raw coordinates of the last ACTION_DOWN event. */ - private var touchDown: PointF? = null - - /** The coordinates of the view, at the time of the last ACTION_DOWN event. */ - private val viewPositionOnTouchDown = PointF() - - private val velocityTracker = VelocityTracker.obtain() - - private var touchSlop: Int = -1 - private var movedEnough = false - - private var performedLongClick = false - - override fun onTouch(v: View, ev: MotionEvent): Boolean { - addMovement(ev) - - val dx = touchDown?.let { ev.rawX - it.x } ?: 0f - val dy = touchDown?.let { ev.rawY - it.y } ?: 0f - - when (ev.action) { - MotionEvent.ACTION_DOWN -> { - if (!onDown(v, ev)) { - return false - } - - // Grab the touch slop, it might have changed if the config changed since the - // last gesture. - touchSlop = ViewConfiguration.get(v.context).scaledTouchSlop - - touchDown = PointF(ev.rawX, ev.rawY) - viewPositionOnTouchDown.set(v.translationX, v.translationY) - - performedLongClick = false - v.handler?.postDelayed({ - if (v.isLongClickable) { - performedLongClick = v.performLongClick() - } - }, ViewConfiguration.getLongPressTimeout().toLong()) - } - - MotionEvent.ACTION_MOVE -> { - if (touchDown == null) return false - if (!movedEnough && hypot(dx, dy) > touchSlop && !performedLongClick) { - movedEnough = true - v.handler?.removeCallbacksAndMessages(null) - } - - if (movedEnough) { - onMove(v, ev, viewPositionOnTouchDown.x, viewPositionOnTouchDown.y, dx, dy) - } - } - - MotionEvent.ACTION_UP -> { - if (touchDown == null) return false - if (movedEnough) { - velocityTracker.computeCurrentVelocity(1000 /* units */) - onUp(v, ev, viewPositionOnTouchDown.x, viewPositionOnTouchDown.y, dx, dy, - velocityTracker.xVelocity, velocityTracker.yVelocity) - } else if (!performedLongClick) { - v.performClick() - } else { - v.handler?.removeCallbacksAndMessages(null) - } - - velocityTracker.clear() - movedEnough = false - touchDown = null - } - - MotionEvent.ACTION_CANCEL -> { - if (touchDown == null) return false - v.handler?.removeCallbacksAndMessages(null) - velocityTracker.clear() - movedEnough = false - touchDown = null - onCancel(v, ev, viewPositionOnTouchDown.x, viewPositionOnTouchDown.y) - } - } - - return true - } - - /** - * Adds a movement to the velocity tracker using raw screen coordinates. - */ - private fun addMovement(event: MotionEvent) { - val deltaX = event.rawX - event.x - val deltaY = event.rawY - event.y - event.offsetLocation(deltaX, deltaY) - velocityTracker.addMovement(event) - event.offsetLocation(-deltaX, -deltaY) - } -} \ No newline at end of file diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/RemovedBubble.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/RemovedBubble.java deleted file mode 100644 index f90591b84b7e..000000000000 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/RemovedBubble.java +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright (C) 2023 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.wm.shell.common.bubbles; - -import android.annotation.NonNull; -import android.os.Parcel; -import android.os.Parcelable; - -/** - * Represents a removed bubble, defining the key and reason the bubble was removed. - */ -public class RemovedBubble implements Parcelable { - - private final String mKey; - private final int mRemovalReason; - - public RemovedBubble(String key, int removalReason) { - mKey = key; - mRemovalReason = removalReason; - } - - public RemovedBubble(Parcel parcel) { - mKey = parcel.readString(); - mRemovalReason = parcel.readInt(); - } - - public String getKey() { - return mKey; - } - - public int getRemovalReason() { - return mRemovalReason; - } - - @Override - public int describeContents() { - return 0; - } - - @Override - public void writeToParcel(Parcel dest, int flags) { - dest.writeString(mKey); - dest.writeInt(mRemovalReason); - } - - @NonNull - public static final Creator CREATOR = - new Creator() { - public RemovedBubble createFromParcel(Parcel source) { - return new RemovedBubble(source); - } - public RemovedBubble[] newArray(int size) { - return new RemovedBubble[size]; - } - }; -} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/changes.md b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/changes.md index 0acc7df98d1c..faa97ac4512f 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/changes.md +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/changes.md @@ -98,9 +98,8 @@ Don't: ### Exposing shared code for use in Launcher Launcher doesn't currently build against the Shell library, but needs to have access to some shared AIDL interfaces and constants. Currently, all AIDL files, and classes under the -`com.android.wm.shell.util` package are automatically built into the `SystemUISharedLib` that +`com.android.wm.shell.shared` package are automatically built into the `SystemUISharedLib` that Launcher uses. -If the new code doesn't fall into those categories, they can be added explicitly in the Shell's -[Android.bp](/libs/WindowManager/Shell/Android.bp) file under the -`wm_shell_util-sources` filegroup. \ No newline at end of file +If the new code doesn't fall into those categories, they should be moved to the Shell shared +package (`com.android.wm.shell.shared`) under the `WindowManager-Shell-shared` library. \ No newline at end of file diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipDismissTargetHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipDismissTargetHandler.java index 0d2b8e70422d..06d231144d81 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipDismissTargetHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipDismissTargetHandler.java @@ -35,9 +35,9 @@ import androidx.annotation.NonNull; import com.android.wm.shell.R; import com.android.wm.shell.bubbles.DismissViewUtils; import com.android.wm.shell.common.ShellExecutor; -import com.android.wm.shell.common.bubbles.DismissCircleView; -import com.android.wm.shell.common.bubbles.DismissView; import com.android.wm.shell.common.pip.PipUiEventLogger; +import com.android.wm.shell.shared.bubbles.DismissCircleView; +import com.android.wm.shell.shared.bubbles.DismissView; import com.android.wm.shell.shared.magnetictarget.MagnetizedObject; import kotlin.Unit; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipDismissTargetHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipDismissTargetHandler.java index e04178e6d58c..b3070f29c6e2 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipDismissTargetHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipDismissTargetHandler.java @@ -35,9 +35,9 @@ import androidx.annotation.NonNull; import com.android.wm.shell.R; import com.android.wm.shell.bubbles.DismissViewUtils; import com.android.wm.shell.common.ShellExecutor; -import com.android.wm.shell.common.bubbles.DismissCircleView; -import com.android.wm.shell.common.bubbles.DismissView; import com.android.wm.shell.common.pip.PipUiEventLogger; +import com.android.wm.shell.shared.bubbles.DismissCircleView; +import com.android.wm.shell.shared.bubbles.DismissView; import com.android.wm.shell.shared.magnetictarget.MagnetizedObject; import kotlin.Unit; diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java index 859602ec709f..6fa37885b724 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java @@ -50,8 +50,8 @@ import androidx.test.filters.SmallTest; import com.android.wm.shell.ShellTestCase; import com.android.wm.shell.bubbles.BubbleData.TimeSource; import com.android.wm.shell.common.ShellExecutor; -import com.android.wm.shell.common.bubbles.BubbleBarLocation; -import com.android.wm.shell.common.bubbles.BubbleBarUpdate; +import com.android.wm.shell.shared.bubbles.BubbleBarLocation; +import com.android.wm.shell.shared.bubbles.BubbleBarUpdate; import com.google.common.collect.ImmutableList; diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleTest.java index 50c4a1828026..dca5fc4c2fe0 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleTest.java @@ -43,7 +43,7 @@ import androidx.test.filters.SmallTest; import com.android.wm.shell.R; import com.android.wm.shell.ShellTestCase; import com.android.wm.shell.common.ShellExecutor; -import com.android.wm.shell.common.bubbles.BubbleInfo; +import com.android.wm.shell.shared.bubbles.BubbleInfo; import org.junit.Before; import org.junit.Test; diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/bubbles/BubbleBarLocationTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/bubbles/BubbleBarLocationTest.kt deleted file mode 100644 index 27e0b196f0be..000000000000 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/bubbles/BubbleBarLocationTest.kt +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright (C) 2024 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.wm.shell.common.bubbles - -import android.testing.AndroidTestingRunner -import androidx.test.filters.SmallTest -import com.android.wm.shell.ShellTestCase -import com.android.wm.shell.common.bubbles.BubbleBarLocation.DEFAULT -import com.android.wm.shell.common.bubbles.BubbleBarLocation.LEFT -import com.android.wm.shell.common.bubbles.BubbleBarLocation.RIGHT -import com.google.common.truth.Truth.assertThat -import org.junit.Test -import org.junit.runner.RunWith - -@SmallTest -@RunWith(AndroidTestingRunner::class) -class BubbleBarLocationTest : ShellTestCase() { - - @Test - fun isOnLeft_rtlEnabled_defaultsToLeft() { - assertThat(DEFAULT.isOnLeft(isRtl = true)).isTrue() - } - - @Test - fun isOnLeft_rtlDisabled_defaultsToRight() { - assertThat(DEFAULT.isOnLeft(isRtl = false)).isFalse() - } - - @Test - fun isOnLeft_left_trueForAllLanguageDirections() { - assertThat(LEFT.isOnLeft(isRtl = false)).isTrue() - assertThat(LEFT.isOnLeft(isRtl = true)).isTrue() - } - - @Test - fun isOnLeft_right_falseForAllLanguageDirections() { - assertThat(RIGHT.isOnLeft(isRtl = false)).isFalse() - assertThat(RIGHT.isOnLeft(isRtl = true)).isFalse() - } -} \ No newline at end of file diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/bubbles/BubbleInfoTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/bubbles/BubbleInfoTest.kt deleted file mode 100644 index 6695a1e56567..000000000000 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/bubbles/BubbleInfoTest.kt +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright (C) 2023 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.wm.shell.common.bubbles - -import android.os.Parcel -import android.os.Parcelable.PARCELABLE_WRITE_RETURN_VALUE -import android.testing.AndroidTestingRunner -import androidx.test.filters.SmallTest -import com.android.wm.shell.ShellTestCase -import com.google.common.truth.Truth.assertThat -import org.junit.Test -import org.junit.runner.RunWith - -@SmallTest -@RunWith(AndroidTestingRunner::class) -class BubbleInfoTest : ShellTestCase() { - - @Test - fun bubbleInfo() { - val bubbleInfo = - BubbleInfo( - "key", - 0, - "shortcut id", - null, - 6, - "com.some.package", - "title", - "Some app", - true, - true - ) - val parcel = Parcel.obtain() - bubbleInfo.writeToParcel(parcel, PARCELABLE_WRITE_RETURN_VALUE) - parcel.setDataPosition(0) - - val bubbleInfoFromParcel = BubbleInfo.CREATOR.createFromParcel(parcel) - - assertThat(bubbleInfo.key).isEqualTo(bubbleInfoFromParcel.key) - assertThat(bubbleInfo.flags).isEqualTo(bubbleInfoFromParcel.flags) - assertThat(bubbleInfo.shortcutId).isEqualTo(bubbleInfoFromParcel.shortcutId) - assertThat(bubbleInfo.icon).isEqualTo(bubbleInfoFromParcel.icon) - assertThat(bubbleInfo.userId).isEqualTo(bubbleInfoFromParcel.userId) - assertThat(bubbleInfo.packageName).isEqualTo(bubbleInfoFromParcel.packageName) - assertThat(bubbleInfo.title).isEqualTo(bubbleInfoFromParcel.title) - assertThat(bubbleInfo.appName).isEqualTo(bubbleInfoFromParcel.appName) - assertThat(bubbleInfo.isImportantConversation) - .isEqualTo(bubbleInfoFromParcel.isImportantConversation) - } -} diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/bubbles/BubbleBarLocationTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/bubbles/BubbleBarLocationTest.kt new file mode 100644 index 000000000000..b9bf95b16e70 --- /dev/null +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/bubbles/BubbleBarLocationTest.kt @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2024 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.wm.shell.shared.bubbles + +import android.testing.AndroidTestingRunner +import androidx.test.filters.SmallTest +import com.android.wm.shell.ShellTestCase +import com.android.wm.shell.shared.bubbles.BubbleBarLocation.DEFAULT +import com.android.wm.shell.shared.bubbles.BubbleBarLocation.LEFT +import com.android.wm.shell.shared.bubbles.BubbleBarLocation.RIGHT +import com.google.common.truth.Truth.assertThat +import org.junit.Test +import org.junit.runner.RunWith + +@SmallTest +@RunWith(AndroidTestingRunner::class) +class BubbleBarLocationTest : ShellTestCase() { + + @Test + fun isOnLeft_rtlEnabled_defaultsToLeft() { + assertThat(DEFAULT.isOnLeft(isRtl = true)).isTrue() + } + + @Test + fun isOnLeft_rtlDisabled_defaultsToRight() { + assertThat(DEFAULT.isOnLeft(isRtl = false)).isFalse() + } + + @Test + fun isOnLeft_left_trueForAllLanguageDirections() { + assertThat(LEFT.isOnLeft(isRtl = false)).isTrue() + assertThat(LEFT.isOnLeft(isRtl = true)).isTrue() + } + + @Test + fun isOnLeft_right_falseForAllLanguageDirections() { + assertThat(RIGHT.isOnLeft(isRtl = false)).isFalse() + assertThat(RIGHT.isOnLeft(isRtl = true)).isFalse() + } +} \ No newline at end of file diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/bubbles/BubbleInfoTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/bubbles/BubbleInfoTest.kt new file mode 100644 index 000000000000..641063c27076 --- /dev/null +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/bubbles/BubbleInfoTest.kt @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2023 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.wm.shell.shared.bubbles + +import android.os.Parcel +import android.os.Parcelable.PARCELABLE_WRITE_RETURN_VALUE +import android.testing.AndroidTestingRunner +import androidx.test.filters.SmallTest +import com.android.wm.shell.ShellTestCase +import com.google.common.truth.Truth.assertThat +import org.junit.Test +import org.junit.runner.RunWith + +@SmallTest +@RunWith(AndroidTestingRunner::class) +class BubbleInfoTest : ShellTestCase() { + + @Test + fun bubbleInfo() { + val bubbleInfo = + BubbleInfo( + "key", + 0, + "shortcut id", + null, + 6, + "com.some.package", + "title", + "Some app", + true, + true + ) + val parcel = Parcel.obtain() + bubbleInfo.writeToParcel(parcel, PARCELABLE_WRITE_RETURN_VALUE) + parcel.setDataPosition(0) + + val bubbleInfoFromParcel = BubbleInfo.CREATOR.createFromParcel(parcel) + + assertThat(bubbleInfo.key).isEqualTo(bubbleInfoFromParcel.key) + assertThat(bubbleInfo.flags).isEqualTo(bubbleInfoFromParcel.flags) + assertThat(bubbleInfo.shortcutId).isEqualTo(bubbleInfoFromParcel.shortcutId) + assertThat(bubbleInfo.icon).isEqualTo(bubbleInfoFromParcel.icon) + assertThat(bubbleInfo.userId).isEqualTo(bubbleInfoFromParcel.userId) + assertThat(bubbleInfo.packageName).isEqualTo(bubbleInfoFromParcel.packageName) + assertThat(bubbleInfo.title).isEqualTo(bubbleInfoFromParcel.title) + assertThat(bubbleInfo.appName).isEqualTo(bubbleInfoFromParcel.appName) + assertThat(bubbleInfo.isImportantConversation) + .isEqualTo(bubbleInfoFromParcel.isImportantConversation) + } +} -- cgit v1.2.3-59-g8ed1b