diff options
4 files changed, 149 insertions, 42 deletions
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java index 78e56c04ce9b..58d57f68d699 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java @@ -116,6 +116,8 @@ public class NotificationContentView extends FrameLayout { private boolean mForceSelectNextLayout = true; private PendingIntent mPreviousExpandedRemoteInputIntent; private PendingIntent mPreviousHeadsUpRemoteInputIntent; + private RemoteInputView mCachedExpandedRemoteInput; + private RemoteInputView mCachedHeadsUpRemoteInput; private int mContentHeightAtAnimationStart = UNDEFINED; private boolean mFocusOnVisibilityChange; @@ -298,6 +300,9 @@ public class NotificationContentView extends FrameLayout { mExpandedRemoteInput.onNotificationUpdateOrReset(); if (mExpandedRemoteInput.isActive()) { mPreviousExpandedRemoteInputIntent = mExpandedRemoteInput.getPendingIntent(); + mCachedExpandedRemoteInput = mExpandedRemoteInput; + mExpandedRemoteInput.dispatchStartTemporaryDetach(); + ((ViewGroup)mExpandedRemoteInput.getParent()).removeView(mExpandedRemoteInput); } } if (mExpandedChild != null) { @@ -310,6 +315,9 @@ public class NotificationContentView extends FrameLayout { mHeadsUpRemoteInput.onNotificationUpdateOrReset(); if (mHeadsUpRemoteInput.isActive()) { mPreviousHeadsUpRemoteInputIntent = mHeadsUpRemoteInput.getPendingIntent(); + mCachedHeadsUpRemoteInput = mHeadsUpRemoteInput; + mHeadsUpRemoteInput.dispatchStartTemporaryDetach(); + ((ViewGroup)mHeadsUpRemoteInput.getParent()).removeView(mHeadsUpRemoteInput); } } if (mHeadsUpChild != null) { @@ -963,22 +971,35 @@ public class NotificationContentView extends FrameLayout { View bigContentView = mExpandedChild; if (bigContentView != null) { mExpandedRemoteInput = applyRemoteInput(bigContentView, entry, hasRemoteInput, - mPreviousExpandedRemoteInputIntent); + mPreviousExpandedRemoteInputIntent, mCachedExpandedRemoteInput); } else { mExpandedRemoteInput = null; } + if (mCachedExpandedRemoteInput != null + && mCachedExpandedRemoteInput != mExpandedRemoteInput) { + // We had a cached remote input but didn't reuse it. Clean up required. + mCachedExpandedRemoteInput.dispatchFinishTemporaryDetach(); + } + mCachedExpandedRemoteInput = null; View headsUpContentView = mHeadsUpChild; if (headsUpContentView != null) { mHeadsUpRemoteInput = applyRemoteInput(headsUpContentView, entry, hasRemoteInput, - mPreviousHeadsUpRemoteInputIntent); + mPreviousHeadsUpRemoteInputIntent, mCachedHeadsUpRemoteInput); } else { mHeadsUpRemoteInput = null; } + if (mCachedHeadsUpRemoteInput != null + && mCachedHeadsUpRemoteInput != mHeadsUpRemoteInput) { + // We had a cached remote input but didn't reuse it. Clean up required. + mCachedHeadsUpRemoteInput.dispatchFinishTemporaryDetach(); + } + mCachedHeadsUpRemoteInput = null; } private RemoteInputView applyRemoteInput(View view, NotificationData.Entry entry, - boolean hasRemoteInput, PendingIntent existingPendingIntent) { + boolean hasRemoteInput, PendingIntent existingPendingIntent, + RemoteInputView cachedView) { View actionContainerCandidate = view.findViewById( com.android.internal.R.id.actions_container); if (actionContainerCandidate instanceof FrameLayout) { @@ -991,15 +1012,22 @@ public class NotificationContentView extends FrameLayout { if (existing == null && hasRemoteInput) { ViewGroup actionContainer = (FrameLayout) actionContainerCandidate; - RemoteInputView riv = RemoteInputView.inflate( - mContext, actionContainer, entry, mRemoteInputController); - - riv.setVisibility(View.INVISIBLE); - actionContainer.addView(riv, new LayoutParams( - ViewGroup.LayoutParams.MATCH_PARENT, - ViewGroup.LayoutParams.MATCH_PARENT) - ); - existing = riv; + if (cachedView == null) { + RemoteInputView riv = RemoteInputView.inflate( + mContext, actionContainer, entry, mRemoteInputController); + + riv.setVisibility(View.INVISIBLE); + actionContainer.addView(riv, new LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.MATCH_PARENT) + ); + existing = riv; + } else { + actionContainer.addView(cachedView); + cachedView.dispatchFinishTemporaryDetach(); + cachedView.requestFocus(); + existing = cachedView; + } } if (hasRemoteInput) { int color = entry.notification.getNotification().color; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/RemoteInputController.java b/packages/SystemUI/src/com/android/systemui/statusbar/RemoteInputController.java index 6cbaceaa00ff..66cc15d7cc8f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/RemoteInputController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/RemoteInputController.java @@ -21,7 +21,9 @@ import com.android.systemui.statusbar.phone.StatusBarWindowManager; import com.android.systemui.statusbar.policy.HeadsUpManager; import com.android.systemui.statusbar.policy.RemoteInputView; +import android.util.ArrayMap; import android.util.ArraySet; +import android.util.Pair; import java.lang.ref.WeakReference; import java.util.ArrayList; @@ -31,8 +33,9 @@ import java.util.ArrayList; */ public class RemoteInputController { - private final ArrayList<WeakReference<NotificationData.Entry>> mOpen = new ArrayList<>(); - private final ArraySet<String> mSpinning = new ArraySet<>(); + private final ArrayList<Pair<WeakReference<NotificationData.Entry>, Object>> mOpen + = new ArrayList<>(); + private final ArrayMap<String, Object> mSpinning = new ArrayMap<>(); private final ArrayList<Callback> mCallbacks = new ArrayList<>(3); private final HeadsUpManager mHeadsUpManager; @@ -41,36 +44,72 @@ public class RemoteInputController { mHeadsUpManager = headsUpManager; } - public void addRemoteInput(NotificationData.Entry entry) { + /** + * Adds a currently active remote input. + * + * @param entry the entry for which a remote input is now active. + * @param token a token identifying the view that is managing the remote input + */ + public void addRemoteInput(NotificationData.Entry entry, Object token) { Preconditions.checkNotNull(entry); + Preconditions.checkNotNull(token); boolean found = pruneWeakThenRemoveAndContains( - entry /* contains */, null /* remove */); + entry /* contains */, null /* remove */, token /* removeToken */); if (!found) { - mOpen.add(new WeakReference<>(entry)); + mOpen.add(new Pair<>(new WeakReference<>(entry), token)); } apply(entry); } - public void removeRemoteInput(NotificationData.Entry entry) { + /** + * Removes a currently active remote input. + * + * @param entry the entry for which a remote input should be removed. + * @param token a token identifying the view that is requesting the removal. If non-null, + * the entry is only removed if the token matches the last added token for this + * entry. If null, the entry is removed regardless. + */ + public void removeRemoteInput(NotificationData.Entry entry, Object token) { Preconditions.checkNotNull(entry); - pruneWeakThenRemoveAndContains(null /* contains */, entry /* remove */); + pruneWeakThenRemoveAndContains(null /* contains */, entry /* remove */, token); apply(entry); } - public void addSpinning(String key) { - mSpinning.add(key); + /** + * Adds a currently spinning (i.e. sending) remote input. + * + * @param key the key of the entry that's spinning. + * @param token the token of the view managing the remote input. + */ + public void addSpinning(String key, Object token) { + Preconditions.checkNotNull(key); + Preconditions.checkNotNull(token); + + mSpinning.put(key, token); } - public void removeSpinning(String key) { - mSpinning.remove(key); + /** + * Removes a currently spinning remote input. + * + * @param key the key of the entry for which a remote input should be removed. + * @param token a token identifying the view that is requesting the removal. If non-null, + * the entry is only removed if the token matches the last added token for this + * entry. If null, the entry is removed regardless. + */ + public void removeSpinning(String key, Object token) { + Preconditions.checkNotNull(key); + + if (token == null || mSpinning.get(key) == token) { + mSpinning.remove(key); + } } public boolean isSpinning(String key) { - return mSpinning.contains(key); + return mSpinning.containsKey(key); } private void apply(NotificationData.Entry entry) { @@ -86,14 +125,16 @@ public class RemoteInputController { * @return true if {@param entry} has an active RemoteInput */ public boolean isRemoteInputActive(NotificationData.Entry entry) { - return pruneWeakThenRemoveAndContains(entry /* contains */, null /* remove */); + return pruneWeakThenRemoveAndContains(entry /* contains */, null /* remove */, + null /* removeToken */); } /** * @return true if any entry has an active RemoteInput */ public boolean isRemoteInputActive() { - pruneWeakThenRemoveAndContains(null /* contains */, null /* remove */); + pruneWeakThenRemoveAndContains(null /* contains */, null /* remove */, + null /* removeToken */); return !mOpen.isEmpty(); } @@ -101,17 +142,27 @@ public class RemoteInputController { * Prunes dangling weak references, removes entries referring to {@param remove} and returns * whether {@param contains} is part of the array in a single loop. * @param remove if non-null, removes this entry from the active remote inputs + * @param removeToken if non-null, only removes an entry if this matches the token when the + * entry was added. * @return true if {@param contains} is in the set of active remote inputs */ private boolean pruneWeakThenRemoveAndContains( - NotificationData.Entry contains, NotificationData.Entry remove) { + NotificationData.Entry contains, NotificationData.Entry remove, Object removeToken) { boolean found = false; for (int i = mOpen.size() - 1; i >= 0; i--) { - NotificationData.Entry item = mOpen.get(i).get(); - if (item == null || item == remove) { + NotificationData.Entry item = mOpen.get(i).first.get(); + Object itemToken = mOpen.get(i).second; + boolean removeTokenMatches = (removeToken == null || itemToken == removeToken); + + if (item == null || (item == remove && removeTokenMatches)) { mOpen.remove(i); } else if (item == contains) { - found = true; + if (removeToken != null && removeToken != itemToken) { + // We need to update the token. Remove here and let caller reinsert it. + mOpen.remove(i); + } else { + found = true; + } } } return found; @@ -138,7 +189,7 @@ public class RemoteInputController { // Make a copy because closing the remote inputs will modify mOpen. ArrayList<NotificationData.Entry> list = new ArrayList<>(mOpen.size()); for (int i = mOpen.size() - 1; i >= 0; i--) { - NotificationData.Entry item = mOpen.get(i).get(); + NotificationData.Entry item = mOpen.get(i).first.get(); if (item != null && item.row != null) { list.add(item); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java index 4283eb85addb..26a85275c618 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java @@ -1719,7 +1719,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, protected void performRemoveNotification(StatusBarNotification n, boolean removeView) { Entry entry = mNotificationData.get(n.getKey()); if (mRemoteInputController.isRemoteInputActive(entry)) { - mRemoteInputController.removeRemoteInput(entry); + mRemoteInputController.removeRemoteInput(entry, null); } super.performRemoveNotification(n, removeView); } @@ -2667,7 +2667,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, private void removeRemoteInputEntriesKeptUntilCollapsed() { for (int i = 0; i < mRemoteInputEntriesToRemoveOnCollapse.size(); i++) { Entry entry = mRemoteInputEntriesToRemoveOnCollapse.valueAt(i); - mRemoteInputController.removeRemoteInput(entry); + mRemoteInputController.removeRemoteInput(entry, null); removeNotification(entry.key, mLatestRankingMap); } mRemoteInputEntriesToRemoveOnCollapse.clear(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java index ab2a8bc6e92b..7b1f7071b5c7 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java @@ -69,6 +69,8 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene // A marker object that let's us easily find views of this class. public static final Object VIEW_TAG = new Object(); + public final Object mToken = new Object(); + private RemoteEditText mEditText; private ImageButton mSendButton; private ProgressBar mProgressBar; @@ -140,8 +142,8 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene mSendButton.setVisibility(INVISIBLE); mProgressBar.setVisibility(VISIBLE); mEntry.remoteInputText = mEditText.getText(); - mController.addSpinning(mEntry.key); - mController.removeRemoteInput(mEntry); + mController.addSpinning(mEntry.key, mToken); + mController.removeRemoteInput(mEntry, mToken); mEditText.mShowImeOnInputConnection = false; mController.remoteInputSent(mEntry); @@ -193,7 +195,7 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene } private void onDefocus(boolean animate) { - mController.removeRemoteInput(mEntry); + mController.removeRemoteInput(mEntry, mToken); mEntry.remoteInputText = mEditText.getText(); // During removal, we get reattached and lose focus. Not hiding in that @@ -232,11 +234,11 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene @Override protected void onDetachedFromWindow() { super.onDetachedFromWindow(); - if (mEntry.row.isChangingPosition()) { + if (mEntry.row.isChangingPosition() || isTemporarilyDetached()) { return; } - mController.removeRemoteInput(mEntry); - mController.removeSpinning(mEntry.key); + mController.removeRemoteInput(mEntry, mToken); + mController.removeSpinning(mEntry.key, mToken); } public void setPendingIntent(PendingIntent pendingIntent) { @@ -265,7 +267,7 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene mEntry.notification.getPackageName()); setVisibility(VISIBLE); - mController.addRemoteInput(mEntry); + mController.addRemoteInput(mEntry, mToken); mEditText.setInnerFocusable(true); mEditText.mShowImeOnInputConnection = true; mEditText.setText(mEntry.remoteInputText); @@ -290,7 +292,7 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene mEditText.setEnabled(true); mSendButton.setVisibility(VISIBLE); mProgressBar.setVisibility(INVISIBLE); - mController.removeSpinning(mEntry.key); + mController.removeSpinning(mEntry.key, mToken); updateSendButton(); onDefocus(false /* animate */); @@ -432,6 +434,24 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene mRevealR = r; } + @Override + public void dispatchStartTemporaryDetach() { + super.dispatchStartTemporaryDetach(); + // Detach the EditText temporarily such that it doesn't get onDetachedFromWindow and + // won't lose IME focus. + detachViewFromParent(mEditText); + } + + @Override + public void dispatchFinishTemporaryDetach() { + if (isAttachedToWindow()) { + attachViewToParent(mEditText, 0, mEditText.getLayoutParams()); + } else { + removeDetachedView(mEditText, false /* animate */); + } + super.dispatchFinishTemporaryDetach(); + } + /** * An EditText that changes appearance based on whether it's focusable and becomes * un-focusable whenever the user navigates away from it or it becomes invisible. @@ -448,7 +468,15 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene } private void defocusIfNeeded(boolean animate) { - if (mRemoteInputView != null && mRemoteInputView.mEntry.row.isChangingPosition()) { + if (mRemoteInputView != null && mRemoteInputView.mEntry.row.isChangingPosition() + || isTemporarilyDetached()) { + if (isTemporarilyDetached()) { + // We might get reattached but then the other one of HUN / expanded might steal + // our focus, so we'll need to save our text here. + if (mRemoteInputView != null) { + mRemoteInputView.mEntry.remoteInputText = getText(); + } + } return; } if (isFocusable() && isEnabled()) { |