Loading packages/SystemUI/src/com/android/systemui/SystemUIFactory.java +6 −2 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading Loading @@ -115,5 +116,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)); } } packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java +5 −2 Original line number Diff line number Diff line Loading @@ -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; } Loading Loading @@ -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 Loading packages/SystemUI/src/com/android/systemui/statusbar/NotificationPresenter.java +18 −9 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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 Loading Loading @@ -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 Loading @@ -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); } packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java 0 → 100644 +441 −0 Original line number Diff line number Diff line /* * 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(); } } packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java +92 −322 File changed.Preview size limit exceeded, changes collapsed. Show changes Loading
packages/SystemUI/src/com/android/systemui/SystemUIFactory.java +6 −2 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading Loading @@ -115,5 +116,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)); } }
packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java +5 −2 Original line number Diff line number Diff line Loading @@ -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; } Loading Loading @@ -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 Loading
packages/SystemUI/src/com/android/systemui/statusbar/NotificationPresenter.java +18 −9 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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 Loading Loading @@ -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 Loading @@ -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); }
packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java 0 → 100644 +441 −0 Original line number Diff line number Diff line /* * 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(); } }
packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java +92 −322 File changed.Preview size limit exceeded, changes collapsed. Show changes