Remove bubble screenshot menu.

Test: Manual. Bubble menu no longer appears when long-pressing bubbles with "allow_bubble_screenshot_menu" enabled.

Bug: 144113115

Change-Id: I64b79e0b44e326e8a037ea20cd2cb9be46e1cd8d
This commit is contained in:
Aran Ink
2020-02-06 13:11:53 -05:00
parent 70ee4c46a6
commit c4381c4717
6 changed files with 7 additions and 303 deletions

View File

@@ -44,19 +44,13 @@ import static java.lang.annotation.RetentionPolicy.SOURCE;
import android.annotation.UserIdInt;
import android.app.ActivityManager.RunningTaskInfo;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.RemoteInput;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
import android.content.pm.ShortcutManager;
import android.content.res.Configuration;
import android.graphics.Rect;
import android.net.Uri;
import android.os.Handler;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.service.notification.NotificationListenerService.RankingMap;
@@ -75,7 +69,6 @@ import androidx.annotation.Nullable;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.statusbar.IStatusBarService;
import com.android.internal.util.ScreenshotHelper;
import com.android.systemui.R;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.shared.system.ActivityManagerWrapper;
@@ -93,7 +86,6 @@ import com.android.systemui.statusbar.phone.NotificationShadeWindowController;
import com.android.systemui.statusbar.phone.ShadeController;
import com.android.systemui.statusbar.phone.StatusBar;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.RemoteInputUriController;
import com.android.systemui.statusbar.policy.ZenModeController;
import java.io.FileDescriptor;
@@ -101,10 +93,8 @@ import java.io.PrintWriter;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.function.Consumer;
import javax.inject.Inject;
import javax.inject.Singleton;
@@ -146,8 +136,6 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
@Nullable private BubbleStackView.SurfaceSynchronizer mSurfaceSynchronizer;
private final NotificationGroupManager mNotificationGroupManager;
private final ShadeController mShadeController;
private final RemoteInputUriController mRemoteInputUriController;
private Handler mHandler = new Handler() {};
private BubbleData mBubbleData;
@Nullable private BubbleStackView mStackView;
@@ -171,7 +159,6 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
private final NotificationShadeWindowController mNotificationShadeWindowController;
private final ZenModeController mZenModeController;
private StatusBarStateListener mStatusBarStateListener;
private final ScreenshotHelper mScreenshotHelper;
// Callback that updates BubbleOverflowActivity on data change.
@Nullable private Runnable mOverflowCallback = null;
@@ -216,16 +203,6 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
void onBubbleExpandChanged(boolean isExpanding, String key);
}
/**
* Listener for handling bubble screenshot events.
*/
public interface BubbleScreenshotListener {
/**
* Called to trigger taking a screenshot and sending the result to a bubble.
*/
void onBubbleScreenshot(Bubble bubble);
}
/**
* Listener to be notified when a bubbles' notification suppression state changes.
*/
@@ -300,12 +277,10 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
ZenModeController zenModeController,
NotificationLockscreenUserManager notifUserManager,
NotificationGroupManager groupManager,
NotificationEntryManager entryManager,
RemoteInputUriController remoteInputUriController) {
NotificationEntryManager entryManager) {
this(context, notificationShadeWindowController, statusBarStateController, shadeController,
data, null /* synchronizer */, configurationController, interruptionStateProvider,
zenModeController, notifUserManager, groupManager, entryManager,
remoteInputUriController);
zenModeController, notifUserManager, groupManager, entryManager);
}
public BubbleController(Context context,
@@ -319,14 +294,12 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
ZenModeController zenModeController,
NotificationLockscreenUserManager notifUserManager,
NotificationGroupManager groupManager,
NotificationEntryManager entryManager,
RemoteInputUriController remoteInputUriController) {
NotificationEntryManager entryManager) {
mContext = context;
mShadeController = shadeController;
mNotificationInterruptionStateProvider = interruptionStateProvider;
mNotifUserManager = notifUserManager;
mZenModeController = zenModeController;
mRemoteInputUriController = remoteInputUriController;
mZenModeController.addCallback(new ZenModeController.Callback() {
@Override
public void onZenChanged(int zen) {
@@ -399,7 +372,6 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
mUserCreatedBubbles = new HashSet<>();
mUserBlockedBubbles = new HashSet<>();
mScreenshotHelper = new ScreenshotHelper(context);
mBubbleIconFactory = new BubbleIconFactory(context);
}
@@ -536,9 +508,6 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
if (mExpandListener != null) {
mStackView.setExpandListener(mExpandListener);
}
if (mBubbleScreenshotListener != null) {
mStackView.setBubbleScreenshotListener(mBubbleScreenshotListener);
}
}
}
@@ -1267,68 +1236,4 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
}
}
}
// TODO: Copied from RemoteInputView. Consolidate RemoteInput intent logic.
private Intent prepareRemoteInputFromData(String contentType, Uri data,
RemoteInput remoteInput, NotificationEntry entry) {
HashMap<String, Uri> results = new HashMap<>();
results.put(contentType, data);
mRemoteInputUriController.grantInlineReplyUriPermission(entry.getSbn(), data);
Intent fillInIntent = new Intent().addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
RemoteInput.addDataResultToIntent(remoteInput, fillInIntent, results);
return fillInIntent;
}
// TODO: Copied from RemoteInputView. Consolidate RemoteInput intent logic.
private void sendRemoteInput(Intent intent, NotificationEntry entry,
PendingIntent pendingIntent) {
// Tell ShortcutManager that this package has been "activated". ShortcutManager
// will reset the throttling for this package.
// Strictly speaking, the intent receiver may be different from the notification publisher,
// but that's an edge case, and also because we can't always know which package will receive
// an intent, so we just reset for the publisher.
mContext.getSystemService(ShortcutManager.class).onApplicationActive(
entry.getSbn().getPackageName(),
entry.getSbn().getUser().getIdentifier());
try {
pendingIntent.send(mContext, 0, intent);
} catch (PendingIntent.CanceledException e) {
Log.i(TAG, "Unable to send remote input result", e);
}
}
private void sendScreenshotToBubble(Bubble bubble) {
mScreenshotHelper.takeScreenshot(
android.view.WindowManager.TAKE_SCREENSHOT_FULLSCREEN,
true /* hasStatus */,
true /* hasNav */,
mHandler,
new Consumer<Uri>() {
@Override
public void accept(Uri uri) {
if (uri != null) {
NotificationEntry entry = bubble.getEntry();
Pair<RemoteInput, Notification.Action> pair = entry.getSbn()
.getNotification().findRemoteInputActionPair(false);
if (pair != null) {
RemoteInput remoteInput = pair.first;
Notification.Action action = pair.second;
Intent dataIntent = prepareRemoteInputFromData("image/png", uri,
remoteInput, entry);
sendRemoteInput(dataIntent, entry, action.actionIntent);
mBubbleData.setSelectedBubble(bubble);
mBubbleData.setExpanded(true);
} else {
Log.w(TAG, "No RemoteInput found for notification: "
+ entry.getSbn().getKey());
}
}
}
});
}
private final BubbleScreenshotListener mBubbleScreenshotListener =
bubble -> sendScreenshotToBubble(bubble);
}

