summaryrefslogtreecommitdiff
path: root/java/com/android/dialershared/bubble
diff options
context:
space:
mode:
Diffstat (limited to 'java/com/android/dialershared/bubble')
-rw-r--r--java/com/android/dialershared/bubble/Bubble.java182
-rw-r--r--java/com/android/dialershared/bubble/BubbleInfo.java19
-rw-r--r--java/com/android/dialershared/bubble/MoveHandler.java22
-rw-r--r--java/com/android/dialershared/bubble/g3doc/INTEGRATION.md69
-rw-r--r--java/com/android/dialershared/bubble/g3doc/images/bubble_collapsed.pngbin0 -> 60187 bytes
-rw-r--r--java/com/android/dialershared/bubble/g3doc/images/bubble_expanded.pngbin0 -> 79674 bytes
-rw-r--r--java/com/android/dialershared/bubble/g3doc/images/bubble_state.pngbin0 -> 83470 bytes
-rw-r--r--java/com/android/dialershared/bubble/g3doc/images/bubble_text.pngbin0 -> 65641 bytes
-rw-r--r--java/com/android/dialershared/bubble/res/layout/bubble_base.xml25
-rw-r--r--java/com/android/dialershared/bubble/res/values/values.xml10
10 files changed, 263 insertions, 64 deletions
diff --git a/java/com/android/dialershared/bubble/Bubble.java b/java/com/android/dialershared/bubble/Bubble.java
index 3eb88aa22..dbb5ea759 100644
--- a/java/com/android/dialershared/bubble/Bubble.java
+++ b/java/com/android/dialershared/bubble/Bubble.java
@@ -26,8 +26,12 @@ import android.content.Context;
import android.content.Intent;
import android.content.res.ColorStateList;
import android.graphics.PixelFormat;
+import android.graphics.drawable.Animatable;
+import android.graphics.drawable.Drawable;
import android.graphics.drawable.RippleDrawable;
import android.net.Uri;
+import android.os.Build.VERSION;
+import android.os.Build.VERSION_CODES;
import android.os.Handler;
import android.provider.Settings;
import android.support.annotation.ColorInt;
@@ -47,6 +51,7 @@ import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
+import android.view.ViewGroup.MarginLayoutParams;
import android.view.ViewPropertyAnimator;
import android.view.ViewTreeObserver.OnPreDrawListener;
import android.view.WindowManager;
@@ -69,6 +74,8 @@ import java.util.List;
* convenience)
*/
public class Bubble {
+ // This class has some odd behavior that is not immediately obvious in order to avoid jank when
+ // resizing. See http://go/bubble-resize for details.
// How long text should show after showText(CharSequence) is called
private static final int SHOW_TEXT_DURATION_MILLIS = 3000;
@@ -96,6 +103,8 @@ public class Bubble {
private final Handler handler = new Handler();
private ViewHolder viewHolder;
+ private ViewPropertyAnimator collapseAnimation;
+ private Integer overrideGravity;
@Retention(RetentionPolicy.SOURCE)
@IntDef({CollapseEnd.NOTHING, CollapseEnd.HIDE})
@@ -114,7 +123,7 @@ public class Bubble {
public static boolean canShowBubbles(@NonNull Context context) {
return canShowBubblesForTesting != null
? canShowBubblesForTesting
- : Settings.canDrawOverlays(context);
+ : VERSION.SDK_INT < VERSION_CODES.M || Settings.canDrawOverlays(context);
}
@VisibleForTesting(otherwise = VisibleForTesting.NONE)
@@ -127,7 +136,7 @@ public class Bubble {
public static Intent getRequestPermissionIntent(@NonNull Context context) {
return new Intent(
Settings.ACTION_MANAGE_OVERLAY_PERMISSION,
- new Uri.Builder().scheme("package").fragment(context.getPackageName()).build());
+ Uri.fromParts("package", context.getPackageName(), null));
}
/** Creates instances of Bubble. The default implementation just calls the constructor. */
@@ -183,13 +192,12 @@ public class Bubble {
type,
LayoutParams.FLAG_NOT_TOUCH_MODAL
| LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
- | LayoutParams.FLAG_NOT_FOCUSABLE,
+ | LayoutParams.FLAG_NOT_FOCUSABLE
+ | LayoutParams.FLAG_LAYOUT_NO_LIMITS,
PixelFormat.TRANSLUCENT);
windowParams.gravity = Gravity.TOP | Gravity.LEFT;
- windowParams.x =
- context.getResources().getDimensionPixelOffset(R.dimen.bubble_initial_offset_x);
- windowParams.y =
- context.getResources().getDimensionPixelOffset(R.dimen.bubble_initial_offset_y);
+ windowParams.x = context.getResources().getDimensionPixelOffset(R.dimen.bubble_safe_margin_x);
+ windowParams.y = currentInfo.getStartingYPosition();
windowParams.height = LayoutParams.WRAP_CONTENT;
windowParams.width = LayoutParams.WRAP_CONTENT;
}
@@ -203,6 +211,7 @@ public class Bubble {
showAnimator.setInterpolator(new OvershootInterpolator());
showAnimator.start();
isShowing = true;
+ updatePrimaryIconAnimation();
}
/**
@@ -235,6 +244,7 @@ public class Bubble {
() -> {
windowManager.removeView(viewHolder.getRoot());
isShowing = false;
+ updatePrimaryIconAnimation();
})
.start();
}
@@ -342,6 +352,11 @@ public class Bubble {
SHOW_TEXT_DURATION_MILLIS);
}
+ @Nullable
+ Integer getGravityOverride() {
+ return overrideGravity;
+ }
+
void onMoveStart() {
startCollapse(CollapseEnd.NOTHING);
viewHolder
@@ -353,23 +368,27 @@ public class Bubble {
void onMoveFinish() {
viewHolder.getPrimaryButton().animate().translationZ(0);
+ // If it's GONE, no resize is necessary. If it's VISIBLE, it will get cleaned up when the
+ // collapse animation finishes
+ if (viewHolder.getExpandedView().getVisibility() == View.INVISIBLE) {
+ doResize(null);
+ }
}
void primaryButtonClick() {
if (expanded || textShowing || currentInfo.getActions().isEmpty()) {
try {
- currentInfo.getPrimaryAction().send();
+ currentInfo.getPrimaryIntent().send();
} catch (CanceledException e) {
throw new RuntimeException(e);
}
return;
}
- boolean onRight = (windowParams.gravity & Gravity.RIGHT) == Gravity.RIGHT;
doResize(
() -> {
- onLeftRightSwitch(onRight);
- viewHolder.getExpandedView().setVisibility(View.VISIBLE);
+ onLeftRightSwitch(isDrawingFromRight());
+ viewHolder.setDrawerVisibility(View.VISIBLE);
});
View expandedView = viewHolder.getExpandedView();
expandedView
@@ -380,7 +399,7 @@ public class Bubble {
public boolean onPreDraw() {
expandedView.getViewTreeObserver().removeOnPreDrawListener(this);
expandedView.setTranslationX(
- onRight ? expandedView.getWidth() : -expandedView.getWidth());
+ isDrawingFromRight() ? expandedView.getWidth() : -expandedView.getWidth());
expandedView
.animate()
.setInterpolator(new LinearOutSlowInInterpolator())
@@ -393,6 +412,14 @@ public class Bubble {
}
void onLeftRightSwitch(boolean onRight) {
+ if (viewHolder.isMoving()) {
+ if (viewHolder.getExpandedView().getVisibility() == View.GONE) {
+ // If the drawer is not part of the layout we don't need to do anything. Layout flips will
+ // happen if necessary when opening the drawer.
+ return;
+ }
+ }
+
viewHolder
.getRoot()
.setLayoutDirection(onRight ? View.LAYOUT_DIRECTION_RTL : View.LAYOUT_DIRECTION_LTR);
@@ -437,6 +464,7 @@ public class Bubble {
viewHolder.getSecondButton().setVisibility(numButtons < 2 ? View.GONE : View.VISIBLE);
viewHolder.getPrimaryIcon().setImageIcon(currentInfo.getPrimaryIcon());
+ updatePrimaryIconAnimation();
viewHolder
.getExpandedView()
@@ -445,6 +473,17 @@ public class Bubble {
updateButtonStates();
}
+ private void updatePrimaryIconAnimation() {
+ Drawable drawable = viewHolder.getPrimaryIcon().getDrawable();
+ if (drawable instanceof Animatable) {
+ if (isShowing) {
+ ((Animatable) drawable).start();
+ } else {
+ ((Animatable) drawable).stop();
+ }
+ }
+ }
+
private void setBackgroundDrawable(CheckableImageButton view, @ColorInt int color) {
RippleDrawable itemRipple =
(RippleDrawable)
@@ -492,7 +531,7 @@ public class Bubble {
private void doAction(Action action) {
try {
- action.getAction().send();
+ action.getIntent().send();
} catch (CanceledException e) {
throw new RuntimeException(e);
}
@@ -504,9 +543,8 @@ public class Bubble {
// would occur. To fix this, instead of resizing the window, we create a new one and destroy
// the old one. There is a short delay before destroying the old view to ensure the new one has
// had time to draw.
- boolean onRight = (windowParams.gravity & Gravity.RIGHT) == Gravity.RIGHT;
ViewHolder oldViewHolder = viewHolder;
- if (onRight) {
+ if (isDrawingFromRight()) {
viewHolder = new ViewHolder(oldViewHolder.getRoot().getContext());
update();
viewHolder
@@ -519,12 +557,13 @@ public class Bubble {
operation.run();
}
- if (onRight) {
+ if (isDrawingFromRight()) {
swapViewHolders(oldViewHolder);
}
}
private void swapViewHolders(ViewHolder oldViewHolder) {
+ oldViewHolder.getShadowProvider().setVisibility(View.GONE);
ViewGroup root = viewHolder.getRoot();
windowManager.addView(root, windowParams);
root.getViewTreeObserver()
@@ -542,32 +581,56 @@ public class Bubble {
});
}
- private ViewPropertyAnimator startCollapse(@CollapseEnd int collapseEndAction) {
- setFocused(false);
- boolean onRight = (windowParams.gravity & Gravity.RIGHT) == Gravity.RIGHT;
+ private void startCollapse(@CollapseEnd int collapseEndAction) {
View expandedView = viewHolder.getExpandedView();
- return expandedView
- .animate()
- .translationX(onRight ? expandedView.getWidth() : -expandedView.getWidth())
- .setInterpolator(new FastOutLinearInInterpolator())
- .withEndAction(
- () -> {
- expanded = false;
- if (collapseEndAction == CollapseEnd.HIDE) {
- hide();
- } else if (!textShowing) {
- // Don't swap the window while the user is moving it, even if we're on the right.
- // The movement will help hide the jank of the resize.
- boolean swapWindow = onRight && !viewHolder.isMoving();
- if (swapWindow) {
- // We don't actually need to set the drawer to GONE since in the new window it
- // will already be GONE. Just do the resize operation.
- doResize(null);
- } else {
- expandedView.setVisibility(View.GONE);
- }
- }
- });
+ if (expandedView.getVisibility() != View.VISIBLE || collapseAnimation != null) {
+ // Drawer is already collapsed or animation is running.
+ return;
+ }
+
+ overrideGravity = isDrawingFromRight() ? Gravity.RIGHT : Gravity.LEFT;
+ setFocused(false);
+ collapseAnimation =
+ expandedView
+ .animate()
+ .translationX(isDrawingFromRight() ? expandedView.getWidth() : -expandedView.getWidth())
+ .setInterpolator(new FastOutLinearInInterpolator())
+ .withEndAction(
+ () -> {
+ collapseAnimation = null;
+ expanded = false;
+
+ if (textShowing) {
+ // Will do resize once the text is done.
+ return;
+ }
+
+ // Hide the drawer and resize if possible.
+ viewHolder.setDrawerVisibility(View.INVISIBLE);
+ if (!viewHolder.isMoving() || !isDrawingFromRight()) {
+ doResize(() -> viewHolder.setDrawerVisibility(View.GONE));
+ }
+
+ // If this collapse was to come before a hide, do it now.
+ if (collapseEndAction == CollapseEnd.HIDE) {
+ hide();
+ }
+
+ // Resume normal gravity after any resizing is done.
+ handler.postDelayed(
+ () -> {
+ overrideGravity = null;
+ if (!viewHolder.isMoving()) {
+ viewHolder.undoGravityOverride();
+ }
+ },
+ // Need to wait twice as long for resize and layout
+ WINDOW_REDRAW_DELAY_MILLIS * 2);
+ });
+ }
+
+ private boolean isDrawingFromRight() {
+ return (windowParams.gravity & Gravity.RIGHT) == Gravity.RIGHT;
}
private void setFocused(boolean focused) {
@@ -594,6 +657,7 @@ public class Bubble {
private final CheckableImageButton secondButton;
private final CheckableImageButton thirdButton;
private final View expandedView;
+ private final View shadowProvider;
public ViewHolder(Context context) {
// Window root is not in the layout file so that the inflater has a view to inflate into
@@ -604,6 +668,7 @@ public class Bubble {
primaryButton = contentView.findViewById(R.id.bubble_button_primary);
primaryIcon = contentView.findViewById(R.id.bubble_icon_primary);
primaryText = contentView.findViewById(R.id.bubble_text);
+ shadowProvider = contentView.findViewById(R.id.bubble_drawer_shadow_provider);
firstButton = contentView.findViewById(R.id.bubble_icon_first);
secondButton = contentView.findViewById(R.id.bubble_icon_second);
@@ -625,6 +690,28 @@ public class Bubble {
}
return false;
});
+ expandedView
+ .getViewTreeObserver()
+ .addOnDrawListener(
+ () -> {
+ int translationX = (int) expandedView.getTranslationX();
+ int parentOffset =
+ ((MarginLayoutParams) ((ViewGroup) expandedView.getParent()).getLayoutParams())
+ .leftMargin;
+ if (isDrawingFromRight()) {
+ int maxLeft =
+ shadowProvider.getRight()
+ - context.getResources().getDimensionPixelSize(R.dimen.bubble_size);
+ shadowProvider.setLeft(
+ Math.min(maxLeft, expandedView.getLeft() + translationX + parentOffset));
+ } else {
+ int minRight =
+ shadowProvider.getLeft()
+ + context.getResources().getDimensionPixelSize(R.dimen.bubble_size);
+ shadowProvider.setRight(
+ Math.max(minRight, expandedView.getRight() + translationX + parentOffset));
+ }
+ });
moveHandler = new MoveHandler(primaryButton, Bubble.this);
}
@@ -660,8 +747,21 @@ public class Bubble {
return expandedView;
}
+ public View getShadowProvider() {
+ return shadowProvider;
+ }
+
+ public void setDrawerVisibility(int visibility) {
+ expandedView.setVisibility(visibility);
+ shadowProvider.setVisibility(visibility);
+ }
+
public boolean isMoving() {
return moveHandler.isMoving();
}
+
+ public void undoGravityOverride() {
+ moveHandler.undoGravityOverride();
+ }
}
}
diff --git a/java/com/android/dialershared/bubble/BubbleInfo.java b/java/com/android/dialershared/bubble/BubbleInfo.java
index 52417ae7b..eb9abd059 100644
--- a/java/com/android/dialershared/bubble/BubbleInfo.java
+++ b/java/com/android/dialershared/bubble/BubbleInfo.java
@@ -20,6 +20,7 @@ import android.app.PendingIntent;
import android.graphics.drawable.Icon;
import android.support.annotation.ColorInt;
import android.support.annotation.NonNull;
+import android.support.annotation.Px;
import com.google.auto.value.AutoValue;
import java.util.Collections;
import java.util.List;
@@ -34,7 +35,10 @@ public abstract class BubbleInfo {
public abstract Icon getPrimaryIcon();
@NonNull
- public abstract PendingIntent getPrimaryAction();
+ public abstract PendingIntent getPrimaryIntent();
+
+ @Px
+ public abstract int getStartingYPosition();
@NonNull
public abstract List<Action> getActions();
@@ -45,9 +49,10 @@ public abstract class BubbleInfo {
public static Builder from(@NonNull BubbleInfo bubbleInfo) {
return builder()
- .setPrimaryAction(bubbleInfo.getPrimaryAction())
+ .setPrimaryIntent(bubbleInfo.getPrimaryIntent())
.setPrimaryColor(bubbleInfo.getPrimaryColor())
.setPrimaryIcon(bubbleInfo.getPrimaryIcon())
+ .setStartingYPosition(bubbleInfo.getStartingYPosition())
.setActions(bubbleInfo.getActions());
}
@@ -59,7 +64,9 @@ public abstract class BubbleInfo {
public abstract Builder setPrimaryIcon(@NonNull Icon primaryIcon);
- public abstract Builder setPrimaryAction(@NonNull PendingIntent primaryAction);
+ public abstract Builder setPrimaryIntent(@NonNull PendingIntent primaryIntent);
+
+ public abstract Builder setStartingYPosition(@Px int startingYPosition);
public abstract Builder setActions(List<Action> actions);
@@ -77,7 +84,7 @@ public abstract class BubbleInfo {
public abstract CharSequence getName();
@NonNull
- public abstract PendingIntent getAction();
+ public abstract PendingIntent getIntent();
public abstract boolean isEnabled();
@@ -89,7 +96,7 @@ public abstract class BubbleInfo {
public static Builder from(@NonNull Action action) {
return builder()
- .setAction(action.getAction())
+ .setIntent(action.getIntent())
.setChecked(action.isChecked())
.setEnabled(action.isEnabled())
.setName(action.getName())
@@ -104,7 +111,7 @@ public abstract class BubbleInfo {
public abstract Builder setName(@NonNull CharSequence name);
- public abstract Builder setAction(@NonNull PendingIntent action);
+ public abstract Builder setIntent(@NonNull PendingIntent intent);
public abstract Builder setEnabled(boolean enabled);
diff --git a/java/com/android/dialershared/bubble/MoveHandler.java b/java/com/android/dialershared/bubble/MoveHandler.java
index 8a21cd7e1..bc6db64bc 100644
--- a/java/com/android/dialershared/bubble/MoveHandler.java
+++ b/java/com/android/dialershared/bubble/MoveHandler.java
@@ -39,7 +39,7 @@ class MoveHandler implements OnTouchListener {
// Amount the ViewConfiguration's minFlingVelocity will be scaled by for our own minVelocity
private static final int MIN_FLING_VELOCITY_FACTOR = 8;
// The friction multiplier to control how slippery the bubble is when flung
- private static final float SCROLL_FRICTION_MULTIPLIER = 8f;
+ private static final float SCROLL_FRICTION_MULTIPLIER = 4f;
private final Context context;
private final WindowManager windowManager;
@@ -78,12 +78,22 @@ class MoveHandler implements OnTouchListener {
@Override
public void setValue(LayoutParams windowParams, float value) {
+ boolean wasOnRight = (windowParams.gravity & Gravity.RIGHT) == Gravity.RIGHT;
int displayWidth = context.getResources().getDisplayMetrics().widthPixels;
- boolean onRight = value > displayWidth / 2;
+ boolean onRight;
+ Integer gravityOverride = bubble.getGravityOverride();
+ if (gravityOverride == null) {
+ onRight = value > displayWidth / 2;
+ } else {
+ onRight = (gravityOverride & Gravity.RIGHT) == Gravity.RIGHT;
+ }
int centeringOffset = bubbleSize / 2 + shadowPaddingSize;
windowParams.x =
(int) (onRight ? (displayWidth - value - centeringOffset) : value - centeringOffset);
windowParams.gravity = Gravity.TOP | (onRight ? Gravity.RIGHT : Gravity.LEFT);
+ if (wasOnRight != onRight) {
+ bubble.onLeftRightSwitch(onRight);
+ }
if (bubble.isShowing()) {
windowManager.updateViewLayout(bubble.getRootView(), windowParams);
}
@@ -134,6 +144,11 @@ class MoveHandler implements OnTouchListener {
return isMoving;
}
+ public void undoGravityOverride() {
+ LayoutParams windowParams = bubble.getWindowParams();
+ xProperty.setValue(windowParams, xProperty.getValue(windowParams));
+ }
+
@Override
public boolean onTouch(View v, MotionEvent event) {
float eventX = event.getRawX();
@@ -190,13 +205,12 @@ class MoveHandler implements OnTouchListener {
} else {
snapX();
}
-
+ isMoving = false;
bubble.onMoveFinish();
} else {
v.performClick();
bubble.primaryButtonClick();
}
- isMoving = false;
break;
}
return true;
diff --git a/java/com/android/dialershared/bubble/g3doc/INTEGRATION.md b/java/com/android/dialershared/bubble/g3doc/INTEGRATION.md
new file mode 100644
index 000000000..a13a6053b
--- /dev/null
+++ b/java/com/android/dialershared/bubble/g3doc/INTEGRATION.md
@@ -0,0 +1,69 @@
+# Floating Bubble Integration
+
+go/bubble-integration
+
+Author: keyboardr@
+
+Last Updated: 2017-06-06
+
+Floating bubbles provide a lightweight means of providing interactive UI while
+the user is away from the app. This document details the steps necessary to
+integrate these bubbles into your app.
+
+[TOC]
+
+![Floating bubble](images/bubble_collapsed.png){height=400}
+
+## Ensure Bubbles can be shown
+
+Add the `android.permission.SYSTEM_ALERT_WINDOW` permission to your manifest.
+Before you show the bubble, call `Bubble.canShowBubbles(Context)` to see if the
+user has granted you permission. If not, you can start an Activity from
+`Bubble.getRequestPermissionIntent(Context)` to navigate the user to the system
+settings to enable drawing over other apps. This is more than just a simple
+runtime permission; the user must explicitly allow you to draw over other apps
+via this system setting. System apps may have this allowed by default, but be
+sure to test.
+
+## Create your initial `BubbleInfo`
+
+Use `BubbleInfo.builder()` to populate a `BubbleInfo` with your color, main
+icon, main Intent (which should navigate back to your app), starting Y position,
+and a list of `Actions` to put in the drawer. Each `Action` will define its
+icon, user-displayable name (used for content description), Intent to perform
+when clicked, whether it is enabled (optional, default true), and whether it is
+checked (optional, default false).
+
+![Floating bubble expanded](images/bubble_expanded.png){height=400}
+
+## Create, show, and hide the Bubble
+
+Create the bubble using `Bubble.createBubble(Context, BubbleInfo)`. The `show()`
+method is safe to call at any time. If the Bubble is already showing, it is a
+no-op. `hide()` may also be called at any time and will collapse the drawer
+before hiding if already open. While `show()` will show immediately, `hide()`
+may need to wait for other operations or animations before the bubble is hidden.
+It is unlikely you will need to keep track of this, however. The bubble will be
+hidden at its next opportunity, and `hide()` will not block.
+
+![Floating bubble with state](images/bubble_state.png){height=400}
+
+## Update the Bubble's state
+
+Call `Bubble.setBubbleInfo(BubbleInfo)` to update all displayed state.
+`BubbleInfo`s are immutable, so to make a new one using an existing
+`BubbleInfo`, use `BubbleInfo.from(BubbleInfo)` to get a `Builder` with
+prepopulated info. If only the `Action` state has changed, it is more efficient
+to just call `Bubble.updateActions(List<Action>)`
+
+![Floating bubble with text](images/bubble_text.png){height=400}
+
+## Show text
+
+To temporarily replace the icon with a textual message, call
+`Bubble.showText(CharSequence)`. The text will be displayed for several seconds
+before transitioning back to the primary icon. The drawer will be closed if open
+and cannot be reopened while the text is displayed. Any calls to `hide()` will
+be deferred until after the text is done being displayed, so if you wish to show
+an ending message of some sort you may call `hide()` immediately after
+`showText(CharSequence)`.
diff --git a/java/com/android/dialershared/bubble/g3doc/images/bubble_collapsed.png b/java/com/android/dialershared/bubble/g3doc/images/bubble_collapsed.png
new file mode 100644
index 000000000..7ecc0675b
--- /dev/null
+++ b/java/com/android/dialershared/bubble/g3doc/images/bubble_collapsed.png
Binary files differ
diff --git a/java/com/android/dialershared/bubble/g3doc/images/bubble_expanded.png b/java/com/android/dialershared/bubble/g3doc/images/bubble_expanded.png
new file mode 100644
index 000000000..cd477f334
--- /dev/null
+++ b/java/com/android/dialershared/bubble/g3doc/images/bubble_expanded.png
Binary files differ
diff --git a/java/com/android/dialershared/bubble/g3doc/images/bubble_state.png b/java/com/android/dialershared/bubble/g3doc/images/bubble_state.png
new file mode 100644
index 000000000..21ca8a8b5
--- /dev/null
+++ b/java/com/android/dialershared/bubble/g3doc/images/bubble_state.png
Binary files differ
diff --git a/java/com/android/dialershared/bubble/g3doc/images/bubble_text.png b/java/com/android/dialershared/bubble/g3doc/images/bubble_text.png
new file mode 100644
index 000000000..9c476dca6
--- /dev/null
+++ b/java/com/android/dialershared/bubble/g3doc/images/bubble_text.png
Binary files differ
diff --git a/java/com/android/dialershared/bubble/res/layout/bubble_base.xml b/java/com/android/dialershared/bubble/res/layout/bubble_base.xml
index 3acd2af2e..76970f020 100644
--- a/java/com/android/dialershared/bubble/res/layout/bubble_base.xml
+++ b/java/com/android/dialershared/bubble/res/layout/bubble_base.xml
@@ -19,24 +19,36 @@
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
+ android:clipToPadding="false"
tools:theme="@style/Theme.AppCompat">
+ <View
+ android:id="@+id/bubble_drawer_shadow_provider"
+ android:layout_width="@dimen/bubble_size"
+ android:layout_height="@dimen/bubble_size"
+ android:layout_marginTop="@dimen/bubble_shadow_padding_size"
+ android:layout_marginBottom="@dimen/bubble_shadow_padding_size"
+ android:layout_marginStart="@dimen/bubble_shadow_padding_size"
+ android:background="@drawable/bubble_ripple_circle"
+ android:backgroundTint="@android:color/transparent"
+ android:elevation="10dp"
+ android:visibility="invisible"
+ />
<FrameLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="48dp"
+ android:elevation="10dp"
android:paddingTop="@dimen/bubble_shadow_padding_size"
android:paddingBottom="@dimen/bubble_shadow_padding_size"
- android:paddingEnd="@dimen/bubble_shadow_padding_size"
- android:background="@android:color/transparent">
+ android:paddingEnd="@dimen/bubble_shadow_padding_size">
<LinearLayout
android:id="@+id/bubble_expanded_layout"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingStart="32dp"
- android:paddingEnd="12dp"
+ android:paddingEnd="8dp"
android:background="@drawable/bubble_background_pill_ltr"
- android:elevation="2dp"
android:layoutDirection="inherit"
android:orientation="horizontal"
android:visibility="gone"
@@ -80,16 +92,15 @@
android:layout_height="wrap_content"
android:layout_gravity="start"
android:animateLayoutChanges="true"
- android:background="@android:color/transparent"
android:clipChildren="false"
- android:clipToPadding="false">
+ android:clipToPadding="false"
+ android:elevation="12dp">
<ViewAnimator
android:id="@+id/bubble_button_primary"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="@dimen/bubble_shadow_padding_size"
android:background="@drawable/bubble_ripple_circle"
- android:elevation="6dp"
android:measureAllChildren="false"
tools:backgroundTint="#FF0000AA">
<ImageView
diff --git a/java/com/android/dialershared/bubble/res/values/values.xml b/java/com/android/dialershared/bubble/res/values/values.xml
index 5b85e0d23..f5816172d 100644
--- a/java/com/android/dialershared/bubble/res/values/values.xml
+++ b/java/com/android/dialershared/bubble/res/values/values.xml
@@ -16,12 +16,10 @@
-->
<resources>
- <dimen name="bubble_safe_margin_x">0dp</dimen>
- <dimen name="bubble_safe_margin_y">0dp</dimen>
- <dimen name="bubble_initial_offset_x">0dp</dimen>
- <dimen name="bubble_initial_offset_y">120dp</dimen>
- <dimen name="bubble_size">64dp</dimen>
- <dimen name="bubble_icon_padding">20dp</dimen>
+ <dimen name="bubble_safe_margin_x">16dp</dimen>
+ <dimen name="bubble_safe_margin_y">64dp</dimen>
+ <dimen name="bubble_size">56dp</dimen>
+ <dimen name="bubble_icon_padding">16dp</dimen>
<dimen name="bubble_move_elevation_change">4dp</dimen>
<dimen name="bubble_shadow_padding_size">16dp</dimen>
</resources>