diff options
8 files changed, 290 insertions, 61 deletions
diff --git a/packages/SystemUI/res/drawable/bubble_expanded_header_bg.xml b/packages/SystemUI/res/drawable/bubble_expanded_header_bg.xml index 26bf981f9625..a76b7b1c8f7b 100644 --- a/packages/SystemUI/res/drawable/bubble_expanded_header_bg.xml +++ b/packages/SystemUI/res/drawable/bubble_expanded_header_bg.xml @@ -23,10 +23,4 @@ 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>
\ 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 f0d2b2eff130..403d92869fbf 100644 --- a/packages/SystemUI/res/layout/bubble_expanded_view.xml +++ b/packages/SystemUI/res/layout/bubble_expanded_view.xml @@ -27,48 +27,70 @@ android:layout_height="@dimen/bubble_pointer_height" /> - <LinearLayout - android:id="@+id/header_layout" - android:layout_height="@dimen/bubble_expanded_header_height" - android:layout_width="match_parent" - android:orientation="horizontal" + <FrameLayout + android:id="@+id/header_permission_wrapper" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:animateLayoutChanges="true" android:background="@drawable/bubble_expanded_header_bg"> - <TextView - android:id="@+id/header_text" - android:textAppearance="@*android:style/TextAppearance.Material.Title" - android:textSize="18sp" - android:layout_weight="1" - android:layout_width="0dp" - android:layout_height="match_parent" - android:gravity="start|center_vertical" - android:singleLine="true" - android:paddingLeft="@dimen/bubble_expanded_header_horizontal_padding" - android:paddingRight="@dimen/bubble_expanded_header_horizontal_padding" - /> + <LinearLayout + android:id="@+id/header_layout" + android:layout_height="@dimen/bubble_expanded_header_height" + android:layout_width="match_parent" + android:animateLayoutChanges="true" + android:orientation="horizontal"> - <ImageButton - android:id="@+id/deep_link_button" - android:layout_width="@dimen/bubble_header_icon_size" - android:layout_height="@dimen/bubble_header_icon_size" - android:gravity="end|center_vertical" - android:src="@drawable/ic_open_in_new" - android:scaleType="center" - android:tint="?android:attr/colorForeground" - android:background="?android:attr/selectableItemBackground" - /> + <TextView + android:id="@+id/header_text" + android:textAppearance="@*android:style/TextAppearance.Material.Title" + android:textSize="18sp" + android:layout_weight="1" + android:layout_width="0dp" + android:layout_height="match_parent" + android:gravity="start|center_vertical" + android:singleLine="true" + android:paddingLeft="@dimen/bubble_expanded_header_horizontal_padding" + android:paddingRight="@dimen/bubble_expanded_header_horizontal_padding" + /> + + <ImageButton + android:id="@+id/deep_link_button" + android:layout_width="@dimen/bubble_header_icon_size" + android:layout_height="@dimen/bubble_header_icon_size" + android:gravity="end|center_vertical" + android:src="@drawable/ic_open_in_new" + android:scaleType="center" + android:tint="?android:attr/colorForeground" + android:background="?android:attr/selectableItemBackground" + /> - <ImageButton - android:id="@id/settings_button" - android:layout_width="@dimen/bubble_header_icon_size" - android:layout_height="@dimen/bubble_header_icon_size" - android:src="@drawable/ic_settings" - android:gravity="end|center_vertical" - android:scaleType="center" - android:tint="?android:attr/colorForeground" - android:background="?android:attr/selectableItemBackground" + <ImageButton + android:id="@id/settings_button" + android:layout_width="@dimen/bubble_header_icon_size" + android:layout_height="@dimen/bubble_header_icon_size" + android:src="@drawable/ic_settings" + android:gravity="end|center_vertical" + android:scaleType="center" + android:tint="?android:attr/colorForeground" + android:background="?android:attr/selectableItemBackground" + /> + + </LinearLayout> + + <include layout="@layout/bubble_permission_view" + android:id="@+id/permission_layout" + android:layout_width="match_parent" + android:layout_height="wrap_content" /> - </LinearLayout> + <View + android:id="@+id/divider" + android:layout_width="match_parent" + android:layout_height="1dp" + android:layout_gravity="bottom" + android:background="?android:attr/dividerHorizontal"/> + + </FrameLayout> </com.android.systemui.bubbles.BubbleExpandedViewContainer> diff --git a/packages/SystemUI/res/layout/bubble_permission_view.xml b/packages/SystemUI/res/layout/bubble_permission_view.xml new file mode 100644 index 000000000000..7fbb78a6feee --- /dev/null +++ b/packages/SystemUI/res/layout/bubble_permission_view.xml @@ -0,0 +1,85 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright 2019, 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. +--> +<LinearLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:animateLayoutChanges="true" + android:orientation="vertical" + android:paddingStart="@dimen/bubble_expanded_header_horizontal_padding" + android:paddingEnd="@dimen/bubble_expanded_header_horizontal_padding"> + + <!-- App info --> + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="horizontal" + android:layout_marginTop="@dimen/bubble_expanded_header_horizontal_padding" > + + <ImageView + android:id="@+id/pkgicon" + android:layout_width="@dimen/bubble_permission_icon_size" + android:layout_height="@dimen/bubble_permission_icon_size" + android:layout_centerVertical="true" + android:layout_marginEnd="3dp" + /> + + <TextView + android:id="@+id/pkgname" + android:layout_width="wrap_content" + android:layout_height="match_parent" + android:textAppearance="@*android:style/TextAppearance.Material.Body2" + android:layout_marginStart="3dp" + android:layout_marginEnd="2dp" + android:singleLine="true" + android:gravity="center_vertical" + android:layout_centerVertical="true" + /> + </LinearLayout> + + <!-- Actual permission --> + <TextView + android:id="@+id/prompt" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginTop="8dp" + android:text="@string/bubbles_prompt" + style="@*android:style/TextAppearance.Material.Body1" /> + + <!-- Buttons --> + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:gravity="end" + android:orientation="horizontal"> + + <TextView + android:id="@+id/no_bubbles_button" + android:text="@string/no_bubbles" + android:layout_width="wrap_content" + android:layout_height="match_parent" + android:background="@drawable/ripple_drawable" + style="@style/TextAppearance.NotificationInfo.Button"/> + <TextView + android:id="@+id/yes_bubbles_button" + android:text="@string/yes_bubbles" + android:layout_width="wrap_content" + android:layout_height="match_parent" + android:background="@drawable/ripple_drawable" + style="@style/TextAppearance.NotificationInfo.Button"/> + </LinearLayout> +</LinearLayout>
\ No newline at end of file diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index a5c8eafb8e8f..03445352e330 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -1041,4 +1041,6 @@ <dimen name="bubble_stack_starting_offset_y">100dp</dimen> <!-- Size of image buttons in the bubble header --> <dimen name="bubble_header_icon_size">48dp</dimen> + <!-- Size of the app icon shown in the bubble permission view --> + <dimen name="bubble_permission_icon_size">24dp</dimen> </resources> diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index 5365dcf72f11..db4a6cc1d704 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -2356,5 +2356,12 @@ <!-- Text used for content description of settings button in the header of expanded bubble view. [CHAR_LIMIT=NONE] --> <string name="bubbles_settings_button_description">Open notification settings for <xliff:g id="app_name" example="YouTube">%1$s</xliff:g></string> + <!-- Text for asking the user whether bubbles (floating app content) should be enabled for an + app. [CHAR LIMIT=NONE] --> + <string name="bubbles_prompt">Allow bubbles from this app?</string> + <!-- Text used for button allowing user to opt out of bubbles [CHAR LIMIT=20] --> + <string name="no_bubbles">Block</string> + <!-- Text used for button allowing user to approve / enable bubbles [CHAR LIMIT=20] --> + <string name="yes_bubbles">Allow</string> </resources> diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java index 9a9a52f40227..f36dca7fbade 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java @@ -70,7 +70,7 @@ import javax.inject.Singleton; * The controller manages addition, removal, and visible state of bubbles on screen. */ @Singleton -public class BubbleController { +public class BubbleController implements BubbleExpandedViewContainer.OnBubbleBlockedListener { private static final int MAX_BUBBLES = 5; // TODO: actually enforce this private static final String TAG = "BubbleController"; @@ -266,6 +266,7 @@ public class BubbleController { if (mExpandListener != null) { mStackView.setExpandListener(mExpandListener); } + mStackView.setOnBlockedListener(this); } // It's new BubbleView bubble = (BubbleView) mInflater.inflate( @@ -302,6 +303,19 @@ public class BubbleController { updateVisibility(); } + @Override + public void onBubbleBlocked(NotificationEntry entry) { + Object[] bubbles = mBubbles.values().toArray(); + for (int i = 0; i < bubbles.length; i++) { + NotificationEntry e = ((BubbleView) bubbles[i]).getEntry(); + boolean samePackage = entry.notification.getPackageName().equals( + e.notification.getPackageName()); + if (samePackage) { + removeBubble(entry.key); + } + } + } + @SuppressWarnings("FieldCanBeLocal") private final NotificationEntryListener mEntryListener = new NotificationEntryListener() { @Override diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedViewContainer.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedViewContainer.java index 67b18fdfc858..f08ba1936b40 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedViewContainer.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedViewContainer.java @@ -16,7 +16,10 @@ package com.android.systemui.bubbles; +import android.animation.LayoutTransition; +import android.animation.ObjectAnimator; import android.annotation.Nullable; +import android.app.INotificationManager; import android.app.Notification; import android.app.PendingIntent; import android.content.Context; @@ -26,16 +29,21 @@ import android.content.pm.PackageManager; import android.content.res.Resources; import android.content.res.TypedArray; import android.graphics.Color; +import android.graphics.drawable.Drawable; import android.graphics.drawable.ShapeDrawable; +import android.os.RemoteException; +import android.os.ServiceManager; import android.provider.Settings; -import android.text.TextUtils; import android.util.AttributeSet; import android.util.Log; import android.view.View; +import android.widget.FrameLayout; import android.widget.ImageButton; +import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.TextView; +import com.android.systemui.Interpolators; import com.android.systemui.R; import com.android.systemui.recents.TriangleShape; import com.android.systemui.statusbar.notification.collection.NotificationEntry; @@ -48,22 +56,31 @@ public class BubbleExpandedViewContainer extends LinearLayout implements View.On // The triangle pointing to the expanded view private View mPointerView; - // The view displayed between the pointer and the expanded view - private TextView mHeaderView; - // Tappable header icon deeplinking into the app + + // Header + private View mHeaderView; + private TextView mHeaderTextView; private ImageButton mDeepLinkIcon; - // Tappable header icon deeplinking into notification settings private ImageButton mSettingsIcon; + + // Permission view + private View mPermissionView; + // The view that is being displayed for the expanded state private View mExpandedView; private NotificationEntry mEntry; private PackageManager mPm; private String mAppName; + private Drawable mAppIcon; + + private INotificationManager mNotificationManagerService; // Need reference to let it know to collapse when new task is launched private BubbleStackView mStackView; + private OnBubbleBlockedListener mOnBubbleBlockedListener; + public BubbleExpandedViewContainer(Context context) { this(context, null); } @@ -80,6 +97,12 @@ public class BubbleExpandedViewContainer extends LinearLayout implements View.On int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); mPm = context.getPackageManager(); + try { + mNotificationManagerService = INotificationManager.Stub.asInterface( + ServiceManager.getServiceOrThrow(Context.NOTIFICATION_SERVICE)); + } catch (ServiceManager.ServiceNotFoundException e) { + Log.w(TAG, e); + } } @Override @@ -101,11 +124,39 @@ public class BubbleExpandedViewContainer extends LinearLayout implements View.On triangleDrawable.setTint(bgColor); mPointerView.setBackground(triangleDrawable); - mHeaderView = findViewById(R.id.header_text); + FrameLayout viewWrapper = findViewById(R.id.header_permission_wrapper); + LayoutTransition transition = new LayoutTransition(); + transition.setDuration(200); + + ObjectAnimator appearAnimator = ObjectAnimator.ofFloat(null, View.ALPHA, 0f, 1f); + transition.setAnimator(LayoutTransition.APPEARING, appearAnimator); + transition.setInterpolator(LayoutTransition.APPEARING, Interpolators.ALPHA_IN); + + ObjectAnimator disappearAnimator = ObjectAnimator.ofFloat(null, View.ALPHA, 1f, 0f); + transition.setAnimator(LayoutTransition.DISAPPEARING, disappearAnimator); + transition.setInterpolator(LayoutTransition.DISAPPEARING, Interpolators.ALPHA_OUT); + + transition.setAnimateParentHierarchy(false); + viewWrapper.setLayoutTransition(transition); + viewWrapper.getLayoutTransition().enableTransitionType(LayoutTransition.CHANGING); + + mHeaderView = findViewById(R.id.header_layout); + mHeaderTextView = findViewById(R.id.header_text); mDeepLinkIcon = findViewById(R.id.deep_link_button); mSettingsIcon = findViewById(R.id.settings_button); mDeepLinkIcon.setOnClickListener(this); mSettingsIcon.setOnClickListener(this); + + mPermissionView = findViewById(R.id.permission_layout); + findViewById(R.id.no_bubbles_button).setOnClickListener(this); + findViewById(R.id.yes_bubbles_button).setOnClickListener(this); + } + + /** + * Sets the listener to notify when a bubble has been blocked. + */ + public void setOnBlockedListener(OnBubbleBlockedListener listener) { + mOnBubbleBlockedListener = listener; } /** @@ -125,13 +176,17 @@ public class BubbleExpandedViewContainer extends LinearLayout implements View.On | PackageManager.MATCH_DIRECT_BOOT_AWARE); if (info != null) { mAppName = String.valueOf(mPm.getApplicationLabel(info)); + mAppIcon = mPm.getApplicationIcon(info); } } catch (PackageManager.NameNotFoundException e) { // Ahh... just use package name mAppName = entry.notification.getPackageName(); } - + if (mAppIcon == null) { + mAppIcon = mPm.getDefaultActivityIcon(); + } updateHeaderView(); + updatePermissionView(); } private void updateHeaderView() { @@ -140,11 +195,31 @@ public class BubbleExpandedViewContainer extends LinearLayout implements View.On mDeepLinkIcon.setContentDescription(getResources().getString( R.string.bubbles_deep_link_button_description, mAppName)); if (mEntry != null && mEntry.getBubbleMetadata() != null) { - setHeaderText(mEntry.getBubbleMetadata().getTitle()); + mHeaderTextView.setText(mEntry.getBubbleMetadata().getTitle()); } else { // This should only happen if we're auto-bubbling notification content that isn't // explicitly a bubble - setHeaderText(mAppName); + mHeaderTextView.setText(mAppName); + } + } + + private void updatePermissionView() { + boolean hasUserApprovedBubblesForPackage = false; + try { + hasUserApprovedBubblesForPackage = + mNotificationManagerService.hasUserApprovedBubblesForPackage( + mEntry.notification.getPackageName(), mEntry.notification.getUid()); + } catch (RemoteException e) { + Log.w(TAG, e); + } + if (hasUserApprovedBubblesForPackage) { + mHeaderView.setVisibility(VISIBLE); + mPermissionView.setVisibility(GONE); + } else { + mHeaderView.setVisibility(GONE); + mPermissionView.setVisibility(VISIBLE); + ((ImageView) mPermissionView.findViewById(R.id.pkgicon)).setImageDrawable(mAppIcon); + ((TextView) mPermissionView.findViewById(R.id.pkgname)).setText(mAppName); } } @@ -168,6 +243,27 @@ public class BubbleExpandedViewContainer extends LinearLayout implements View.On Intent intent = getSettingsIntent(mEntry.notification.getPackageName(), mEntry.notification.getUid()); mStackView.collapseStack(() -> mContext.startActivity(intent)); + } else if (id == R.id.no_bubbles_button) { + setBubblesAllowed(false); + } else if (id == R.id.yes_bubbles_button) { + setBubblesAllowed(true); + } + } + + private void setBubblesAllowed(boolean allowed) { + try { + mNotificationManagerService.setBubblesAllowed( + mEntry.notification.getPackageName(), + mEntry.notification.getUid(), + allowed); + if (allowed) { + mPermissionView.setVisibility(GONE); + mHeaderView.setVisibility(VISIBLE); + } else if (mOnBubbleBlockedListener != null) { + mOnBubbleBlockedListener.onBubbleBlocked(mEntry); + } + } catch (RemoteException e) { + Log.w(TAG, e); } } @@ -181,14 +277,6 @@ public class BubbleExpandedViewContainer extends LinearLayout implements View.On } /** - * Set the text displayed within the header. - */ - private void setHeaderText(CharSequence text) { - mHeaderView.setText(text); - mHeaderView.setVisibility(TextUtils.isEmpty(text) ? GONE : VISIBLE); - } - - /** * Set the view to display for the expanded state. Passing null will clear the view. */ public void setExpandedView(View view) { @@ -220,4 +308,14 @@ public class BubbleExpandedViewContainer extends LinearLayout implements View.On intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); return intent; } + + /** + * Listener that is notified when a bubble is blocked. + */ + public interface OnBubbleBlockedListener { + /** + * Called when a bubble is blocked for the provided entry. + */ + void onBubbleBlocked(NotificationEntry entry); + } } diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java index b6acd6385c34..afa9f02c6ee2 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java @@ -224,6 +224,13 @@ public class BubbleStackView extends FrameLayout implements BubbleTouchHandler.F } /** + * Sets the listener to notify when a bubble is blocked. + */ + public void setOnBlockedListener(BubbleExpandedViewContainer.OnBubbleBlockedListener listener) { + mExpandedViewContainer.setOnBlockedListener(listener); + } + + /** * Whether the stack of bubbles is expanded or not. */ public boolean isExpanded() { |