View File

@@ -73,9 +73,6 @@ public class BubbleExperimentConfig {
private static final String WHITELISTED_AUTO_BUBBLE_APPS = "whitelisted_auto_bubble_apps";
private static final String ALLOW_BUBBLE_MENU = "allow_bubble_screenshot_menu";
private static final boolean ALLOW_BUBBLE_MENU_DEFAULT = false;
private static final String ALLOW_BUBBLE_OVERFLOW = "allow_bubble_overflow";
private static final boolean ALLOW_BUBBLE_OVERFLOW_DEFAULT = false;
@@ -133,16 +130,6 @@ public class BubbleExperimentConfig {
return false;
}
/**
* When true, show a menu when a bubble is long-pressed, which will allow the user to take
* actions on that bubble.
*/
static boolean allowBubbleScreenshotMenu(Context context) {
return Settings.Secure.getInt(context.getContentResolver(),
ALLOW_BUBBLE_MENU,
ALLOW_BUBBLE_MENU_DEFAULT ? 1 : 0) != 0;
}
/**
* When true, show a menu when a bubble is long-pressed, which will allow the user to take
* actions on that bubble.

View File

@@ -1,84 +0,0 @@
/*
* Copyright (C) 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.
*/
package com.android.systemui.bubbles;
import android.content.Context;
import android.util.AttributeSet;
import android.view.View;
import android.widget.FrameLayout;
import android.widget.ImageView;
import com.android.systemui.R;
/**
* Menu which allows users to take actions on bubbles, ex. screenshots.
*/
public class BubbleMenuView extends FrameLayout {
private FrameLayout mMenu;
private boolean mShowing = false;
/** Delay before taking a screenshot once the button is tapped to allow the menu time to hide.*/
public static final long SCREENSHOT_DELAY = 200;
public BubbleMenuView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public BubbleMenuView(Context context) {
super(context);
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
mMenu = findViewById(R.id.bubble_menu_view);
ImageView icon = findViewById(com.android.internal.R.id.icon);
icon.setImageDrawable(mContext.getDrawable(com.android.internal.R.drawable.ic_screenshot));
}
/**
* Get the bubble menu view.
*/
public View getMenuView() {
return mMenu;
}
/**
* Checks whether the bubble menu is currently displayed.
*/
public boolean isShowing() {
return mShowing;
}
/**
* Show the bubble menu at the specified position on the screen.
*/
public void show(float x, float y) {
mShowing = true;
this.setVisibility(VISIBLE);
mMenu.setTranslationX(x);
mMenu.setTranslationY(y);
}
/**
* Hide the bubble menu.
*/
public void hide() {
mShowing = false;
this.setVisibility(GONE);
}
}

View File

@@ -115,7 +115,6 @@ public class BubbleStackView extends FrameLayout {
/** How long to wait, in milliseconds, before hiding the flyout. */
@VisibleForTesting
static final int FLYOUT_HIDE_AFTER = 5000;
private BubbleController.BubbleScreenshotListener mBubbleScreenshotListener;
/**
* Interface to synchronize {@link View} state and the screen.
@@ -169,7 +168,6 @@ public class BubbleStackView extends FrameLayout {
private ExpandedAnimationController mExpandedAnimationController;
private FrameLayout mExpandedViewContainer;
@Nullable private BubbleMenuView mBubbleMenuView;
private BubbleFlyoutView mFlyout;
/** Runnable that fades out the flyout and then sets it to GONE. */
@@ -516,9 +514,6 @@ public class BubbleStackView extends FrameLayout {
mDesaturateAndDarkenPaint.setColorFilter(new ColorMatrixColorFilter(animatedMatrix));
mDesaturateAndDarkenTargetView.setLayerPaint(mDesaturateAndDarkenPaint);
});
mInflater.inflate(R.layout.bubble_menu_view, this);
mBubbleMenuView = findViewById(R.id.bubble_menu_container);
}
private void setUpOverflow() {
@@ -741,13 +736,6 @@ public class BubbleStackView extends FrameLayout {
mExpandListener = listener;
}
/**
* Sets the screenshot listener.
*/
public void setBubbleScreenshotListener(BubbleController.BubbleScreenshotListener listener) {
mBubbleScreenshotListener = listener;
}
/**
* Whether the stack of bubbles is expanded or not.
*/
@@ -942,12 +930,6 @@ public class BubbleStackView extends FrameLayout {
public View getTargetView(MotionEvent event) {
float x = event.getRawX();
float y = event.getRawY();
if (mBubbleMenuView.isShowing()) {
if (isIntersecting(mBubbleMenuView.getMenuView(), x, y)) {
return mBubbleMenuView;
}
return null;
}
if (mIsExpanded) {
if (isIntersecting(mBubbleContainer, x, y)) {
// Could be tapping or dragging a bubble while expanded
@@ -1164,7 +1146,6 @@ public class BubbleStackView extends FrameLayout {
}
return;
}
hideBubbleMenu();
mStackAnimationController.cancelStackPositionAnimations();
mBubbleContainer.setActiveController(mStackAnimationController);
hideFlyoutImmediate();
@@ -1566,10 +1547,6 @@ public class BubbleStackView extends FrameLayout {
@Override
public void getBoundsOnScreen(Rect outRect) {
// If the bubble menu is open, the entire screen should capture touch events.
if (mBubbleMenuView.isShowing()) {
outRect.set(0, 0, getWidth(), getHeight());
return;
}
if (!mIsExpanded) {
if (getBubbleCount() > 0) {
mBubbleContainer.getChildAt(0).getBoundsOnScreen(outRect);
@@ -1804,50 +1781,4 @@ public class BubbleStackView extends FrameLayout {
}
return bubbles;
}
/**
* Show the bubble menu, positioned relative to the stack.
*/
public void showBubbleMenu() {
PointF currentPos = mStackAnimationController.getStackPosition();
mBubbleMenuView.setVisibility(View.INVISIBLE);
post(() -> {
float yPos = currentPos.y;
float xPos = currentPos.x;
if (mStackAnimationController.isStackOnLeftSide()) {
xPos += mBubbleSize;
} else {
xPos -= mBubbleMenuView.getMenuView().getWidth();
}
mBubbleMenuView.show(xPos, yPos);
});
}
/**
* Hide the bubble menu.
*/
public void hideBubbleMenu() {
mBubbleMenuView.hide();
}
/**
* Determines whether the bubble menu is currently showing.
*/
public boolean isShowingBubbleMenu() {
return mBubbleMenuView.isShowing();
}
/**
* Take a screenshot and send it to the specified bubble.
*/
public void sendScreenshotToBubble(Bubble bubble) {
hideBubbleMenu();
// delay allows the bubble menu to disappear before the screenshot
// done here because we already have a Handler to delay with.
// TODO: Hide bubble + menu UI from screenshots entirely instead of just delaying.
postDelayed(() -> {
mBubbleScreenshotListener.onBubbleScreenshot(bubble);
}, BubbleMenuView.SCREENSHOT_DELAY);
}
}

View File

@@ -57,14 +57,12 @@ class BubbleTouchHandler implements View.OnTouchListener {
private final PointF mViewPositionOnTouchDown = new PointF();
private final BubbleStackView mStack;
private final BubbleData mBubbleData;
private final Context mContext;
private BubbleController mController = Dependency.get(BubbleController.class);
private boolean mMovedEnough;
private int mTouchSlopSquared;
private VelocityTracker mVelocityTracker;
private Runnable mShowBubbleMenuRunnable;
/** View that was initially touched, when we received the first ACTION_DOWN event. */
private View mTouchedView;
@@ -77,7 +75,6 @@ class BubbleTouchHandler implements View.OnTouchListener {
mTouchSlopSquared = touchSlop * touchSlop;
mBubbleData = bubbleData;
mStack = stackView;
mContext = context;
}
@Override
@@ -94,24 +91,15 @@ class BubbleTouchHandler implements View.OnTouchListener {
// anything, collapse the stack.
if (action == MotionEvent.ACTION_OUTSIDE || mTouchedView == null) {
mBubbleData.setExpanded(false);
mStack.hideBubbleMenu();
resetForNextGesture();
return false;
}
if (mTouchedView instanceof BubbleMenuView) {
mStack.hideBubbleMenu();
resetForNextGesture();
mStack.sendScreenshotToBubble(mBubbleData.getSelectedBubble());
return false;
}
if (!(mTouchedView instanceof BadgedImageView)
&& !(mTouchedView instanceof BubbleStackView)
&& !(mTouchedView instanceof BubbleFlyoutView)) {
// Not touching anything touchable, but we shouldn't collapse (e.g. touching edge
// of expanded view).
mStack.hideBubbleMenu();
resetForNextGesture();
return false;
}
@@ -134,12 +122,6 @@ class BubbleTouchHandler implements View.OnTouchListener {
if (isStack) {
mViewPositionOnTouchDown.set(mStack.getStackPosition());
mStack.onDragStart();
if (!mStack.isShowingBubbleMenu() && !mStack.isExpanded()
&& BubbleExperimentConfig.allowBubbleScreenshotMenu(mContext)) {
mShowBubbleMenuRunnable = mStack::showBubbleMenu;
mStack.postDelayed(mShowBubbleMenuRunnable,
ViewConfiguration.getLongPressTimeout());
}
} else if (isFlyout) {
mStack.onFlyoutDragStart();
} else {
@@ -150,10 +132,6 @@ class BubbleTouchHandler implements View.OnTouchListener {
break;
case MotionEvent.ACTION_MOVE:
// block all further touch inputs once the menu is open
if (mStack.isShowingBubbleMenu()) {
return true;
}
trackMovement(event);
final float deltaX = rawX - mTouchDown.x;
final float deltaY = rawY - mTouchDown.y;
@@ -163,7 +141,6 @@ class BubbleTouchHandler implements View.OnTouchListener {
}
if (mMovedEnough) {
mStack.removeCallbacks(mShowBubbleMenuRunnable);
if (isStack) {
mStack.onDragged(viewX, viewY);
} else if (isFlyout) {
@@ -194,12 +171,6 @@ class BubbleTouchHandler implements View.OnTouchListener {
break;
case MotionEvent.ACTION_UP:
if (mStack.isShowingBubbleMenu()) {
resetForNextGesture();
return true;
} else {
mStack.removeCallbacks(mShowBubbleMenuRunnable);
}
trackMovement(event);
mVelocityTracker.computeCurrentVelocity(/* maxVelocity */ 1000);
final float velX = mVelocityTracker.getXVelocity();

View File

@@ -81,7 +81,6 @@ import com.android.systemui.statusbar.phone.ShadeController;
import com.android.systemui.statusbar.policy.BatteryController;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.HeadsUpManager;
import com.android.systemui.statusbar.policy.RemoteInputUriController;
import com.android.systemui.statusbar.policy.ZenModeController;
import com.android.systemui.util.InjectionInflationController;
@@ -152,8 +151,6 @@ public class BubbleControllerTest extends SysuiTestCase {
@Mock
private ShadeController mShadeController;
@Mock
private RemoteInputUriController mRemoteInputUriController;
@Mock
private NotificationRowComponent mNotificationRowComponent;
private SuperStatusBarViewFactory mSuperStatusBarViewFactory;
@@ -226,8 +223,7 @@ public class BubbleControllerTest extends SysuiTestCase {
mZenModeController,
mLockscreenUserManager,
mNotificationGroupManager,
mNotificationEntryManager,
mRemoteInputUriController);
mNotificationEntryManager);
mBubbleController.setBubbleStateChangeListener(mBubbleStateChangeListener);
mBubbleController.setExpandListener(mBubbleExpandListener);
@@ -783,13 +779,11 @@ public class BubbleControllerTest extends SysuiTestCase {
ZenModeController zenModeController,
NotificationLockscreenUserManager lockscreenUserManager,
NotificationGroupManager groupManager,
NotificationEntryManager entryManager,
RemoteInputUriController remoteInputUriController) {
NotificationEntryManager entryManager) {
super(context,
notificationShadeWindowController, statusBarStateController, shadeController,
data, Runnable::run, configurationController, interruptionStateProvider,
zenModeController, lockscreenUserManager, groupManager, entryManager,
remoteInputUriController);
zenModeController, lockscreenUserManager, groupManager, entryManager);
setInflateSynchronously(true);
}
}
@@ -806,7 +800,7 @@ public class BubbleControllerTest extends SysuiTestCase {
}
/**
* Sets the bubble metadata flags for this entry. These flags are normally set by
* Sets the bubble metadata flags for this entry. These ]flags are normally set by
* NotificationManagerService when the notification is sent, however, these tests do not
* go through that path so we set them explicitly when testing.
*/