Add NotificationRemoteInputManager and associated tests.

This splits out several remote input related pieces of logic:
  1. Handling clicks on remote views
  2. Handling notifications kept for remote input
  3. Handling notifications to be removed on NotificationPresenter
      collapse.

Bug: 63874929
Bug: 62602530
Test: runtest systemui
Test: Compile and run
Change-Id: I7acd4bcb2ab7bde67d307408f509d3ca038eb3d4
This commit is contained in:
Eliot Courtney
2017-11-15 14:25:21 +09:00
parent d3616f7790
commit e77edea2f2
8 changed files with 687 additions and 338 deletions

View File

@@ -26,9 +26,11 @@ import com.android.internal.widget.LockPatternUtils;
import com.android.keyguard.ViewMediatorCallback;
import com.android.systemui.Dependency.DependencyProvider;
import com.android.systemui.keyguard.DismissCallbackRegistry;
import com.android.systemui.qs.QSTileHost;
import com.android.systemui.statusbar.KeyguardIndicationController;
import com.android.systemui.statusbar.NotificationGutsManager;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
import com.android.systemui.statusbar.NotificationRemoteInputManager;
import com.android.systemui.statusbar.ScrimView;
import com.android.systemui.statusbar.phone.DozeParameters;
import com.android.systemui.statusbar.phone.KeyguardBouncer;
@@ -36,9 +38,8 @@ import com.android.systemui.statusbar.phone.LightBarController;
import com.android.systemui.statusbar.phone.LockIcon;
import com.android.systemui.statusbar.phone.LockscreenWallpaper;
import com.android.systemui.statusbar.phone.NotificationIconAreaController;
import com.android.systemui.statusbar.phone.StatusBar;
import com.android.systemui.qs.QSTileHost;
import com.android.systemui.statusbar.phone.ScrimController;
import com.android.systemui.statusbar.phone.StatusBar;
import com.android.systemui.statusbar.phone.StatusBarIconController;
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
@@ -119,5 +120,8 @@ public class SystemUIFactory {
() -> new NotificationLockscreenUserManager(context));
providers.put(NotificationGutsManager.class, () -> new NotificationGutsManager(
Dependency.get(NotificationLockscreenUserManager.class), context));
providers.put(NotificationRemoteInputManager.class,
() -> new NotificationRemoteInputManager(
Dependency.get(NotificationLockscreenUserManager.class), context));
}
}

View File

