diff --git a/packages/SystemUI/res/drawable/bubble_expanded_header_bg.xml b/packages/SystemUI/res/drawable/bubble_expanded_header_bg.xml new file mode 100644 index 0000000000000..26bf981f96255 --- /dev/null +++ b/packages/SystemUI/res/drawable/bubble_expanded_header_bg.xml @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/packages/SystemUI/res/layout/bubble_expanded_view.xml b/packages/SystemUI/res/layout/bubble_expanded_view.xml index b2307e71e3ce5..1aeb52cdce690 100644 --- a/packages/SystemUI/res/layout/bubble_expanded_view.xml +++ b/packages/SystemUI/res/layout/bubble_expanded_view.xml @@ -20,11 +20,23 @@ android:layout_width="match_parent" android:id="@+id/bubble_expanded_view"> - - + + + diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index 06df0e763498f..10e5f74983a3c 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -997,4 +997,8 @@ 6dp 16dp + + 48dp + + 24dp diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java index 75776098decf3..9f3ff782211de 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java @@ -23,15 +23,20 @@ import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT; import static com.android.systemui.bubbles.BubbleMovementHelper.EDGE_OVERLAP; import android.app.Notification; +import android.app.PendingIntent; import android.content.Context; +import android.content.pm.ActivityInfo; import android.graphics.Point; import android.graphics.Rect; import android.provider.Settings; import android.service.notification.StatusBarNotification; +import android.util.Log; import android.view.ViewGroup; import android.view.WindowManager; import android.widget.FrameLayout; +import androidx.annotation.Nullable; + import com.android.internal.annotations.VisibleForTesting; import com.android.systemui.Dependency; import com.android.systemui.R; @@ -68,6 +73,8 @@ public class BubbleController { private static final String ENABLE_AUTO_BUBBLE_MESSAGES = "experiment_autobubble_messaging"; private static final String ENABLE_AUTO_BUBBLE_ONGOING = "experiment_autobubble_ongoing"; private static final String ENABLE_AUTO_BUBBLE_ALL = "experiment_autobubble_all"; + private static final String ENABLE_BUBBLE_ACTIVITY_VIEW = "experiment_bubble_activity_view"; + private static final String ENABLE_BUBBLE_CONTENT_INTENT = "experiment_bubble_content_intent"; private final Context mContext; private final NotificationEntryManager mNotificationEntryManager; @@ -189,6 +196,9 @@ public class BubbleController { // It's new BubbleView bubble = new BubbleView(mContext); bubble.setNotif(notif); + if (shouldUseActivityView(mContext)) { + bubble.setAppOverlayIntent(getAppOverlayIntent(notif)); + } mBubbles.put(bubble.getKey(), bubble); boolean setPosition = mStackView != null && mStackView.getVisibility() != VISIBLE; @@ -216,6 +226,21 @@ public class BubbleController { } } + @Nullable + private PendingIntent getAppOverlayIntent(NotificationEntry notif) { + Notification notification = notif.notification.getNotification(); + if (canLaunchInActivityView(notification.getAppOverlayIntent())) { + return notification.getAppOverlayIntent(); + } else if (shouldUseContentIntent(mContext) + && canLaunchInActivityView(notification.contentIntent)) { + Log.d(TAG, "[addBubble " + notif.key + + "]: No appOverlayIntent, using contentIntent."); + return notification.contentIntent; + } + Log.d(TAG, "[addBubble " + notif.key + "]: No supported intent for ActivityView."); + return null; + } + /** * Removes the bubble associated with the {@param uri}. */ @@ -223,6 +248,7 @@ public class BubbleController { BubbleView bv = mBubbles.get(key); if (mStackView != null && bv != null) { mStackView.removeBubble(bv); + bv.destroyActivityView(mStackView); bv.getEntry().setBubbleDismissed(true); } @@ -282,9 +308,10 @@ public class BubbleController { } } } - for (BubbleView view : viewsToRemove) { - mBubbles.remove(view.getKey()); - mStackView.removeBubble(view); + for (BubbleView bubbleView : viewsToRemove) { + mBubbles.remove(bubbleView.getKey()); + mStackView.removeBubble(bubbleView); + bubbleView.destroyActivityView(mStackView); } if (mStackView != null) { mStackView.setVisibility(visible ? VISIBLE : INVISIBLE); @@ -306,6 +333,17 @@ public class BubbleController { return mTempRect; } + private boolean canLaunchInActivityView(PendingIntent intent) { + if (intent == null) { + return false; + } + ActivityInfo info = + intent.getIntent().resolveActivityInfo(mContext.getPackageManager(), 0); + return info != null + && ActivityInfo.isResizeableMode(info.resizeMode) + && (info.flags & ActivityInfo.FLAG_ALLOW_EMBEDDED) != 0; + } + @VisibleForTesting BubbleStackView getStackView() { return mStackView; @@ -378,4 +416,14 @@ public class BubbleController { return Settings.Secure.getInt(context.getContentResolver(), ENABLE_AUTO_BUBBLE_ALL, 0) != 0; } + + private static boolean shouldUseActivityView(Context context) { + return Settings.Secure.getInt(context.getContentResolver(), + ENABLE_BUBBLE_ACTIVITY_VIEW, 0) != 0; + } + + private static boolean shouldUseContentIntent(Context context) { + return Settings.Secure.getInt(context.getContentResolver(), + ENABLE_BUBBLE_CONTENT_INTENT, 0) != 0; + } } diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedViewContainer.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedViewContainer.java index e28d96b2def93..badefe182bdde 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedViewContainer.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedViewContainer.java @@ -24,6 +24,7 @@ import android.graphics.drawable.ShapeDrawable; import android.util.AttributeSet; import android.view.View; import android.widget.LinearLayout; +import android.widget.TextView; import com.android.systemui.R; import com.android.systemui.recents.TriangleShape; @@ -35,6 +36,8 @@ public class BubbleExpandedViewContainer extends LinearLayout { // The triangle pointing to the expanded view private View mPointerView; + // The view displayed between the pointer and the expanded view + private TextView mHeaderView; // The view that is being displayed for the expanded state private View mExpandedView; @@ -68,6 +71,7 @@ public class BubbleExpandedViewContainer extends LinearLayout { TriangleShape.create(width, height, true /* pointUp */)); triangleDrawable.setTint(Color.WHITE); // TODO: dark mode mPointerView.setBackground(triangleDrawable); + mHeaderView = findViewById(R.id.bubble_content_header); } /** @@ -79,10 +83,20 @@ public class BubbleExpandedViewContainer extends LinearLayout { mPointerView.setTranslationX(x); } + /** + * Set the text displayed within the header. + */ + public void setHeaderText(CharSequence text) { + mHeaderView.setText(text); + } + /** * Set the view to display for the expanded state. Passing null will clear the view. */ public void setExpandedView(View view) { + if (mExpandedView == view) { + return; + } if (mExpandedView != null) { removeView(mExpandedView); } diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java index d69e7b3ae924b..3280a331a5c7b 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java @@ -21,10 +21,13 @@ import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.AnimatorSet; +import android.app.ActivityView; +import android.app.PendingIntent; import android.content.Context; import android.content.res.Resources; import android.graphics.Point; import android.graphics.RectF; +import android.util.Log; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; @@ -50,6 +53,7 @@ import com.android.systemui.statusbar.notification.stack.ViewState; */ public class BubbleStackView extends FrameLayout implements BubbleTouchHandler.FloatingView { + private static final String TAG = "BubbleStackView"; private Point mDisplaySize; private FrameLayout mBubbleContainer; @@ -59,6 +63,7 @@ public class BubbleStackView extends FrameLayout implements BubbleTouchHandler.F private int mBubblePadding; private boolean mIsExpanded; + private int mExpandedBubbleHeight; private BubbleView mExpandedBubble; private Point mCollapsedPosition; private BubbleTouchHandler mTouchHandler; @@ -106,6 +111,7 @@ public class BubbleStackView extends FrameLayout implements BubbleTouchHandler.F mBubbleSize = res.getDimensionPixelSize(R.dimen.bubble_size); mBubblePadding = res.getDimensionPixelSize(R.dimen.bubble_padding); + mExpandedBubbleHeight = res.getDimensionPixelSize(R.dimen.bubble_expanded_default_height); mDisplaySize = new Point(); WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); wm.getDefaultDisplay().getSize(mDisplaySize); @@ -389,27 +395,63 @@ public class BubbleStackView extends FrameLayout implements BubbleTouchHandler.F } private void updateExpandedBubble() { - if (mExpandedBubble != null) { + if (mExpandedBubble == null) { + return; + } + + if (mExpandedBubble.hasAppOverlayIntent()) { + ActivityView expandedView = mExpandedBubble.getActivityView(); + expandedView.setLayoutParams(new ViewGroup.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, mExpandedBubbleHeight)); + + final PendingIntent intent = mExpandedBubble.getAppOverlayIntent(); + mExpandedViewContainer.setHeaderText(intent.getIntent().getComponent().toShortString()); + mExpandedViewContainer.setExpandedView(expandedView); + expandedView.setCallback(new ActivityView.StateCallback() { + @Override + public void onActivityViewReady(ActivityView view) { + Log.d(TAG, "onActivityViewReady(" + + mExpandedBubble.getEntry().key + "): " + view); + view.startActivity(intent); + } + + @Override + public void onActivityViewDestroyed(ActivityView view) { + NotificationEntry entry = mExpandedBubble.getEntry(); + Log.d(TAG, "onActivityViewDestroyed(key=" + + ((entry != null) ? entry.key : "(none)") + "): " + view); + } + }); + } else { ExpandableNotificationRow row = mExpandedBubble.getRowView(); - if (!row.equals(mExpandedViewContainer.getChildAt(0))) { + if (!row.equals(mExpandedViewContainer.getExpandedView())) { // Different expanded view than what we have mExpandedViewContainer.setExpandedView(null); } - int pointerPosition = mExpandedBubble.getPosition().x - + (mExpandedBubble.getWidth() / 2); - mExpandedViewContainer.setPointerPosition(pointerPosition); mExpandedViewContainer.setExpandedView(row); + mExpandedViewContainer.setHeaderText(null); } + int pointerPosition = mExpandedBubble.getPosition().x + + (mExpandedBubble.getWidth() / 2); + mExpandedViewContainer.setPointerPosition(pointerPosition); } private void applyCurrentState() { + Log.d(TAG, "applyCurrentState: mIsExpanded=" + mIsExpanded); + mExpandedViewContainer.setVisibility(mIsExpanded ? VISIBLE : GONE); if (!mIsExpanded) { mExpandedViewContainer.setExpandedView(null); } else { mExpandedViewContainer.setTranslationY(mBubbleContainer.getHeight()); - ExpandableNotificationRow row = mExpandedBubble.getRowView(); - applyRowState(row); + View expandedView = mExpandedViewContainer.getExpandedView(); + if (expandedView instanceof ActivityView) { + if (expandedView.isAttachedToWindow()) { + ((ActivityView) expandedView).onLocationChanged(); + } + } else { + applyRowState(mExpandedBubble.getRowView()); + } } int bubbsCount = mBubbleContainer.getChildCount(); for (int i = 0; i < bubbsCount; i++) { diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleTouchHandler.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleTouchHandler.java index 88030eefdf543..96b2dbab9bdfb 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleTouchHandler.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleTouchHandler.java @@ -33,7 +33,7 @@ import com.android.systemui.pip.phone.PipDismissViewController; * Handles interpreting touches on a {@link BubbleStackView}. This includes expanding, collapsing, * dismissing, and flings. */ -public class BubbleTouchHandler implements View.OnTouchListener { +class BubbleTouchHandler implements View.OnTouchListener { private BubbleController mController = Dependency.get(BubbleController.class); private PipDismissViewController mDismissViewController; diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleView.java index 3307992e779a9..c1bbb9379e9c5 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleView.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleView.java @@ -16,7 +16,9 @@ package com.android.systemui.bubbles; +import android.app.ActivityView; import android.app.Notification; +import android.app.PendingIntent; import android.content.Context; import android.graphics.Color; import android.graphics.Point; @@ -25,7 +27,9 @@ import android.graphics.drawable.Icon; import android.graphics.drawable.ShapeDrawable; import android.graphics.drawable.shapes.OvalShape; import android.util.AttributeSet; +import android.util.Log; import android.view.View; +import android.view.ViewGroup; import android.widget.ImageView; import android.widget.LinearLayout; @@ -37,7 +41,7 @@ import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow /** * A floating object on the screen that has a collapsed and expanded state. */ -public class BubbleView extends LinearLayout implements BubbleTouchHandler.FloatingView { +class BubbleView extends LinearLayout implements BubbleTouchHandler.FloatingView { private static final String TAG = "BubbleView"; private Context mContext; @@ -46,6 +50,8 @@ public class BubbleView extends LinearLayout implements BubbleTouchHandler.Float private NotificationEntry mEntry; private int mBubbleSize; private int mIconSize; + private PendingIntent mAppOverlayIntent; + private ActivityView mActivityView; public BubbleView(Context context) { this(context, null); @@ -117,12 +123,51 @@ public class BubbleView extends LinearLayout implements BubbleTouchHandler.Float } /** - * @return the view to display when the bubble is expanded. + * @return the view to display notification content when the bubble is expanded. */ public ExpandableNotificationRow getRowView() { return mEntry.getRow(); } + /** + * @return a view used to display app overlay content when expanded. + */ + public ActivityView getActivityView() { + if (mActivityView == null) { + mActivityView = new ActivityView(mContext); + Log.d(TAG, "[getActivityView] created: " + mActivityView); + } + return mActivityView; + } + + /** + * Removes and releases an ActivityView if one was previously created for this bubble. + */ + public void destroyActivityView(ViewGroup tmpParent) { + if (mActivityView == null) { + return; + } + // HACK: Only release if initialized. There's no way to know if the ActivityView has + // been initialized. Calling release() if it hasn't been initialized will crash. + + if (!mActivityView.isAttachedToWindow()) { + // HACK: release() will crash if the view is not attached. + + mActivityView.setVisibility(View.GONE); + tmpParent.addView(mActivityView, new ViewGroup.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.MATCH_PARENT)); + } + try { + mActivityView.release(); + } catch (IllegalStateException ex) { + Log.e(TAG, "ActivityView either already released, or not yet initialized.", ex); + } + + ((ViewGroup) mActivityView.getParent()).removeView(mActivityView); + mActivityView = null; + } + @Override public void setPosition(int x, int y) { setTranslationX(x); @@ -162,4 +207,20 @@ public class BubbleView extends LinearLayout implements BubbleTouchHandler.Float lp.height = mBubbleSize; v.setLayoutParams(lp); } + + /** + * @return whether an ActivityView should be used to display the content of this Bubble + */ + public boolean hasAppOverlayIntent() { + return mAppOverlayIntent != null; + } + + public PendingIntent getAppOverlayIntent() { + return mAppOverlayIntent; + + } + + public void setAppOverlayIntent(PendingIntent intent) { + mAppOverlayIntent = intent; + } }