diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java index f28096f083c23..40d00edd96aa1 100644 --- a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java +++ b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java @@ -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)); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java index 6bcd174adeae4..4952da4b1fecc 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java @@ -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 diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationPresenter.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationPresenter.java index 4eca2415d5c30..33c72534b1b69 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationPresenter.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationPresenter.java @@ -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 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); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java new file mode 100644 index 0000000000000..7827f62970e79 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java @@ -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 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 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 getKeysKeptForRemoteInput() { + return mKeysKeptForRemoteInput; + } + + @VisibleForTesting + public Set 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(); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java index 80bab7293a8be..736eb7a0a271b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java @@ -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 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 mHeadsUpEntriesToRemoveOnSwitch = new ArraySet<>(); - protected final ArraySet 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 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 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. diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowManager.java index ed96b41158885..b0b5b8ec1e09a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowManager.java @@ -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) { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationListenerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationListenerTest.java index 6ecfe3e6be47d..f562340664c82 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationListenerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationListenerTest.java @@ -56,6 +56,7 @@ public class NotificationListenerTest extends SysuiTestCase { private NotificationListenerService.RankingMap mRanking; private Set 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); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationRemoteInputManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationRemoteInputManagerTest.java new file mode 100644 index 0000000000000..b881c098a1a31 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationRemoteInputManagerTest.java @@ -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; + } + } +}