1 /*
2  * Copyright (C) 2015 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License
15  */
16 
17 package com.android.systemui.statusbar;
18 
19 import com.android.internal.util.Preconditions;
20 import com.android.systemui.statusbar.phone.StatusBarWindowManager;
21 import com.android.systemui.statusbar.policy.HeadsUpManager;
22 import com.android.systemui.statusbar.policy.RemoteInputView;
23 
24 import android.util.ArraySet;
25 
26 import java.lang.ref.WeakReference;
27 import java.util.ArrayList;
28 
29 /**
30  * Keeps track of the currently active {@link RemoteInputView}s.
31  */
32 public class RemoteInputController {
33 
34     private final ArrayList<WeakReference<NotificationData.Entry>> mOpen = new ArrayList<>();
35     private final ArraySet<String> mSpinning = new ArraySet<>();
36     private final ArrayList<Callback> mCallbacks = new ArrayList<>(3);
37     private final HeadsUpManager mHeadsUpManager;
38 
RemoteInputController(StatusBarWindowManager sbwm, HeadsUpManager headsUpManager)39     public RemoteInputController(StatusBarWindowManager sbwm, HeadsUpManager headsUpManager) {
40         addCallback(sbwm);
41         mHeadsUpManager = headsUpManager;
42     }
43 
addRemoteInput(NotificationData.Entry entry)44     public void addRemoteInput(NotificationData.Entry entry) {
45         Preconditions.checkNotNull(entry);
46 
47         boolean found = pruneWeakThenRemoveAndContains(
48                 entry /* contains */, null /* remove */);
49         if (!found) {
50             mOpen.add(new WeakReference<>(entry));
51         }
52 
53         apply(entry);
54     }
55 
removeRemoteInput(NotificationData.Entry entry)56     public void removeRemoteInput(NotificationData.Entry entry) {
57         Preconditions.checkNotNull(entry);
58 
59         pruneWeakThenRemoveAndContains(null /* contains */, entry /* remove */);
60 
61         apply(entry);
62     }
63 
addSpinning(String key)64     public void addSpinning(String key) {
65         mSpinning.add(key);
66     }
67 
removeSpinning(String key)68     public void removeSpinning(String key) {
69         mSpinning.remove(key);
70     }
71 
isSpinning(String key)72     public boolean isSpinning(String key) {
73         return mSpinning.contains(key);
74     }
75 
apply(NotificationData.Entry entry)76     private void apply(NotificationData.Entry entry) {
77         mHeadsUpManager.setRemoteInputActive(entry, isRemoteInputActive(entry));
78         boolean remoteInputActive = isRemoteInputActive();
79         int N = mCallbacks.size();
80         for (int i = 0; i < N; i++) {
81             mCallbacks.get(i).onRemoteInputActive(remoteInputActive);
82         }
83     }
84 
85     /**
86      * @return true if {@param entry} has an active RemoteInput
87      */
isRemoteInputActive(NotificationData.Entry entry)88     public boolean isRemoteInputActive(NotificationData.Entry entry) {
89         return pruneWeakThenRemoveAndContains(entry /* contains */, null /* remove */);
90     }
91 
92     /**
93      * @return true if any entry has an active RemoteInput
94      */
isRemoteInputActive()95     public boolean isRemoteInputActive() {
96         pruneWeakThenRemoveAndContains(null /* contains */, null /* remove */);
97         return !mOpen.isEmpty();
98     }
99 
100     /**
101      * Prunes dangling weak references, removes entries referring to {@param remove} and returns
102      * whether {@param contains} is part of the array in a single loop.
103      * @param remove if non-null, removes this entry from the active remote inputs
104      * @return true if {@param contains} is in the set of active remote inputs
105      */
pruneWeakThenRemoveAndContains( NotificationData.Entry contains, NotificationData.Entry remove)106     private boolean pruneWeakThenRemoveAndContains(
107             NotificationData.Entry contains, NotificationData.Entry remove) {
108         boolean found = false;
109         for (int i = mOpen.size() - 1; i >= 0; i--) {
110             NotificationData.Entry item = mOpen.get(i).get();
111             if (item == null || item == remove) {
112                 mOpen.remove(i);
113             } else if (item == contains) {
114                 found = true;
115             }
116         }
117         return found;
118     }
119 
120 
addCallback(Callback callback)121     public void addCallback(Callback callback) {
122         Preconditions.checkNotNull(callback);
123         mCallbacks.add(callback);
124     }
125 
remoteInputSent(NotificationData.Entry entry)126     public void remoteInputSent(NotificationData.Entry entry) {
127         int N = mCallbacks.size();
128         for (int i = 0; i < N; i++) {
129             mCallbacks.get(i).onRemoteInputSent(entry);
130         }
131     }
132 
closeRemoteInputs()133     public void closeRemoteInputs() {
134         if (mOpen.size() == 0) {
135             return;
136         }
137 
138         // Make a copy because closing the remote inputs will modify mOpen.
139         ArrayList<NotificationData.Entry> list = new ArrayList<>(mOpen.size());
140         for (int i = mOpen.size() - 1; i >= 0; i--) {
141             NotificationData.Entry item = mOpen.get(i).get();
142             if (item != null && item.row != null) {
143                 list.add(item);
144             }
145         }
146 
147         for (int i = list.size() - 1; i >= 0; i--) {
148             NotificationData.Entry item = list.get(i);
149             if (item.row != null) {
150                 item.row.closeRemoteInput();
151             }
152         }
153     }
154 
155     public interface Callback {
onRemoteInputActive(boolean active)156         default void onRemoteInputActive(boolean active) {}
157 
onRemoteInputSent(NotificationData.Entry entry)158         default void onRemoteInputSent(NotificationData.Entry entry) {}
159     }
160 }
161