Merge "Add manage menu to bubble bar expanded view" into udc-qpr-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
2057b58789
@@ -0,0 +1,24 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
~ 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.
|
||||
-->
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:state_pressed="true">
|
||||
<ripple android:color="#99999999">
|
||||
<item android:drawable="@drawable/bubble_manage_menu_bg" />
|
||||
</ripple>
|
||||
</item>
|
||||
<item android:drawable="@drawable/bubble_manage_menu_bg" />
|
||||
</selector>
|
||||
25
libs/WindowManager/Shell/res/drawable/ic_expand_less.xml
Normal file
25
libs/WindowManager/Shell/res/drawable/ic_expand_less.xml
Normal file
@@ -0,0 +1,25 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
~ 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
|
||||
-->
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:pathData="M18.59,16.41L20,15L12,7L4,15L5.41,16.41L12,9.83"
|
||||
android:fillColor="#5F6368"/>
|
||||
</vector>
|
||||
41
libs/WindowManager/Shell/res/layout/bubble_bar_menu_item.xml
Normal file
41
libs/WindowManager/Shell/res/layout/bubble_bar_menu_item.xml
Normal file
@@ -0,0 +1,41 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
~ 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
|
||||
-->
|
||||
<com.android.wm.shell.bubbles.bar.BubbleBarMenuItemView
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:orientation="horizontal"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/bubble_bar_manage_menu_item_height"
|
||||
android:gravity="center_vertical"
|
||||
android:paddingStart="@dimen/bubble_menu_padding"
|
||||
android:paddingEnd="@dimen/bubble_menu_padding"
|
||||
android:background="@drawable/bubble_manage_menu_row">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/bubble_bar_menu_item_icon"
|
||||
android:layout_width="@dimen/bubble_bar_manage_menu_item_icon_size"
|
||||
android:layout_height="@dimen/bubble_bar_manage_menu_item_icon_size"
|
||||
android:contentDescription="@null"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/bubble_bar_menu_item_title"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:textColor="?android:attr/textColorPrimary"
|
||||
android:textAppearance="@*android:style/TextAppearance.DeviceDefault" />
|
||||
|
||||
</com.android.wm.shell.bubbles.bar.BubbleBarMenuItemView>
|
||||
77
libs/WindowManager/Shell/res/layout/bubble_bar_menu_view.xml
Normal file
77
libs/WindowManager/Shell/res/layout/bubble_bar_menu_view.xml
Normal file
@@ -0,0 +1,77 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
~ 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
|
||||
-->
|
||||
<com.android.wm.shell.bubbles.bar.BubbleBarMenuView
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:minWidth="@dimen/bubble_bar_manage_menu_min_width"
|
||||
android:orientation="vertical"
|
||||
android:elevation="@dimen/bubble_manage_menu_elevation"
|
||||
android:paddingTop="@dimen/bubble_bar_manage_menu_padding_top"
|
||||
android:paddingHorizontal="@dimen/bubble_bar_manage_menu_padding"
|
||||
android:paddingBottom="@dimen/bubble_bar_manage_menu_padding"
|
||||
android:clipToPadding="false">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/bubble_bar_manage_menu_bubble_section"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/bubble_bar_manage_menu_item_height"
|
||||
android:orientation="horizontal"
|
||||
android:gravity="center_vertical"
|
||||
android:paddingStart="14dp"
|
||||
android:paddingEnd="12dp"
|
||||
android:background="@drawable/bubble_manage_menu_section"
|
||||
android:elevation="@dimen/bubble_manage_menu_elevation">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/bubble_bar_manage_menu_bubble_icon"
|
||||
android:layout_width="@dimen/bubble_menu_icon_size"
|
||||
android:layout_height="@dimen/bubble_menu_icon_size"
|
||||
android:contentDescription="@null" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/bubble_bar_manage_menu_bubble_title"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="8dp"
|
||||
android:layout_weight="1"
|
||||
android:textColor="?android:attr/textColorPrimary"
|
||||
android:textAppearance="@*android:style/TextAppearance.DeviceDefault" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/bubble_bar_manage_menu_dismiss_icon"
|
||||
android:layout_width="@dimen/bubble_bar_manage_menu_dismiss_icon_size"
|
||||
android:layout_height="@dimen/bubble_bar_manage_menu_dismiss_icon_size"
|
||||
android:layout_marginStart="8dp"
|
||||
android:contentDescription="@null"
|
||||
android:src="@drawable/ic_expand_less"
|
||||
app:tint="?android:attr/textColorPrimary" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/bubble_bar_manage_menu_actions_section"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:layout_marginTop="@dimen/bubble_bar_manage_menu_section_spacing"
|
||||
android:background="@drawable/bubble_manage_menu_bg"
|
||||
android:elevation="@dimen/bubble_manage_menu_elevation" />
|
||||
|
||||
</com.android.wm.shell.bubbles.bar.BubbleBarMenuView>
|
||||
@@ -32,6 +32,7 @@
|
||||
<color name="bubbles_icon_tint">@color/GM2_grey_700</color>
|
||||
<color name="bubble_bar_expanded_view_handle_light">#EBffffff</color>
|
||||
<color name="bubble_bar_expanded_view_handle_dark">#99000000</color>
|
||||
<color name="bubble_bar_expanded_view_menu_close">#DC362E</color>
|
||||
|
||||
<!-- PiP -->
|
||||
<color name="pip_custom_close_bg">#D93025</color>
|
||||
|
||||
@@ -234,6 +234,20 @@
|
||||
<dimen name="bubble_bar_expanded_view_handle_width">128dp</dimen>
|
||||
<!-- The height of the drag handle shown along with a bubble bar expanded view. -->
|
||||
<dimen name="bubble_bar_expanded_view_handle_height">4dp</dimen>
|
||||
<!-- Minimum width of the bubble bar manage menu. -->
|
||||
<dimen name="bubble_bar_manage_menu_min_width">200dp</dimen>
|
||||
<!-- Size of the dismiss icon in the bubble bar manage menu. -->
|
||||
<dimen name="bubble_bar_manage_menu_dismiss_icon_size">16dp</dimen>
|
||||
<!-- Padding of the bubble bar manage menu, provides space for menu shadows -->
|
||||
<dimen name="bubble_bar_manage_menu_padding">8dp</dimen>
|
||||
<!-- Top padding of the bubble bar manage menu -->
|
||||
<dimen name="bubble_bar_manage_menu_padding_top">2dp</dimen>
|
||||
<!-- Spacing between sections of the bubble bar manage menu -->
|
||||
<dimen name="bubble_bar_manage_menu_section_spacing">2dp</dimen>
|
||||
<!-- Height of an item in the bubble bar manage menu. -->
|
||||
<dimen name="bubble_bar_manage_menu_item_height">52dp</dimen>
|
||||
<!-- Size of the icons in the bubble bar manage menu. -->
|
||||
<dimen name="bubble_bar_manage_menu_item_icon_size">20dp</dimen>
|
||||
|
||||
<!-- Bottom and end margin for compat buttons. -->
|
||||
<dimen name="compat_button_margin">24dp</dimen>
|
||||
|
||||
@@ -855,7 +855,8 @@ public class Bubble implements BubbleViewProvider {
|
||||
return mIsAppBubble;
|
||||
}
|
||||
|
||||
Intent getSettingsIntent(final Context context) {
|
||||
/** Creates open app settings intent */
|
||||
public Intent getSettingsIntent(final Context context) {
|
||||
final Intent intent = new Intent(Settings.ACTION_APP_NOTIFICATION_BUBBLE_SETTINGS);
|
||||
intent.putExtra(Settings.EXTRA_APP_PACKAGE, getPackageName());
|
||||
final int uid = getUid(context);
|
||||
|
||||
@@ -720,6 +720,7 @@ public class BubbleController implements ConfigurationChangeListener,
|
||||
// TODO(b/273312602): consider foldables where we do need a stack view when folded
|
||||
if (mLayerView == null) {
|
||||
mLayerView = new BubbleBarLayerView(mContext, this);
|
||||
mLayerView.setUnBubbleConversationCallback(mSysuiProxy::onUnbubbleConversation);
|
||||
}
|
||||
} else {
|
||||
if (mStackView == null) {
|
||||
@@ -1220,6 +1221,13 @@ public class BubbleController implements ConfigurationChangeListener,
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Dismiss bubble if it exists and remove it from the stack
|
||||
*/
|
||||
public void dismissBubble(Bubble bubble, @Bubbles.DismissReason int reason) {
|
||||
mBubbleData.dismissBubbleWithKey(bubble.getKey(), reason);
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs a screenshot that may exclude the bubble layer, if one is present. The screenshot
|
||||
* can be access via the supplied {@link ScreenshotSync#get()} asynchronously.
|
||||
|
||||
@@ -192,6 +192,11 @@ public class BubbleViewInfoTask extends AsyncTask<Void, Void, BubbleViewInfoTask
|
||||
}
|
||||
|
||||
info.rawBadgeBitmap = iconFactory.getBadgeBitmap(badgedIcon, false).icon;
|
||||
float[] bubbleBitmapScale = new float[1];
|
||||
info.bubbleBitmap = iconFactory.getBubbleBitmap(
|
||||
iconFactory.getBubbleDrawable(c, info.shortcutInfo,
|
||||
b.getIcon()), bubbleBitmapScale);
|
||||
|
||||
|
||||
return info;
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ import android.content.Context;
|
||||
import android.content.res.TypedArray;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Outline;
|
||||
import android.graphics.Rect;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
import android.view.ViewOutlineProvider;
|
||||
@@ -33,8 +34,12 @@ import com.android.wm.shell.R;
|
||||
import com.android.wm.shell.bubbles.Bubble;
|
||||
import com.android.wm.shell.bubbles.BubbleController;
|
||||
import com.android.wm.shell.bubbles.BubbleTaskViewHelper;
|
||||
import com.android.wm.shell.bubbles.Bubbles;
|
||||
import com.android.wm.shell.taskview.TaskView;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
* Expanded view of a bubble when it's part of the bubble bar.
|
||||
*
|
||||
@@ -47,6 +52,9 @@ public class BubbleBarExpandedView extends FrameLayout implements BubbleTaskView
|
||||
|
||||
private BubbleController mController;
|
||||
private BubbleTaskViewHelper mBubbleTaskViewHelper;
|
||||
private BubbleBarMenuViewController mMenuViewController;
|
||||
private @Nullable Supplier<Rect> mLayerBoundsSupplier;
|
||||
private @Nullable Consumer<String> mUnBubbleConversationCallback;
|
||||
|
||||
private BubbleBarHandleView mHandleView = new BubbleBarHandleView(getContext());
|
||||
private @Nullable TaskView mTaskView;
|
||||
@@ -98,6 +106,13 @@ public class BubbleBarExpandedView extends FrameLayout implements BubbleTaskView
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDetachedFromWindow() {
|
||||
super.onDetachedFromWindow();
|
||||
// Hide manage menu when view disappears
|
||||
mMenuViewController.hideMenu(false /* animated */);
|
||||
}
|
||||
|
||||
/** Set the BubbleController on the view, must be called before doing anything else. */
|
||||
public void initialize(BubbleController controller) {
|
||||
mController = controller;
|
||||
@@ -108,6 +123,36 @@ public class BubbleBarExpandedView extends FrameLayout implements BubbleTaskView
|
||||
addView(mTaskView);
|
||||
mTaskView.setEnableSurfaceClipping(true);
|
||||
mTaskView.setCornerRadius(mCornerRadius);
|
||||
mMenuViewController = new BubbleBarMenuViewController(mContext, this);
|
||||
mMenuViewController.setListener(new BubbleBarMenuViewController.Listener() {
|
||||
@Override
|
||||
public void onMenuVisibilityChanged(boolean visible) {
|
||||
if (mTaskView == null || mLayerBoundsSupplier == null) return;
|
||||
// Updates the obscured touchable region for the task surface.
|
||||
mTaskView.setObscuredTouchRect(visible ? mLayerBoundsSupplier.get() : null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUnBubbleConversation(Bubble bubble) {
|
||||
if (mUnBubbleConversationCallback != null) {
|
||||
mUnBubbleConversationCallback.accept(bubble.getKey());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onOpenAppSettings(Bubble bubble) {
|
||||
mController.collapseStack();
|
||||
mContext.startActivityAsUser(bubble.getSettingsIntent(mContext), bubble.getUser());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDismissBubble(Bubble bubble) {
|
||||
mController.dismissBubble(bubble, Bubbles.DISMISS_USER_REMOVED);
|
||||
}
|
||||
});
|
||||
mHandleView.setOnClickListener(view -> {
|
||||
mMenuViewController.showMenu(true /* animated */);
|
||||
});
|
||||
}
|
||||
|
||||
// TODO (b/275087636): call this when theme/config changes
|
||||
@@ -183,11 +228,13 @@ public class BubbleBarExpandedView extends FrameLayout implements BubbleTaskView
|
||||
}
|
||||
mBubbleTaskViewHelper.cleanUpTaskView();
|
||||
}
|
||||
mMenuViewController.hideMenu(false /* animated */);
|
||||
}
|
||||
|
||||
/** Updates the bubble shown in this task view. */
|
||||
/** Updates the bubble shown in the expanded view. */
|
||||
public void update(Bubble bubble) {
|
||||
mBubbleTaskViewHelper.update(bubble);
|
||||
mMenuViewController.updateMenu(bubble);
|
||||
}
|
||||
|
||||
/** The task id of the activity shown in the task view, if it exists. */
|
||||
@@ -195,6 +242,17 @@ public class BubbleBarExpandedView extends FrameLayout implements BubbleTaskView
|
||||
return mBubbleTaskViewHelper != null ? mBubbleTaskViewHelper.getTaskId() : INVALID_TASK_ID;
|
||||
}
|
||||
|
||||
/** Sets layer bounds supplier used for obscured touchable region of task view */
|
||||
void setLayerBoundsSupplier(@Nullable Supplier<Rect> supplier) {
|
||||
mLayerBoundsSupplier = supplier;
|
||||
}
|
||||
|
||||
/** Sets the function to call to un-bubble the given conversation. */
|
||||
public void setUnBubbleConversationCallback(
|
||||
@Nullable Consumer<String> unBubbleConversationCallback) {
|
||||
mUnBubbleConversationCallback = unBubbleConversationCallback;
|
||||
}
|
||||
|
||||
/**
|
||||
* Call when the location or size of the view has changed to update TaskView.
|
||||
*/
|
||||
|
||||
@@ -20,7 +20,6 @@ import android.animation.AnimatorListenerAdapter;
|
||||
import android.animation.ObjectAnimator;
|
||||
import android.annotation.Nullable;
|
||||
import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.Outline;
|
||||
import android.graphics.Rect;
|
||||
import android.util.AttributeSet;
|
||||
@@ -38,35 +37,35 @@ import com.android.wm.shell.R;
|
||||
public class BubbleBarHandleView extends View {
|
||||
private static final long COLOR_CHANGE_DURATION = 120;
|
||||
|
||||
private final int mHandleWidth;
|
||||
private final int mHandleHeight;
|
||||
private final @ColorInt int mHandleLightColor;
|
||||
private final @ColorInt int mHandleDarkColor;
|
||||
private int mHandleWidth;
|
||||
private int mHandleHeight;
|
||||
private @ColorInt int mHandleLightColor;
|
||||
private @ColorInt int mHandleDarkColor;
|
||||
private @Nullable ObjectAnimator mColorChangeAnim;
|
||||
|
||||
public BubbleBarHandleView(Context context) {
|
||||
this(context, null);
|
||||
this(context, null /* attrs */);
|
||||
}
|
||||
|
||||
public BubbleBarHandleView(Context context, AttributeSet attrs) {
|
||||
this(context, attrs, 0);
|
||||
this(context, attrs, 0 /* defStyleAttr */);
|
||||
}
|
||||
|
||||
public BubbleBarHandleView(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
this(context, attrs, defStyleAttr, 0);
|
||||
this(context, attrs, defStyleAttr, 0 /* defStyleRes */);
|
||||
}
|
||||
|
||||
public BubbleBarHandleView(Context context, AttributeSet attrs, int defStyleAttr,
|
||||
int defStyleRes) {
|
||||
super(context, attrs, defStyleAttr, defStyleRes);
|
||||
Resources resources = context.getResources();
|
||||
mHandleWidth = resources.getDimensionPixelSize(
|
||||
|
||||
mHandleWidth = getResources().getDimensionPixelSize(
|
||||
R.dimen.bubble_bar_expanded_view_handle_width);
|
||||
mHandleHeight = resources.getDimensionPixelSize(
|
||||
mHandleHeight = getResources().getDimensionPixelSize(
|
||||
R.dimen.bubble_bar_expanded_view_handle_height);
|
||||
mHandleLightColor = ContextCompat.getColor(context,
|
||||
mHandleLightColor = ContextCompat.getColor(getContext(),
|
||||
R.color.bubble_bar_expanded_view_handle_light);
|
||||
mHandleDarkColor = ContextCompat.getColor(context,
|
||||
mHandleDarkColor = ContextCompat.getColor(getContext(),
|
||||
R.color.bubble_bar_expanded_view_handle_dark);
|
||||
|
||||
setClipToOutline(true);
|
||||
|
||||
@@ -32,6 +32,8 @@ import com.android.wm.shell.bubbles.BubbleController;
|
||||
import com.android.wm.shell.bubbles.BubblePositioner;
|
||||
import com.android.wm.shell.bubbles.BubbleViewProvider;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
* Similar to {@link com.android.wm.shell.bubbles.BubbleStackView}, this view is added to window
|
||||
* manager to display bubbles. However, it is only used when bubbles are being displayed in
|
||||
@@ -53,6 +55,7 @@ public class BubbleBarLayerView extends FrameLayout
|
||||
@Nullable
|
||||
private BubbleViewProvider mExpandedBubble;
|
||||
private BubbleBarExpandedView mExpandedView;
|
||||
private @Nullable Consumer<String> mUnBubbleConversationCallback;
|
||||
|
||||
// TODO(b/273310265) - currently the view is always on the right, need to update for RTL.
|
||||
/** Whether the expanded view is displaying on the left of the screen or not. */
|
||||
@@ -146,6 +149,13 @@ public class BubbleBarLayerView extends FrameLayout
|
||||
final int width = mPositioner.getExpandedViewWidthForBubbleBar();
|
||||
final int height = mPositioner.getExpandedViewHeightForBubbleBar();
|
||||
mExpandedView.setVisibility(GONE);
|
||||
mExpandedView.setUnBubbleConversationCallback(mUnBubbleConversationCallback);
|
||||
mExpandedView.setLayerBoundsSupplier(() -> new Rect(0, 0, getWidth(), getHeight()));
|
||||
mExpandedView.setUnBubbleConversationCallback(bubbleKey -> {
|
||||
if (mUnBubbleConversationCallback != null) {
|
||||
mUnBubbleConversationCallback.accept(bubbleKey);
|
||||
}
|
||||
});
|
||||
addView(mExpandedView, new FrameLayout.LayoutParams(width, height));
|
||||
}
|
||||
|
||||
@@ -165,6 +175,12 @@ public class BubbleBarLayerView extends FrameLayout
|
||||
showScrim(false);
|
||||
}
|
||||
|
||||
/** Sets the function to call to un-bubble the given conversation. */
|
||||
public void setUnBubbleConversationCallback(
|
||||
@Nullable Consumer<String> unBubbleConversationCallback) {
|
||||
mUnBubbleConversationCallback = unBubbleConversationCallback;
|
||||
}
|
||||
|
||||
/** Updates the expanded view size and position. */
|
||||
private void updateExpandedView() {
|
||||
if (mExpandedView == null) return;
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
/*
|
||||
* 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.bubbles.bar;
|
||||
|
||||
import android.annotation.ColorInt;
|
||||
import android.content.Context;
|
||||
import android.content.res.TypedArray;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.drawable.Icon;
|
||||
import android.util.AttributeSet;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.android.wm.shell.R;
|
||||
|
||||
/**
|
||||
* Bubble bar expanded view menu item view to display menu action details
|
||||
*/
|
||||
public class BubbleBarMenuItemView extends LinearLayout {
|
||||
private ImageView mImageView;
|
||||
private TextView mTextView;
|
||||
|
||||
public BubbleBarMenuItemView(Context context) {
|
||||
this(context, null /* attrs */);
|
||||
}
|
||||
|
||||
public BubbleBarMenuItemView(Context context, AttributeSet attrs) {
|
||||
this(context, attrs, 0 /* defStyleAttr */);
|
||||
}
|
||||
|
||||
public BubbleBarMenuItemView(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
this(context, attrs, defStyleAttr, 0 /* defStyleRes */);
|
||||
}
|
||||
|
||||
public BubbleBarMenuItemView(Context context, AttributeSet attrs, int defStyleAttr,
|
||||
int defStyleRes) {
|
||||
super(context, attrs, defStyleAttr, defStyleRes);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onFinishInflate() {
|
||||
super.onFinishInflate();
|
||||
mImageView = findViewById(R.id.bubble_bar_menu_item_icon);
|
||||
mTextView = findViewById(R.id.bubble_bar_menu_item_title);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update menu item with the details and tint color
|
||||
*/
|
||||
void update(Icon icon, String title, @ColorInt int tint) {
|
||||
if (tint == Color.TRANSPARENT) {
|
||||
final TypedArray typedArray = getContext().obtainStyledAttributes(
|
||||
new int[]{android.R.attr.textColorPrimary});
|
||||
mTextView.setTextColor(typedArray.getColor(0, Color.BLACK));
|
||||
} else {
|
||||
icon.setTint(tint);
|
||||
mTextView.setTextColor(tint);
|
||||
}
|
||||
|
||||
mImageView.setImageIcon(icon);
|
||||
mTextView.setText(title);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,145 @@
|
||||
/*
|
||||
* 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.bubbles.bar;
|
||||
|
||||
import android.annotation.ColorInt;
|
||||
import android.content.Context;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.drawable.Icon;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.android.wm.shell.R;
|
||||
import com.android.wm.shell.bubbles.Bubble;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* Bubble bar expanded view menu
|
||||
*/
|
||||
public class BubbleBarMenuView extends LinearLayout {
|
||||
private ViewGroup mBubbleSectionView;
|
||||
private ViewGroup mActionsSectionView;
|
||||
private ImageView mBubbleIconView;
|
||||
private TextView mBubbleTitleView;
|
||||
|
||||
public BubbleBarMenuView(Context context) {
|
||||
this(context, null /* attrs */);
|
||||
}
|
||||
|
||||
public BubbleBarMenuView(Context context, AttributeSet attrs) {
|
||||
this(context, attrs, 0 /* defStyleAttr */);
|
||||
}
|
||||
|
||||
public BubbleBarMenuView(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
this(context, attrs, defStyleAttr, 0 /* defStyleRes */);
|
||||
}
|
||||
|
||||
public BubbleBarMenuView(Context context, AttributeSet attrs, int defStyleAttr,
|
||||
int defStyleRes) {
|
||||
super(context, attrs, defStyleAttr, defStyleRes);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onFinishInflate() {
|
||||
super.onFinishInflate();
|
||||
mBubbleSectionView = findViewById(R.id.bubble_bar_manage_menu_bubble_section);
|
||||
mActionsSectionView = findViewById(R.id.bubble_bar_manage_menu_actions_section);
|
||||
mBubbleIconView = findViewById(R.id.bubble_bar_manage_menu_bubble_icon);
|
||||
mBubbleTitleView = findViewById(R.id.bubble_bar_manage_menu_bubble_title);
|
||||
}
|
||||
|
||||
/** Update menu details with bubble info */
|
||||
void updateInfo(Bubble bubble) {
|
||||
if (bubble.getIcon() != null) {
|
||||
mBubbleIconView.setImageIcon(bubble.getIcon());
|
||||
} else {
|
||||
mBubbleIconView.setImageBitmap(bubble.getBubbleIcon());
|
||||
}
|
||||
mBubbleTitleView.setText(bubble.getTitle());
|
||||
}
|
||||
|
||||
/**
|
||||
* Update menu action items views
|
||||
* @param actions used to populate menu item views
|
||||
*/
|
||||
void updateActions(ArrayList<MenuAction> actions) {
|
||||
mActionsSectionView.removeAllViews();
|
||||
LayoutInflater inflater = LayoutInflater.from(mContext);
|
||||
|
||||
for (MenuAction action : actions) {
|
||||
BubbleBarMenuItemView itemView = (BubbleBarMenuItemView) inflater.inflate(
|
||||
R.layout.bubble_bar_menu_item, mActionsSectionView, false);
|
||||
itemView.update(action.mIcon, action.mTitle, action.mTint);
|
||||
itemView.setOnClickListener(action.mOnClick);
|
||||
mActionsSectionView.addView(itemView);
|
||||
}
|
||||
}
|
||||
|
||||
/** Sets on close menu listener */
|
||||
void setOnCloseListener(Runnable onClose) {
|
||||
mBubbleSectionView.setOnClickListener(view -> {
|
||||
onClose.run();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Overridden to proxy to section views alpha.
|
||||
* @implNote
|
||||
* If animate alpha on the parent (menu container) view, section view shadows get distorted.
|
||||
* To prevent distortion and artifacts alpha changes applied directly on the section views.
|
||||
*/
|
||||
@Override
|
||||
public void setAlpha(float alpha) {
|
||||
mBubbleSectionView.setAlpha(alpha);
|
||||
mActionsSectionView.setAlpha(alpha);
|
||||
}
|
||||
|
||||
/**
|
||||
* Overridden to proxy section view alpha value.
|
||||
* @implNote
|
||||
* The assumption is that both section views have the same alpha value
|
||||
*/
|
||||
@Override
|
||||
public float getAlpha() {
|
||||
return mBubbleSectionView.getAlpha();
|
||||
}
|
||||
|
||||
/**
|
||||
* Menu action details used to create menu items
|
||||
*/
|
||||
static class MenuAction {
|
||||
private Icon mIcon;
|
||||
private @ColorInt int mTint;
|
||||
private String mTitle;
|
||||
private OnClickListener mOnClick;
|
||||
|
||||
MenuAction(Icon icon, String title, OnClickListener onClick) {
|
||||
this(icon, title, Color.TRANSPARENT, onClick);
|
||||
}
|
||||
|
||||
MenuAction(Icon icon, String title, @ColorInt int tint, OnClickListener onClick) {
|
||||
this.mIcon = icon;
|
||||
this.mTitle = title;
|
||||
this.mTint = tint;
|
||||
this.mOnClick = onClick;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,242 @@
|
||||
/*
|
||||
* 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.bubbles.bar;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.drawable.Icon;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import androidx.core.content.ContextCompat;
|
||||
import androidx.dynamicanimation.animation.DynamicAnimation;
|
||||
import androidx.dynamicanimation.animation.SpringForce;
|
||||
|
||||
import com.android.wm.shell.R;
|
||||
import com.android.wm.shell.animation.PhysicsAnimator;
|
||||
import com.android.wm.shell.bubbles.Bubble;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* Manages bubble bar expanded view menu presentation and animations
|
||||
*/
|
||||
class BubbleBarMenuViewController {
|
||||
private static final float MENU_INITIAL_SCALE = 0.5f;
|
||||
private final Context mContext;
|
||||
private final ViewGroup mRootView;
|
||||
private @Nullable Listener mListener;
|
||||
private @Nullable Bubble mBubble;
|
||||
private @Nullable BubbleBarMenuView mMenuView;
|
||||
/** A transparent view used to intercept touches to collapse menu when presented */
|
||||
private @Nullable View mScrimView;
|
||||
private @Nullable PhysicsAnimator<BubbleBarMenuView> mMenuAnimator;
|
||||
private PhysicsAnimator.SpringConfig mMenuSpringConfig;
|
||||
|
||||
BubbleBarMenuViewController(Context context, ViewGroup rootView) {
|
||||
mContext = context;
|
||||
mRootView = rootView;
|
||||
mMenuSpringConfig = new PhysicsAnimator.SpringConfig(
|
||||
SpringForce.STIFFNESS_MEDIUM, SpringForce.DAMPING_RATIO_LOW_BOUNCY);
|
||||
}
|
||||
|
||||
/** Sets menu actions listener */
|
||||
void setListener(@Nullable Listener listener) {
|
||||
mListener = listener;
|
||||
}
|
||||
|
||||
/** Update menu with bubble */
|
||||
void updateMenu(@NonNull Bubble bubble) {
|
||||
mBubble = bubble;
|
||||
}
|
||||
|
||||
/**
|
||||
* Show bubble bar expanded view menu
|
||||
* @param animated if should animate transition
|
||||
*/
|
||||
void showMenu(boolean animated) {
|
||||
if (mMenuView == null || mScrimView == null) {
|
||||
setupMenu();
|
||||
}
|
||||
cancelAnimations();
|
||||
mMenuView.setVisibility(View.VISIBLE);
|
||||
mScrimView.setVisibility(View.VISIBLE);
|
||||
Runnable endActions = () -> {
|
||||
mMenuView.getChildAt(0).requestAccessibilityFocus();
|
||||
if (mListener != null) {
|
||||
mListener.onMenuVisibilityChanged(true /* isShown */);
|
||||
}
|
||||
};
|
||||
if (animated) {
|
||||
animateTransition(true /* show */, endActions);
|
||||
} else {
|
||||
endActions.run();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Hide bubble bar expanded view menu
|
||||
* @param animated if should animate transition
|
||||
*/
|
||||
void hideMenu(boolean animated) {
|
||||
if (mMenuView == null || mScrimView == null) return;
|
||||
cancelAnimations();
|
||||
Runnable endActions = () -> {
|
||||
mMenuView.setVisibility(View.GONE);
|
||||
mScrimView.setVisibility(View.GONE);
|
||||
if (mListener != null) {
|
||||
mListener.onMenuVisibilityChanged(false /* isShown */);
|
||||
}
|
||||
};
|
||||
if (animated) {
|
||||
animateTransition(false /* show */, endActions);
|
||||
} else {
|
||||
endActions.run();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Animate show/hide menu transition
|
||||
* @param show if should show or hide the menu
|
||||
* @param endActions will be called when animation ends
|
||||
*/
|
||||
private void animateTransition(boolean show, Runnable endActions) {
|
||||
if (mMenuView == null) return;
|
||||
mMenuAnimator = PhysicsAnimator.getInstance(mMenuView);
|
||||
mMenuAnimator.setDefaultSpringConfig(mMenuSpringConfig);
|
||||
mMenuAnimator
|
||||
.spring(DynamicAnimation.ALPHA, show ? 1f : 0f)
|
||||
.spring(DynamicAnimation.SCALE_Y, show ? 1f : MENU_INITIAL_SCALE)
|
||||
.withEndActions(() -> {
|
||||
mMenuAnimator = null;
|
||||
endActions.run();
|
||||
})
|
||||
.start();
|
||||
}
|
||||
|
||||
/** Cancel running animations */
|
||||
private void cancelAnimations() {
|
||||
if (mMenuAnimator != null) {
|
||||
mMenuAnimator.cancel();
|
||||
mMenuAnimator = null;
|
||||
}
|
||||
}
|
||||
|
||||
/** Sets up and inflate menu views */
|
||||
private void setupMenu() {
|
||||
// Menu view setup
|
||||
mMenuView = (BubbleBarMenuView) LayoutInflater.from(mContext).inflate(
|
||||
R.layout.bubble_bar_menu_view, mRootView, false);
|
||||
mMenuView.setAlpha(0f);
|
||||
mMenuView.setPivotY(0f);
|
||||
mMenuView.setScaleY(MENU_INITIAL_SCALE);
|
||||
mMenuView.setOnCloseListener(() -> hideMenu(true /* animated */));
|
||||
if (mBubble != null) {
|
||||
mMenuView.updateInfo(mBubble);
|
||||
mMenuView.updateActions(createMenuActions(mBubble));
|
||||
}
|
||||
// Scrim view setup
|
||||
mScrimView = new View(mContext);
|
||||
mScrimView.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO);
|
||||
mScrimView.setOnClickListener(view -> hideMenu(true /* animated */));
|
||||
// Attach to root view
|
||||
mRootView.addView(mScrimView);
|
||||
mRootView.addView(mMenuView);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates menu actions to populate menu view
|
||||
* @param bubble used to create actions depending on bubble type
|
||||
*/
|
||||
private ArrayList<BubbleBarMenuView.MenuAction> createMenuActions(Bubble bubble) {
|
||||
ArrayList<BubbleBarMenuView.MenuAction> menuActions = new ArrayList<>();
|
||||
Resources resources = mContext.getResources();
|
||||
|
||||
if (bubble.isConversation()) {
|
||||
// Don't bubble conversation action
|
||||
menuActions.add(new BubbleBarMenuView.MenuAction(
|
||||
Icon.createWithResource(mContext, R.drawable.bubble_ic_stop_bubble),
|
||||
resources.getString(R.string.bubbles_dont_bubble_conversation),
|
||||
view -> {
|
||||
hideMenu(true /* animated */);
|
||||
if (mListener != null) {
|
||||
mListener.onUnBubbleConversation(bubble);
|
||||
}
|
||||
}
|
||||
));
|
||||
// Open settings action
|
||||
Icon appIcon = bubble.getRawAppBadge() != null ? Icon.createWithBitmap(
|
||||
bubble.getRawAppBadge()) : null;
|
||||
menuActions.add(new BubbleBarMenuView.MenuAction(
|
||||
appIcon,
|
||||
resources.getString(R.string.bubbles_app_settings, bubble.getAppName()),
|
||||
view -> {
|
||||
hideMenu(true /* animated */);
|
||||
if (mListener != null) {
|
||||
mListener.onOpenAppSettings(bubble);
|
||||
}
|
||||
}
|
||||
));
|
||||
}
|
||||
|
||||
// Dismiss bubble action
|
||||
menuActions.add(new BubbleBarMenuView.MenuAction(
|
||||
Icon.createWithResource(resources, R.drawable.ic_remove_no_shadow),
|
||||
resources.getString(R.string.bubble_dismiss_text),
|
||||
ContextCompat.getColor(mContext, R.color.bubble_bar_expanded_view_menu_close),
|
||||
view -> {
|
||||
hideMenu(true /* animated */);
|
||||
if (mListener != null) {
|
||||
mListener.onDismissBubble(bubble);
|
||||
}
|
||||
}
|
||||
));
|
||||
|
||||
return menuActions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Bubble bar expanded view menu actions listener
|
||||
*/
|
||||
interface Listener {
|
||||
/**
|
||||
* Called when manage menu is shown/hidden
|
||||
* If animated will be called when animation ends
|
||||
*/
|
||||
void onMenuVisibilityChanged(boolean visible);
|
||||
|
||||
/**
|
||||
* Un-bubbles conversation and removes the bubble from the stack
|
||||
* This conversation will not be bubbled with new messages
|
||||
* @see com.android.wm.shell.bubbles.BubbleController
|
||||
*/
|
||||
void onUnBubbleConversation(Bubble bubble);
|
||||
|
||||
/**
|
||||
* Launches app notification bubble settings for the bubble with intent created in:
|
||||
* {@code Bubble.getSettingsIntent}
|
||||
*/
|
||||
void onOpenAppSettings(Bubble bubble);
|
||||
|
||||
/**
|
||||
* Dismiss bubble and remove it from the bubble stack
|
||||
*/
|
||||
void onDismissBubble(Bubble bubble);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user