Merge "Enable ActivityView in bubbles"
This commit is contained in:
committed by
Android (Google) Code Review
commit
ccfc793fe2
32
packages/SystemUI/res/drawable/bubble_expanded_header_bg.xml
Normal file
32
packages/SystemUI/res/drawable/bubble_expanded_header_bg.xml
Normal file
@@ -0,0 +1,32 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
~ Copyright (C) 2018 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
|
||||
-->
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item>
|
||||
<shape android:shape="rectangle">
|
||||
<solid android:color="?android:attr/colorBackgroundFloating"/>
|
||||
<corners
|
||||
android:topLeftRadius="@dimen/corner_size"
|
||||
android:topRightRadius="@dimen/corner_size"/>
|
||||
</shape>
|
||||
</item>
|
||||
<item android:gravity="bottom">
|
||||
<shape>
|
||||
<size android:height="1dp"/>
|
||||
<solid android:color="?android:attr/textColorSecondary" />
|
||||
</shape>
|
||||
</item>
|
||||
</layer-list>
|
||||
@@ -20,11 +20,23 @@
|
||||
android:layout_width="match_parent"
|
||||
android:id="@+id/bubble_expanded_view">
|
||||
|
||||
<!-- TODO: header -->
|
||||
|
||||
<View
|
||||
android:id="@+id/pointer_view"
|
||||
android:layout_width="@dimen/bubble_pointer_width"
|
||||
android:layout_height="@dimen/bubble_pointer_height"
|
||||
/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/bubble_content_header"
|
||||
android:background="@drawable/bubble_expanded_header_bg"
|
||||
android:textAppearance="@*android:style/TextAppearance.Material.Title"
|
||||
android:textSize="18sp"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/bubble_expanded_header_height"
|
||||
android:gravity="start|center_vertical"
|
||||
android:singleLine="true"
|
||||
android:paddingLeft="@dimen/bubble_expanded_header_horizontal_padding"
|
||||
android:paddingRight="@dimen/bubble_expanded_header_horizontal_padding"
|
||||
/>
|
||||
|
||||
</com.android.systemui.bubbles.BubbleExpandedViewContainer>
|
||||
|
||||
@@ -997,4 +997,8 @@
|
||||
<dimen name="bubble_pointer_width">6dp</dimen>
|
||||
<!-- Extra padding around the dismiss target for bubbles -->
|
||||
<dimen name="bubble_dismiss_slop">16dp</dimen>
|
||||
<!-- Height of the header within the expanded view. -->
|
||||
<dimen name="bubble_expanded_header_height">48dp</dimen>
|
||||
<!-- Left and right padding applied to the header. -->
|
||||
<dimen name="bubble_expanded_header_horizontal_padding">24dp</dimen>
|
||||
</resources>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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++) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user