@@ -37,10 +37,13 @@ public class NotificationListener extends NotificationListenerWithPlugins {
private static final String TAG = "NotificationListener";
private final NotificationPresenter mPresenter;
private final NotificationRemoteInputManager mRemoteInputManager;
private final Context mContext;
public NotificationListener(NotificationPresenter presenter, Context context) {
public NotificationListener(NotificationPresenter presenter,
NotificationRemoteInputManager remoteInputManager, Context context) {
mPresenter = presenter;
mRemoteInputManager = remoteInputManager;
mContext = context;
}
@@ -69,7 +72,7 @@ public class NotificationListener extends NotificationListenerWithPlugins {
mPresenter.getHandler().post(() -> {
processForRemoteInput(sbn.getNotification(), mContext);
String key = sbn.getKey();
mPresenter.getKeysKeptForRemoteInput().remove(key);
mRemoteInputManager.getKeysKeptForRemoteInput().remove(key);
boolean isUpdate = mPresenter.getNotificationData().get(key) != null;
// In case we don't allow child notifications, we ignore children of
// notifications that have a summary, since` we're not going to show them

View File

@@ -18,6 +18,7 @@ package com.android.systemui.statusbar;
import android.content.Intent;
import android.os.Handler;
import android.service.notification.NotificationListenerService;
import android.view.View;
import java.util.Set;
@@ -29,7 +30,7 @@ import java.util.Set;
* want to perform some action before doing so).
*/
public interface NotificationPresenter extends NotificationUpdateHandler,
NotificationData.Environment {
NotificationData.Environment, NotificationRemoteInputManager.Callback {
/**
* Returns true if the presenter is not visible. For example, it may not be necessary to do
@@ -80,14 +81,6 @@ public interface NotificationPresenter extends NotificationUpdateHandler,
*/
void onWorkChallengeChanged();
/**
* Notifications in this set are kept around when they were canceled in response to a remote
* input interaction. This allows us to show what you replied and allows you to continue typing
* into it.
*/
// TODO: Create NotificationEntryManager and move this method to there.
Set<String> getKeysKeptForRemoteInput();
/**
* Called when the current user changes.
* @param newUserId new user id
@@ -98,4 +91,20 @@ public interface NotificationPresenter extends NotificationUpdateHandler,
* Gets the NotificationLockscreenUserManager for this Presenter.
*/
NotificationLockscreenUserManager getNotificationLockscreenUserManager();
/**
* Wakes the device up if dozing.
*
* @param time the time when the request to wake up was issued
* @param where which view caused this wake up request
*/
void wakeUpIfDozing(long time, View where);
/**
* True if the device currently requires a PIN, pattern, or password to unlock.
*
* @param userId user id to query about
* @return true iff the device is locked
*/
boolean isDeviceLocked(int userId);
}

View File

@@ -0,0 +1,441 @@
/*
* Copyright (C) 2017 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.statusbar;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY;
import android.app.ActivityManager;
import android.app.PendingIntent;
import android.app.RemoteInput;
import android.content.Context;
import android.content.Intent;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.SystemClock;
import android.os.SystemProperties;
import android.os.UserManager;
import android.service.notification.StatusBarNotification;
import android.util.ArraySet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewParent;
import android.widget.RemoteViews;
import android.widget.TextView;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.statusbar.IStatusBarService;
import com.android.systemui.Dumpable;
import com.android.systemui.statusbar.policy.RemoteInputView;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.Set;
/**
* Class for handling remote input state over a set of notifications. This class handles things
* like keeping notifications temporarily that were cancelled as a response to a remote input
* interaction, keeping track of notifications to remove when NotificationPresenter is collapsed,
* and handling clicks on remote views.
*/
public class NotificationRemoteInputManager implements Dumpable {
public static final boolean ENABLE_REMOTE_INPUT =
SystemProperties.getBoolean("debug.enable_remote_input", true);
public static final boolean FORCE_REMOTE_INPUT_HISTORY =
SystemProperties.getBoolean("debug.force_remoteinput_history", true);
private static final boolean DEBUG = false;
private static final String TAG = "NotificationRemoteInputManager";
/**
* How long to wait before auto-dismissing a notification that was kept for remote input, and
* has now sent a remote input. We auto-dismiss, because the app may not see a reason to cancel
* these given that they technically don't exist anymore. We wait a bit in case the app issues
* an update.
*/
private static final int REMOTE_INPUT_KEPT_ENTRY_AUTO_CANCEL_DELAY = 200;
protected final ArraySet<NotificationData.Entry> mRemoteInputEntriesToRemoveOnCollapse =
new ArraySet<>();
protected final NotificationLockscreenUserManager mLockscreenUserManager;
/**
* Notifications with keys in this set are not actually around anymore. We kept them around
* when they were canceled in response to a remote input interaction. This allows us to show
* what you replied and allows you to continue typing into it.
*/
protected final ArraySet<String> mKeysKeptForRemoteInput = new ArraySet<>();
protected final Context mContext;
private final UserManager mUserManager;
protected RemoteInputController mRemoteInputController;
protected NotificationPresenter mPresenter;
protected IStatusBarService mBarService;
protected Callback mCallback;
private final RemoteViews.OnClickHandler mOnClickHandler = new RemoteViews.OnClickHandler() {
@Override
public boolean onClickHandler(
final View view, final PendingIntent pendingIntent, final Intent fillInIntent) {
mPresenter.wakeUpIfDozing(SystemClock.uptimeMillis(), view);
if (handleRemoteInput(view, pendingIntent)) {
return true;
}
if (DEBUG) {
Log.v(TAG, "Notification click handler invoked for intent: " + pendingIntent);
}
logActionClick(view);
// The intent we are sending is for the application, which
// won't have permission to immediately start an activity after
// the user switches to home. We know it is safe to do at this
// point, so make sure new activity switches are now allowed.
try {
ActivityManager.getService().resumeAppSwitches();
} catch (RemoteException e) {
}
return mCallback.handleRemoteViewClick(view, pendingIntent, fillInIntent,
() -> superOnClickHandler(view, pendingIntent, fillInIntent));
}
private void logActionClick(View view) {
ViewParent parent = view.getParent();
String key = getNotificationKeyForParent(parent);
if (key == null) {
Log.w(TAG, "Couldn't determine notification for click.");
return;
}
int index = -1;
// If this is a default template, determine the index of the button.
if (view.getId() == com.android.internal.R.id.action0 &&
parent != null && parent instanceof ViewGroup) {
ViewGroup actionGroup = (ViewGroup) parent;
index = actionGroup.indexOfChild(view);
}
try {
mBarService.onNotificationActionClick(key, index);
} catch (RemoteException e) {
// Ignore
}
}
private String getNotificationKeyForParent(ViewParent parent) {
while (parent != null) {
if (parent instanceof ExpandableNotificationRow) {
return ((ExpandableNotificationRow) parent)
.getStatusBarNotification().getKey();
}
parent = parent.getParent();
}
return null;
}
private boolean superOnClickHandler(View view, PendingIntent pendingIntent,
Intent fillInIntent) {
return super.onClickHandler(view, pendingIntent, fillInIntent,
WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY);
}
private boolean handleRemoteInput(View view, PendingIntent pendingIntent) {
if (mCallback.shouldHandleRemoteInput(view, pendingIntent)) {
return true;
}
Object tag = view.getTag(com.android.internal.R.id.remote_input_tag);
RemoteInput[] inputs = null;
if (tag instanceof RemoteInput[]) {
inputs = (RemoteInput[]) tag;
}
if (inputs == null) {
return false;
}
RemoteInput input = null;
for (RemoteInput i : inputs) {
if (i.getAllowFreeFormInput()) {
input = i;
}
}
if (input == null) {
return false;
}
ViewParent p = view.getParent();
RemoteInputView riv = null;
while (p != null) {
if (p instanceof View) {
View pv = (View) p;
if (pv.isRootNamespace()) {
riv = findRemoteInputView(pv);
break;
}
}
p = p.getParent();
}
ExpandableNotificationRow row = null;
while (p != null) {
if (p instanceof ExpandableNotificationRow) {
row = (ExpandableNotificationRow) p;
break;
}
p = p.getParent();
}
if (row == null) {
return false;
}
row.setUserExpanded(true);
if (!mLockscreenUserManager.shouldAllowLockscreenRemoteInput()) {
final int userId = pendingIntent.getCreatorUserHandle().getIdentifier();
if (mLockscreenUserManager.isLockscreenPublicMode(userId)) {
mCallback.onLockedRemoteInput(row, view);
return true;
}
if (mUserManager.getUserInfo(userId).isManagedProfile()
&& mPresenter.isDeviceLocked(userId)) {
mCallback.onLockedWorkRemoteInput(userId, row, view);
return true;
}
}
if (riv == null) {
riv = findRemoteInputView(row.getPrivateLayout().getExpandedChild());
if (riv == null) {
return false;
}
if (!row.getPrivateLayout().getExpandedChild().isShown()) {
mCallback.onMakeExpandedVisibleForRemoteInput(row, view);
return true;
}
}
int width = view.getWidth();
if (view instanceof TextView) {
// Center the reveal on the text which might be off-center from the TextView
TextView tv = (TextView) view;
if (tv.getLayout() != null) {
int innerWidth = (int) tv.getLayout().getLineWidth(0);
innerWidth += tv.getCompoundPaddingLeft() + tv.getCompoundPaddingRight();
width = Math.min(width, innerWidth);
}
}
int cx = view.getLeft() + width / 2;
int cy = view.getTop() + view.getHeight() / 2;
int w = riv.getWidth();
int h = riv.getHeight();
int r = Math.max(
Math.max(cx + cy, cx + (h - cy)),
Math.max((w - cx) + cy, (w - cx) + (h - cy)));
riv.setRevealParameters(cx, cy, r);
riv.setPendingIntent(pendingIntent);
riv.setRemoteInput(inputs, input);
riv.focusAnimated();
return true;
}
private RemoteInputView findRemoteInputView(View v) {
if (v == null) {
return null;
}
return (RemoteInputView) v.findViewWithTag(RemoteInputView.VIEW_TAG);
}
};
public NotificationRemoteInputManager(NotificationLockscreenUserManager lockscreenUserManager,
Context context) {
mLockscreenUserManager = lockscreenUserManager;
mContext = context;
mBarService = IStatusBarService.Stub.asInterface(
ServiceManager.getService(Context.STATUS_BAR_SERVICE));
mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
}
public void setUpWithPresenter(NotificationPresenter presenter,
Callback callback,
RemoteInputController.Delegate delegate) {
mPresenter = presenter;
mCallback = callback;
mRemoteInputController = new RemoteInputController(delegate);
mRemoteInputController.addCallback(new RemoteInputController.Callback() {
@Override
public void onRemoteInputSent(NotificationData.Entry entry) {
if (FORCE_REMOTE_INPUT_HISTORY && mKeysKeptForRemoteInput.contains(entry.key)) {
mPresenter.removeNotification(entry.key, null);
} else if (mRemoteInputEntriesToRemoveOnCollapse.contains(entry)) {
// We're currently holding onto this notification, but from the apps point of
// view it is already canceled, so we'll need to cancel it on the apps behalf
// after sending - unless the app posts an update in the mean time, so wait a
// bit.
mPresenter.getHandler().postDelayed(() -> {
if (mRemoteInputEntriesToRemoveOnCollapse.remove(entry)) {
mPresenter.removeNotification(entry.key, null);
}
}, REMOTE_INPUT_KEPT_ENTRY_AUTO_CANCEL_DELAY);
}
}
});
}
public RemoteInputController getController() {
return mRemoteInputController;
}
public void onUpdateNotification(NotificationData.Entry entry) {
mRemoteInputEntriesToRemoveOnCollapse.remove(entry);
}
/**
* Returns true if NotificationRemoteInputManager wants to keep this notification around.
*
* @param entry notification being removed
*/
public boolean onRemoveNotification(NotificationData.Entry entry) {
if (entry != null && mRemoteInputController.isRemoteInputActive(entry)
&& (entry.row != null && !entry.row.isDismissed())) {
mRemoteInputEntriesToRemoveOnCollapse.add(entry);
return true;
}
return false;
}
public void onPerformRemoveNotification(StatusBarNotification n,
NotificationData.Entry entry) {
if (mRemoteInputController.isRemoteInputActive(entry)) {
mRemoteInputController.removeRemoteInput(entry, null);
}
if (FORCE_REMOTE_INPUT_HISTORY
&& mKeysKeptForRemoteInput.contains(n.getKey())) {
mKeysKeptForRemoteInput.remove(n.getKey());
}
}
public void removeRemoteInputEntriesKeptUntilCollapsed() {
for (int i = 0; i < mRemoteInputEntriesToRemoveOnCollapse.size(); i++) {
NotificationData.Entry entry = mRemoteInputEntriesToRemoveOnCollapse.valueAt(i);
mRemoteInputController.removeRemoteInput(entry, null);
mPresenter.removeNotification(entry.key, mPresenter.getLatestRankingMap());
}
mRemoteInputEntriesToRemoveOnCollapse.clear();
}
public void checkRemoteInputOutside(MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_OUTSIDE // touch outside the source bar
&& event.getX() == 0 && event.getY() == 0 // a touch outside both bars
&& mRemoteInputController.isRemoteInputActive()) {
mRemoteInputController.closeRemoteInputs();
}
}
@Override
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
pw.println("NotificationRemoteInputManager state:");
pw.print(" mRemoteInputEntriesToRemoveOnCollapse: ");
pw.println(mRemoteInputEntriesToRemoveOnCollapse);
pw.print(" mKeysKeptForRemoteInput: ");
pw.println(mKeysKeptForRemoteInput);
}
public void bindRow(ExpandableNotificationRow row) {
row.setRemoteInputController(mRemoteInputController);
row.setRemoteViewClickHandler(mOnClickHandler);
}
public Set<String> getKeysKeptForRemoteInput() {
return mKeysKeptForRemoteInput;
}
@VisibleForTesting
public Set<NotificationData.Entry> getRemoteInputEntriesToRemoveOnCollapse() {
return mRemoteInputEntriesToRemoveOnCollapse;
}
/**
* Callback for various remote input related events, or for providing information that
* NotificationRemoteInputManager needs to know to decide what to do.
*/
public interface Callback {
/**
* Called when remote input was activated but the device is locked.
*
* @param row
* @param clicked
*/
void onLockedRemoteInput(ExpandableNotificationRow row, View clicked);
/**
* Called when remote input was activated but the device is locked and in a managed profile.
*
* @param userId
* @param row
* @param clicked
*/
void onLockedWorkRemoteInput(int userId, ExpandableNotificationRow row, View clicked);
/**
* Called when a row should be made expanded for the purposes of remote input.
*
* @param row
* @param clickedView
*/
void onMakeExpandedVisibleForRemoteInput(ExpandableNotificationRow row, View clickedView);
/**
* Return whether or not remote input should be handled for this view.
*
* @param view
* @param pendingIntent
* @return true iff the remote input should be handled
*/
boolean shouldHandleRemoteInput(View view, PendingIntent pendingIntent);
/**
* Performs any special handling for a remote view click. The default behaviour can be
* called through the defaultHandler parameter.
*
* @param view
* @param pendingIntent
* @param fillInIntent
* @param defaultHandler
* @return true iff the click was handled
*/
boolean handleRemoteViewClick(View view, PendingIntent pendingIntent, Intent fillInIntent,
ClickHandler defaultHandler);
}
/**
* Helper interface meant for passing the default on click behaviour to NotificationPresenter,
* so it may do its own handling before invoking the default behaviour.
*/
public interface ClickHandler {
/**
* Tries to handle a click on a remote view.
*
* @return true iff the click was handled
*/
boolean handleClick();
}
}

View File

@@ -28,6 +28,9 @@ import static com.android.systemui.statusbar.NotificationLockscreenUserManager
.NOTIFICATION_UNLOCKED_BY_WORK_CHALLENGE_ACTION;
import static com.android.systemui.statusbar.NotificationLockscreenUserManager.PERMISSION_SELF;
import static com.android.systemui.statusbar.NotificationMediaManager.DEBUG_MEDIA;
import static com.android.systemui.statusbar.NotificationRemoteInputManager.ENABLE_REMOTE_INPUT;
import static com.android.systemui.statusbar.NotificationRemoteInputManager
.FORCE_REMOTE_INPUT_HISTORY;
import static com.android.systemui.statusbar.notification.NotificationInflater.InflationCallback;
import static com.android.systemui.statusbar.phone.BarTransitions.MODE_LIGHTS_OUT;
import static com.android.systemui.statusbar.phone.BarTransitions.MODE_LIGHTS_OUT_TRANSPARENT;
@@ -47,7 +50,6 @@ import android.app.KeyguardManager;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.RemoteInput;
import android.app.StatusBarManager;
import android.app.TaskStackBuilder;
import android.app.WallpaperColors;
@@ -126,7 +128,6 @@ import android.view.accessibility.AccessibilityManager;
import android.view.animation.AccelerateInterpolator;
import android.widget.DateTimeView;
import android.widget.ImageView;
import android.widget.RemoteViews;
import android.widget.TextView;
import com.android.internal.annotations.VisibleForTesting;
@@ -204,6 +205,7 @@ import com.android.systemui.statusbar.NotificationListener;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
import com.android.systemui.statusbar.NotificationMediaManager;
import com.android.systemui.statusbar.NotificationPresenter;
import com.android.systemui.statusbar.NotificationRemoteInputManager;
import com.android.systemui.statusbar.NotificationShelf;
import com.android.systemui.statusbar.RemoteInputController;
import com.android.systemui.statusbar.ScrimView;
@@ -230,7 +232,6 @@ import com.android.systemui.statusbar.policy.KeyguardUserSwitcher;
import com.android.systemui.statusbar.policy.NetworkController;
import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener;
import com.android.systemui.statusbar.policy.PreviewInflater;
import com.android.systemui.statusbar.policy.RemoteInputView;
import com.android.systemui.statusbar.policy.UserInfoController;
import com.android.systemui.statusbar.policy.UserInfoControllerImpl;
import com.android.systemui.statusbar.policy.UserSwitcherController;
@@ -250,8 +251,8 @@ import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Stack;
import java.util.function.Function;
public class StatusBar extends SystemUI implements DemoMode,
DragDownHelper.DragDownCallback, ActivityStarter, OnUnlockMethodChangedListener,
@@ -262,12 +263,8 @@ public class StatusBar extends SystemUI implements DemoMode,
ColorExtractor.OnColorsChangedListener, ConfigurationListener, NotificationPresenter {
public static final boolean MULTIUSER_DEBUG = false;
public static final boolean ENABLE_REMOTE_INPUT =
SystemProperties.getBoolean("debug.enable_remote_input", true);
public static final boolean ENABLE_CHILD_NOTIFICATIONS
= SystemProperties.getBoolean("debug.child_notifs", true);
public static final boolean FORCE_REMOTE_INPUT_HISTORY =
SystemProperties.getBoolean("debug.force_remoteinput_history", true);
protected static final int MSG_HIDE_RECENT_APPS = 1020;
protected static final int MSG_PRELOAD_RECENT_APPS = 1022;
@@ -344,14 +341,6 @@ public class StatusBar extends SystemUI implements DemoMode,
/** If true, the lockscreen will show a distinct wallpaper */
private static final boolean ENABLE_LOCKSCREEN_WALLPAPER = true;
/**
* How long to wait before auto-dismissing a notification that was kept for remote input, and
* has now sent a remote input. We auto-dismiss, because the app may not see a reason to cancel
* these given that they technically don't exist anymore. We wait a bit in case the app issues
* an update.
*/
private static final int REMOTE_INPUT_KEPT_ENTRY_AUTO_CANCEL_DELAY = 200;
/**
* Never let the alpha become zero for surfaces that draw with SRC - otherwise the RenderNode
* won't draw anything and uninitialized memory will show through
@@ -540,6 +529,7 @@ public class StatusBar extends SystemUI implements DemoMode,
private NotificationMediaManager mMediaManager;
protected NotificationLockscreenUserManager mLockscreenUserManager;
protected NotificationRemoteInputManager mRemoteInputManager;
/** Keys of notifications currently visible to the user. */
private final ArraySet<NotificationVisibility> mCurrentlyVisibleNotifications =
@@ -724,6 +714,7 @@ public class StatusBar extends SystemUI implements DemoMode,
@Override
public void start() {
mRemoteInputManager = Dependency.get(NotificationRemoteInputManager.class);
mNetworkController = Dependency.get(NetworkController.class);
mUserSwitcherController = Dependency.get(UserSwitcherController.class);
mScreenLifecycle = Dependency.get(ScreenLifecycle.class);
@@ -778,7 +769,6 @@ public class StatusBar extends SystemUI implements DemoMode,
mRecents = getComponent(Recents.class);
mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
mKeyguardManager = (KeyguardManager) mContext.getSystemService(Context.KEYGUARD_SERVICE);
mLockPatternUtils = new LockPatternUtils(mContext);
@@ -818,7 +808,7 @@ public class StatusBar extends SystemUI implements DemoMode,
}
// Set up the initial notification state.
mNotificationListener = new NotificationListener(this, mContext);
mNotificationListener = new NotificationListener(this, mRemoteInputManager, mContext);
mNotificationListener.register();
if (DEBUG) {
@@ -1160,7 +1150,7 @@ public class StatusBar extends SystemUI implements DemoMode,
protected View.OnTouchListener getStatusBarWindowTouchListener() {
return (v, event) -> {
checkUserAutohide(event);
checkRemoteInputOutside(event);
mRemoteInputManager.checkRemoteInputOutside(event);
if (event.getAction() == MotionEvent.ACTION_DOWN) {
if (mExpandedVisible) {
animateCollapsePanels();
@@ -1433,31 +1423,7 @@ public class StatusBar extends SystemUI implements DemoMode,
mKeyguardIndicationController
.setStatusBarKeyguardViewManager(mStatusBarKeyguardViewManager);
mFingerprintUnlockController.setStatusBarKeyguardViewManager(mStatusBarKeyguardViewManager);
mRemoteInputController.addCallback(mStatusBarKeyguardViewManager);
mRemoteInputController.addCallback(new RemoteInputController.Callback() {
@Override
public void onRemoteInputSent(Entry entry) {
if (FORCE_REMOTE_INPUT_HISTORY && mKeysKeptForRemoteInput.contains(entry.key)) {
removeNotification(entry.key, null);
} else if (mRemoteInputEntriesToRemoveOnCollapse.contains(entry)) {
// We're currently holding onto this notification, but from the apps point of
// view it is already canceled, so we'll need to cancel it on the apps behalf
// after sending - unless the app posts an update in the mean time, so wait a
// bit.
mHandler.postDelayed(() -> {
if (mRemoteInputEntriesToRemoveOnCollapse.remove(entry)) {
removeNotification(entry.key, null);
}
}, REMOTE_INPUT_KEPT_ENTRY_AUTO_CANCEL_DELAY);
}
try {
mBarService.onNotificationDirectReplied(entry.key);
} catch (RemoteException e) {
// system process is dead if we're here.
}
}
});
mRemoteInputManager.getController().addCallback(mStatusBarKeyguardViewManager);
mKeyguardViewMediatorCallback = keyguardViewMediator.getViewMediatorCallback();
mLightBarController.setFingerprintUnlockController(mFingerprintUnlockController);
@@ -1634,7 +1600,7 @@ public class StatusBar extends SystemUI implements DemoMode,
// sending look longer than it takes.
// Also we should not defer the removal if reordering isn't allowed since otherwise
// some notifications can't disappear before the panel is closed.
boolean ignoreEarliestRemovalTime = mRemoteInputController.isSpinning(key)
boolean ignoreEarliestRemovalTime = mRemoteInputManager.getController().isSpinning(key)
&& !FORCE_REMOTE_INPUT_HISTORY
|| !mVisualStabilityManager.isReorderingAllowed();
deferRemoval = !mHeadsUpManager.removeNotification(key, ignoreEarliestRemovalTime);
@@ -1642,7 +1608,7 @@ public class StatusBar extends SystemUI implements DemoMode,
mMediaManager.onNotificationRemoved(key);
Entry entry = mNotificationData.get(key);
if (FORCE_REMOTE_INPUT_HISTORY && mRemoteInputController.isSpinning(key)
if (FORCE_REMOTE_INPUT_HISTORY && mRemoteInputManager.getController().isSpinning(key)
&& entry.row != null && !entry.row.isDismissed()) {
StatusBarNotification sbn = entry.notification;
@@ -1680,7 +1646,7 @@ public class StatusBar extends SystemUI implements DemoMode,
}
if (updated) {
Log.w(TAG, "Keeping notification around after sending remote input "+ entry.key);
mKeysKeptForRemoteInput.add(entry.key);
mRemoteInputManager.getKeysKeptForRemoteInput().add(entry.key);
return;
}
}
@@ -1690,12 +1656,11 @@ public class StatusBar extends SystemUI implements DemoMode,
return;
}
if (entry != null && mRemoteInputController.isRemoteInputActive(entry)
&& (entry.row != null && !entry.row.isDismissed())) {
if (mRemoteInputManager.onRemoveNotification(entry)) {
mLatestRankingMap = ranking;
mRemoteInputEntriesToRemoveOnCollapse.add(entry);
return;
}
if (entry != null && mGutsManager.getExposedGuts() != null
&& mGutsManager.getExposedGuts() == entry.row.getGuts()
&& entry.row.getGuts() != null && !entry.row.getGuts().isLeavebehind()) {
@@ -1769,9 +1734,7 @@ public class StatusBar extends SystemUI implements DemoMode,
protected void performRemoveNotification(StatusBarNotification n) {
Entry entry = mNotificationData.get(n.getKey());
if (mRemoteInputController.isRemoteInputActive(entry)) {
mRemoteInputController.removeRemoteInput(entry, null);
}
mRemoteInputManager.onPerformRemoveNotification(n, entry);
// start old BaseStatusBar.performRemoveNotification.
final String pkg = n.getPackageName();
final String tag = n.getTag();
@@ -1784,12 +1747,7 @@ public class StatusBar extends SystemUI implements DemoMode,
} else if (mStackScroller.hasPulsingNotifications()) {
dismissalSurface = NotificationStats.DISMISSAL_AOD;
}
mBarService.onNotificationClear(pkg, tag, id, userId, n.getKey(),
dismissalSurface);
if (FORCE_REMOTE_INPUT_HISTORY
&& mKeysKeptForRemoteInput.contains(n.getKey())) {
mKeysKeptForRemoteInput.remove(n.getKey());
}
mBarService.onNotificationClear(pkg, tag, id, userId, n.getKey(), dismissalSurface);
removeNotification(n.getKey(), null);
} catch (RemoteException ex) {
@@ -2508,7 +2466,7 @@ public class StatusBar extends SystemUI implements DemoMode,
mStatusBarWindowManager.setHeadsUpShowing(false);
mHeadsUpManager.setHeadsUpGoingAway(false);
}
removeRemoteInputEntriesKeptUntilCollapsed();
mRemoteInputManager.removeRemoteInputEntriesKeptUntilCollapsed();
});
}
}
@@ -2587,19 +2545,10 @@ public class StatusBar extends SystemUI implements DemoMode,
}
if (!isExpanded) {
removeRemoteInputEntriesKeptUntilCollapsed();
mRemoteInputManager.removeRemoteInputEntriesKeptUntilCollapsed();
}
}
private void removeRemoteInputEntriesKeptUntilCollapsed() {
for (int i = 0; i < mRemoteInputEntriesToRemoveOnCollapse.size(); i++) {
Entry entry = mRemoteInputEntriesToRemoveOnCollapse.valueAt(i);
mRemoteInputController.removeRemoteInput(entry, null);
removeNotification(entry.key, mLatestRankingMap);
}
mRemoteInputEntriesToRemoveOnCollapse.clear();
}
public NotificationStackScrollLayout getNotificationScrollLayout() {
return mStackScroller;
}
@@ -3183,19 +3132,12 @@ public class StatusBar extends SystemUI implements DemoMode,
if ((mSystemUiVisibility & STATUS_OR_NAV_TRANSIENT) != 0 // a transient bar is revealed
&& event.getAction() == MotionEvent.ACTION_OUTSIDE // touch outside the source bar
&& event.getX() == 0 && event.getY() == 0 // a touch outside both bars
&& !mRemoteInputController.isRemoteInputActive()) { // not due to typing in IME
&& !mRemoteInputManager.getController()
.isRemoteInputActive()) { // not due to typing in IME
userAutohide();
}
}
private void checkRemoteInputOutside(MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_OUTSIDE // touch outside the source bar
&& event.getX() == 0 && event.getY() == 0 // a touch outside both bars
&& mRemoteInputController.isRemoteInputActive()) {
mRemoteInputController.closeRemoteInputs();
}
}
private void userAutohide() {
cancelAutohide();
mHandler.postDelayed(mAutohide, 350); // longer than app gesture -> flag clear
@@ -3376,18 +3318,18 @@ public class StatusBar extends SystemUI implements DemoMode,
private void addStatusBarWindow() {
makeStatusBarView();
mStatusBarWindowManager = Dependency.get(StatusBarWindowManager.class);
mRemoteInputController = new RemoteInputController(new RemoteInputController.Delegate() {
public void setRemoteInputActive(NotificationData.Entry entry,
boolean remoteInputActive) {
mHeadsUpManager.setRemoteInputActive(entry, remoteInputActive);
}
public void lockScrollTo(NotificationData.Entry entry) {
mStackScroller.lockScrollTo(entry.row);
}
public void requestDisallowLongPressAndDismiss() {
mStackScroller.requestDisallowLongPress();
mStackScroller.requestDisallowDismiss();
}
mRemoteInputManager.setUpWithPresenter(this, this, new RemoteInputController.Delegate() {
public void setRemoteInputActive(NotificationData.Entry entry,
boolean remoteInputActive) {
mHeadsUpManager.setRemoteInputActive(entry, remoteInputActive);
}
public void lockScrollTo(NotificationData.Entry entry) {
mStackScroller.lockScrollTo(entry.row);
}
public void requestDisallowLongPressAndDismiss() {
mStackScroller.requestDisallowLongPress();
mStackScroller.requestDisallowDismiss();
}
});
mStatusBarWindowManager.add(mStatusBarWindow, getStatusBarHeight());
}
@@ -3507,8 +3449,8 @@ public class StatusBar extends SystemUI implements DemoMode,
String action = intent.getAction();
if (Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(action)) {
KeyboardShortcuts.dismiss();
if (mRemoteInputController != null) {
mRemoteInputController.closeRemoteInputs();
if (mRemoteInputManager.getController() != null) {
mRemoteInputManager.getController().closeRemoteInputs();
}
if (mLockscreenUserManager.isCurrentProfile(getSendingUserId())) {
int flags = CommandQueue.FLAG_EXCLUDE_NONE;
@@ -4549,7 +4491,7 @@ public class StatusBar extends SystemUI implements DemoMode,
clearNotificationEffects();
}
if (state == StatusBarState.KEYGUARD) {
removeRemoteInputEntriesKeptUntilCollapsed();
mRemoteInputManager.removeRemoteInputEntriesKeptUntilCollapsed();
maybeEscalateHeadsUp();
}
mState = state;
@@ -4753,13 +4695,15 @@ public class StatusBar extends SystemUI implements DemoMode,
dismissKeyguardThenExecute(dismissAction, true /* afterKeyguardGone */);
}
protected void onLockedRemoteInput(ExpandableNotificationRow row, View clicked) {
@Override
public void onLockedRemoteInput(ExpandableNotificationRow row, View clicked) {
mLeaveOpenOnKeyguardHide = true;
showBouncer();
mPendingRemoteInputView = clicked;
}
protected void onMakeExpandedVisibleForRemoteInput(ExpandableNotificationRow row,
@Override
public void onMakeExpandedVisibleForRemoteInput(ExpandableNotificationRow row,
View clickedView) {
if (isKeyguardShowing()) {
onLockedRemoteInput(row, clickedView);
@@ -4769,6 +4713,47 @@ public class StatusBar extends SystemUI implements DemoMode,
}
}
@Override
public boolean shouldHandleRemoteInput(View view, PendingIntent pendingIntent) {
// Skip remote input as doing so will expand the notification shade.
return (mDisabled2 & StatusBarManager.DISABLE2_NOTIFICATION_SHADE) != 0;
}
@Override
public boolean handleRemoteViewClick(View view, PendingIntent pendingIntent,
Intent fillInIntent, NotificationRemoteInputManager.ClickHandler defaultHandler) {
final boolean isActivity = pendingIntent.isActivity();
if (isActivity) {
final boolean afterKeyguardGone = PreviewInflater.wouldLaunchResolverActivity(
mContext, pendingIntent.getIntent(), mLockscreenUserManager.getCurrentUserId());
dismissKeyguardThenExecute(() -> {
try {
ActivityManager.getService().resumeAppSwitches();
} catch (RemoteException e) {
}
boolean handled = defaultHandler.handleClick();
// close the shade if it was open
if (handled && !mNotificationPanel.isFullyCollapsed()) {
animateCollapsePanels(
CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL, true /* force */);
visibilityChanged(false);
mAssistManager.hideAssist();
// Wait for activity start.
return true;
} else {
return false;
}
}, afterKeyguardGone);
return true;
} else {
return defaultHandler.handleClick();
}
}
protected boolean startWorkChallengeIfNecessary(int userId, IntentSender intendSender,
String notificationKey) {
// Clear pending remote view, as we do not want to trigger pending remote input view when
@@ -4805,7 +4790,8 @@ public class StatusBar extends SystemUI implements DemoMode,
// End old BaseStatusBar.startWorkChallengeIfNecessary.
}
protected void onLockedWorkRemoteInput(int userId, ExpandableNotificationRow row,
@Override
public void onLockedWorkRemoteInput(int userId, ExpandableNotificationRow row,
View clicked) {
// Collapse notification and show work challenge
animateCollapsePanels();
@@ -5031,6 +5017,7 @@ public class StatusBar extends SystemUI implements DemoMode,
return !mNotificationData.getActiveNotifications().isEmpty();
}
@Override
public void wakeUpIfDozing(long time, View where) {
if (mDozing) {
PowerManager pm = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
@@ -5044,6 +5031,11 @@ public class StatusBar extends SystemUI implements DemoMode,
}
}
@Override
public boolean isDeviceLocked(int userId) {
return mKeyguardManager.isDeviceLocked(userId);
}
@Override
public void appTransitionCancelled() {
EventBus.getDefault().send(new AppTransitionFinishedEvent());
@@ -5387,7 +5379,6 @@ public class StatusBar extends SystemUI implements DemoMode,
protected final NotificationGroupManager mGroupManager = new NotificationGroupManager();
protected RemoteInputController mRemoteInputController;
// for heads up notifications
protected HeadsUpManager mHeadsUpManager;
@@ -5404,14 +5395,6 @@ public class StatusBar extends SystemUI implements DemoMode,
protected boolean mVisible;
protected final ArraySet<Entry> mHeadsUpEntriesToRemoveOnSwitch = new ArraySet<>();
protected final ArraySet<Entry> mRemoteInputEntriesToRemoveOnCollapse = new ArraySet<>();
/**
* Notifications with keys in this set are not actually around anymore. We kept them around
* when they were canceled in response to a remote input interaction. This allows us to show
* what you replied and allows you to continue typing into it.
*/
protected final ArraySet<String> mKeysKeptForRemoteInput = new ArraySet<>();
// mScreenOnFromKeyguard && mVisible.
private boolean mVisibleToUser;
@@ -5423,8 +5406,6 @@ public class StatusBar extends SystemUI implements DemoMode,
protected PowerManager mPowerManager;
protected StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
private UserManager mUserManager;
protected KeyguardManager mKeyguardManager;
private LockPatternUtils mLockPatternUtils;
private DeviceProvisionedController mDeviceProvisionedController
@@ -5478,212 +5459,6 @@ public class StatusBar extends SystemUI implements DemoMode,
}
};
private final RemoteViews.OnClickHandler mOnClickHandler = new RemoteViews.OnClickHandler() {
@Override
public boolean onClickHandler(
final View view, final PendingIntent pendingIntent, final Intent fillInIntent) {
wakeUpIfDozing(SystemClock.uptimeMillis(), view);
if (handleRemoteInput(view, pendingIntent)) {
return true;
}
if (DEBUG) {
Log.v(TAG, "Notification click handler invoked for intent: " + pendingIntent);
}
logActionClick(view);
// The intent we are sending is for the application, which
// won't have permission to immediately start an activity after
// the user switches to home. We know it is safe to do at this
// point, so make sure new activity switches are now allowed.
try {
ActivityManager.getService().resumeAppSwitches();
} catch (RemoteException e) {
}
final boolean isActivity = pendingIntent.isActivity();
if (isActivity) {
final boolean afterKeyguardGone = PreviewInflater.wouldLaunchResolverActivity(
mContext, pendingIntent.getIntent(),
mLockscreenUserManager.getCurrentUserId());
dismissKeyguardThenExecute(() -> {
try {
ActivityManager.getService().resumeAppSwitches();
} catch (RemoteException e) {
}
boolean handled = superOnClickHandler(view, pendingIntent, fillInIntent);
// close the shade if it was open
if (handled && !mNotificationPanel.isFullyCollapsed()) {
animateCollapsePanels(
CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL, true /* force */);
visibilityChanged(false);
mAssistManager.hideAssist();
// Wait for activity start.
return true;
} else {
return false;
}
}, afterKeyguardGone);
return true;
} else {
return superOnClickHandler(view, pendingIntent, fillInIntent);
}
}
private void logActionClick(View view) {
ViewParent parent = view.getParent();
String key = getNotificationKeyForParent(parent);
if (key == null) {
Log.w(TAG, "Couldn't determine notification for click.");
return;
}
int index = -1;
// If this is a default template, determine the index of the button.
if (view.getId() == com.android.internal.R.id.action0 &&
parent != null && parent instanceof ViewGroup) {
ViewGroup actionGroup = (ViewGroup) parent;
index = actionGroup.indexOfChild(view);
}
try {
mBarService.onNotificationActionClick(key, index);
} catch (RemoteException e) {
// Ignore
}
}
private String getNotificationKeyForParent(ViewParent parent) {
while (parent != null) {
if (parent instanceof ExpandableNotificationRow) {
return ((ExpandableNotificationRow) parent).getStatusBarNotification().getKey();
}
parent = parent.getParent();
}
return null;
}
private boolean superOnClickHandler(View view, PendingIntent pendingIntent,
Intent fillInIntent) {
return super.onClickHandler(view, pendingIntent, fillInIntent,
WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY);
}
private boolean handleRemoteInput(View view, PendingIntent pendingIntent) {
if ((mDisabled2 & StatusBarManager.DISABLE2_NOTIFICATION_SHADE) != 0) {
// Skip remote input as doing so will expand the notification shade.
return true;
}
Object tag = view.getTag(com.android.internal.R.id.remote_input_tag);
RemoteInput[] inputs = null;
if (tag instanceof RemoteInput[]) {
inputs = (RemoteInput[]) tag;
}
if (inputs == null) {
return false;
}
RemoteInput input = null;
for (RemoteInput i : inputs) {
if (i.getAllowFreeFormInput()) {
input = i;
}
}
if (input == null) {
return false;
}
ViewParent p = view.getParent();
RemoteInputView riv = null;
while (p != null) {
if (p instanceof View) {
View pv = (View) p;
if (pv.isRootNamespace()) {
riv = findRemoteInputView(pv);
break;
}
}
p = p.getParent();
}
ExpandableNotificationRow row = null;
while (p != null) {
if (p instanceof ExpandableNotificationRow) {
row = (ExpandableNotificationRow) p;
break;
}
p = p.getParent();
}
if (row == null) {
return false;
}
row.setUserExpanded(true);
if (!mLockscreenUserManager.shouldAllowLockscreenRemoteInput()) {
final int userId = pendingIntent.getCreatorUserHandle().getIdentifier();
if (mLockscreenUserManager.isLockscreenPublicMode(userId)) {
onLockedRemoteInput(row, view);
return true;
}
if (mUserManager.getUserInfo(userId).isManagedProfile()
&& mKeyguardManager.isDeviceLocked(userId)) {
onLockedWorkRemoteInput(userId, row, view);
return true;
}
}
if (riv == null) {
riv = findRemoteInputView(row.getPrivateLayout().getExpandedChild());
if (riv == null) {
return false;
}
if (!row.getPrivateLayout().getExpandedChild().isShown()) {
onMakeExpandedVisibleForRemoteInput(row, view);
return true;
}
}
int width = view.getWidth();
if (view instanceof TextView) {
// Center the reveal on the text which might be off-center from the TextView
TextView tv = (TextView) view;
if (tv.getLayout() != null) {
int innerWidth = (int) tv.getLayout().getLineWidth(0);
innerWidth += tv.getCompoundPaddingLeft() + tv.getCompoundPaddingRight();
width = Math.min(width, innerWidth);
}
}
int cx = view.getLeft() + width / 2;
int cy = view.getTop() + view.getHeight() / 2;
int w = riv.getWidth();
int h = riv.getHeight();
int r = Math.max(
Math.max(cx + cy, cx + (h - cy)),
Math.max((w - cx) + cy, (w - cx) + (h - cy)));
riv.setRevealParameters(cx, cy, r);
riv.setPendingIntent(pendingIntent);
riv.setRemoteInput(inputs, input);
riv.focusAnimated();
return true;
}
private RemoteInputView findRemoteInputView(View v) {
if (v == null) {
return null;
}
return (RemoteInputView) v.findViewWithTag(RemoteInputView.VIEW_TAG);
}
};
private final BroadcastReceiver mBannerActionBroadcastReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
@@ -5928,12 +5703,11 @@ public class StatusBar extends SystemUI implements DemoMode,
row.setGroupManager(mGroupManager);
row.setHeadsUpManager(mHeadsUpManager);
row.setAboveShelfChangedListener(mAboveShelfObserver);
row.setRemoteInputController(mRemoteInputController);
row.setOnExpandClickListener(this);
row.setRemoteViewClickHandler(mOnClickHandler);
row.setInflationCallback(this);
row.setSecureStateProvider(this::isKeyguardCurrentlySecure);
row.setLongPressListener(getNotificationLongClicker());
mRemoteInputManager.bindRow(row);
// Get the app name.
// Note that Notification.Builder#bindHeaderAppName has similar logic
@@ -6395,7 +6169,8 @@ public class StatusBar extends SystemUI implements DemoMode,
return;
}
mHeadsUpEntriesToRemoveOnSwitch.remove(entry);
mRemoteInputEntriesToRemoveOnCollapse.remove(entry);
mRemoteInputManager.onUpdateNotification(entry);
if (key.equals(mGutsManager.getKeyToRemoveOnGutsClosed())) {
mGutsManager.setKeyToRemoveOnGutsClosed(null);
Log.w(TAG, "Notification that was kept for guts was updated. " + key);
@@ -6640,11 +6415,6 @@ public class StatusBar extends SystemUI implements DemoMode,
return mLatestRankingMap;
}
@Override
public Set<String> getKeysKeptForRemoteInput() {
return mKeysKeptForRemoteInput;
}
private final NotificationInfo.CheckSaveListener mCheckSaveListener =
(Runnable saveImportance, StatusBarNotification sbn) -> {
// If the user has security enabled, show challenge if the setting is changed.

View File

@@ -16,6 +16,8 @@
package com.android.systemui.statusbar.phone;
import static com.android.systemui.statusbar.NotificationRemoteInputManager.ENABLE_REMOTE_INPUT;
import android.app.ActivityManager;
import android.app.IActivityManager;
import android.content.Context;
@@ -156,7 +158,7 @@ public class StatusBarWindowManager implements RemoteInputController.Callback, D
private void applyFocusableFlag(State state) {
boolean panelFocusable = state.statusBarFocusable && state.panelExpanded;
if (state.bouncerShowing && (state.keyguardOccluded || state.keyguardNeedsInput)
|| StatusBar.ENABLE_REMOTE_INPUT && state.remoteInputActive) {
|| ENABLE_REMOTE_INPUT && state.remoteInputActive) {
mLpChanged.flags &= ~WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
mLpChanged.flags &= ~WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
} else if (state.isKeyguardShowingAndNotOccluded() || panelFocusable) {

View File

@@ -56,6 +56,7 @@ public class NotificationListenerTest extends SysuiTestCase {
private NotificationListenerService.RankingMap mRanking;
private Set<String> mKeysKeptForRemoteInput;
private NotificationData mNotificationData;
private NotificationRemoteInputManager mRemoteInputManager;
@Before
public void setUp() {
@@ -63,13 +64,14 @@ public class NotificationListenerTest extends SysuiTestCase {
mPresenter = mock(NotificationPresenter.class);
mNotificationData = mock(NotificationData.class);
mRanking = mock(NotificationListenerService.RankingMap.class);
mRemoteInputManager = mock(NotificationRemoteInputManager.class);
mKeysKeptForRemoteInput = new HashSet<>();
when(mPresenter.getHandler()).thenReturn(mHandler);
when(mPresenter.getNotificationData()).thenReturn(mNotificationData);
when(mPresenter.getKeysKeptForRemoteInput()).thenReturn(mKeysKeptForRemoteInput);
when(mRemoteInputManager.getKeysKeptForRemoteInput()).thenReturn(mKeysKeptForRemoteInput);
mListener = new NotificationListener(mPresenter, mContext);
mListener = new NotificationListener(mPresenter, mRemoteInputManager, mContext);
mSbn = new StatusBarNotification(TEST_PACKAGE_NAME, TEST_PACKAGE_NAME, 0, null, TEST_UID, 0,
new Notification(), UserHandle.CURRENT, null, 0);
}

View File

@@ -0,0 +1,118 @@
package com.android.systemui.statusbar;
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertTrue;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.app.Notification;
import android.content.Context;
import android.os.Handler;
import android.os.Looper;
import android.os.UserHandle;
import android.service.notification.NotificationListenerService;
import android.service.notification.StatusBarNotification;
import android.support.test.filters.SmallTest;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import com.android.systemui.SysuiTestCase;
import com.google.android.collect.Sets;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@SmallTest
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper
public class NotificationRemoteInputManagerTest extends SysuiTestCase {
private static final String TEST_PACKAGE_NAME = "test";
private static final int TEST_UID = 0;
private Handler mHandler;
private TestableNotificationRemoteInputManager mRemoteInputManager;
private StatusBarNotification mSbn;
private NotificationData.Entry mEntry;
@Mock private NotificationPresenter mPresenter;
@Mock private RemoteInputController.Delegate mDelegate;
@Mock private NotificationLockscreenUserManager mLockscreenUserManager;
@Mock private NotificationRemoteInputManager.Callback mCallback;
@Mock private RemoteInputController mController;
@Mock private NotificationListenerService.RankingMap mRanking;
@Mock private ExpandableNotificationRow mRow;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
mHandler = new Handler(Looper.getMainLooper());
when(mPresenter.getHandler()).thenReturn(mHandler);
when(mPresenter.getLatestRankingMap()).thenReturn(mRanking);
mRemoteInputManager = new TestableNotificationRemoteInputManager(mLockscreenUserManager,
mContext);
mSbn = new StatusBarNotification(TEST_PACKAGE_NAME, TEST_PACKAGE_NAME, 0, null, TEST_UID,
0, new Notification(), UserHandle.CURRENT, null, 0);
mEntry = new NotificationData.Entry(mSbn);
mEntry.row = mRow;
mRemoteInputManager.setUpWithPresenterForTest(mPresenter, mCallback, mDelegate,
mController);
}
@Test
public void testOnRemoveNotificationNotKept() {
assertFalse(mRemoteInputManager.onRemoveNotification(mEntry));
assertTrue(mRemoteInputManager.getRemoteInputEntriesToRemoveOnCollapse().isEmpty());
}
@Test
public void testOnRemoveNotificationKept() {
when(mController.isRemoteInputActive(mEntry)).thenReturn(true);
assertTrue(mRemoteInputManager.onRemoveNotification(mEntry));
assertTrue(mRemoteInputManager.getRemoteInputEntriesToRemoveOnCollapse().equals(
Sets.newArraySet(mEntry)));
}
@Test
public void testPerformOnRemoveNotification() {
when(mController.isRemoteInputActive(mEntry)).thenReturn(true);
mRemoteInputManager.getKeysKeptForRemoteInput().add(mEntry.key);
mRemoteInputManager.onPerformRemoveNotification(mSbn, mEntry);
verify(mController).removeRemoteInput(mEntry, null);
assertTrue(mRemoteInputManager.getKeysKeptForRemoteInput().isEmpty());
}
@Test
public void testRemoveRemoteInputEntriesKeptUntilCollapsed() {
mRemoteInputManager.getRemoteInputEntriesToRemoveOnCollapse().add(mEntry);
mRemoteInputManager.removeRemoteInputEntriesKeptUntilCollapsed();
assertTrue(mRemoteInputManager.getRemoteInputEntriesToRemoveOnCollapse().isEmpty());
verify(mController).removeRemoteInput(mEntry, null);
verify(mPresenter).removeNotification(mEntry.key, mRanking);
}
private class TestableNotificationRemoteInputManager extends NotificationRemoteInputManager {
public TestableNotificationRemoteInputManager(
NotificationLockscreenUserManager lockscreenUserManager, Context context) {
super(lockscreenUserManager, context);
}
public void setUpWithPresenterForTest(NotificationPresenter presenter,
Callback callback,
RemoteInputController.Delegate delegate,
RemoteInputController controller) {
super.setUpWithPresenter(presenter, callback, delegate);
mRemoteInputController = controller;
}
}
}