Merge "Enable ActivityView in bubbles"

This commit is contained in:
TreeHugger Robot
2019-01-10 16:03:24 +00:00
committed by Android (Google) Code Review
8 changed files with 228 additions and 15 deletions

View 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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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;
}
}

View File

@@ -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);
}

View File

@@ -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++) {

View File

@@ -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;

View File

@@ -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;
}
}