11c0ca508b9d6184beba2050e6ab3a44ac5be137aAdrian Roos/*
21c0ca508b9d6184beba2050e6ab3a44ac5be137aAdrian Roos * Copyright (C) 2015 The Android Open Source Project
31c0ca508b9d6184beba2050e6ab3a44ac5be137aAdrian Roos *
41c0ca508b9d6184beba2050e6ab3a44ac5be137aAdrian Roos * Licensed under the Apache License, Version 2.0 (the "License");
51c0ca508b9d6184beba2050e6ab3a44ac5be137aAdrian Roos * you may not use this file except in compliance with the License.
61c0ca508b9d6184beba2050e6ab3a44ac5be137aAdrian Roos * You may obtain a copy of the License at
71c0ca508b9d6184beba2050e6ab3a44ac5be137aAdrian Roos *
81c0ca508b9d6184beba2050e6ab3a44ac5be137aAdrian Roos *      http://www.apache.org/licenses/LICENSE-2.0
91c0ca508b9d6184beba2050e6ab3a44ac5be137aAdrian Roos *
101c0ca508b9d6184beba2050e6ab3a44ac5be137aAdrian Roos * Unless required by applicable law or agreed to in writing, software
111c0ca508b9d6184beba2050e6ab3a44ac5be137aAdrian Roos * distributed under the License is distributed on an "AS IS" BASIS,
121c0ca508b9d6184beba2050e6ab3a44ac5be137aAdrian Roos * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
131c0ca508b9d6184beba2050e6ab3a44ac5be137aAdrian Roos * See the License for the specific language governing permissions and
141c0ca508b9d6184beba2050e6ab3a44ac5be137aAdrian Roos * limitations under the License
151c0ca508b9d6184beba2050e6ab3a44ac5be137aAdrian Roos */
161c0ca508b9d6184beba2050e6ab3a44ac5be137aAdrian Roos
171c0ca508b9d6184beba2050e6ab3a44ac5be137aAdrian Roospackage com.android.systemui.statusbar;
181c0ca508b9d6184beba2050e6ab3a44ac5be137aAdrian Roos
191c0ca508b9d6184beba2050e6ab3a44ac5be137aAdrian Roosimport com.android.internal.util.Preconditions;
201c0ca508b9d6184beba2050e6ab3a44ac5be137aAdrian Roosimport com.android.systemui.statusbar.phone.StatusBarWindowManager;
211c0ca508b9d6184beba2050e6ab3a44ac5be137aAdrian Roosimport com.android.systemui.statusbar.policy.HeadsUpManager;
221c0ca508b9d6184beba2050e6ab3a44ac5be137aAdrian Roosimport com.android.systemui.statusbar.policy.RemoteInputView;
231c0ca508b9d6184beba2050e6ab3a44ac5be137aAdrian Roos
24c0a579e5752b38257c31acb2f0b5b08447355205Adrian Roosimport android.util.ArraySet;
25c0a579e5752b38257c31acb2f0b5b08447355205Adrian Roos
261c0ca508b9d6184beba2050e6ab3a44ac5be137aAdrian Roosimport java.lang.ref.WeakReference;
271c0ca508b9d6184beba2050e6ab3a44ac5be137aAdrian Roosimport java.util.ArrayList;
281c0ca508b9d6184beba2050e6ab3a44ac5be137aAdrian Roos
291c0ca508b9d6184beba2050e6ab3a44ac5be137aAdrian Roos/**
301c0ca508b9d6184beba2050e6ab3a44ac5be137aAdrian Roos * Keeps track of the currently active {@link RemoteInputView}s.
311c0ca508b9d6184beba2050e6ab3a44ac5be137aAdrian Roos */
321c0ca508b9d6184beba2050e6ab3a44ac5be137aAdrian Roospublic class RemoteInputController {
331c0ca508b9d6184beba2050e6ab3a44ac5be137aAdrian Roos
34c0a579e5752b38257c31acb2f0b5b08447355205Adrian Roos    private final ArrayList<WeakReference<NotificationData.Entry>> mOpen = new ArrayList<>();
35c0a579e5752b38257c31acb2f0b5b08447355205Adrian Roos    private final ArraySet<String> mSpinning = new ArraySet<>();
36d28ccd7cab8e1a187dfd6c5733efe7cdfb652202Adrian Roos    private final ArrayList<Callback> mCallbacks = new ArrayList<>(3);
371c0ca508b9d6184beba2050e6ab3a44ac5be137aAdrian Roos    private final HeadsUpManager mHeadsUpManager;
381c0ca508b9d6184beba2050e6ab3a44ac5be137aAdrian Roos
391c0ca508b9d6184beba2050e6ab3a44ac5be137aAdrian Roos    public RemoteInputController(StatusBarWindowManager sbwm, HeadsUpManager headsUpManager) {
40d28ccd7cab8e1a187dfd6c5733efe7cdfb652202Adrian Roos        addCallback(sbwm);
411c0ca508b9d6184beba2050e6ab3a44ac5be137aAdrian Roos        mHeadsUpManager = headsUpManager;
421c0ca508b9d6184beba2050e6ab3a44ac5be137aAdrian Roos    }
431c0ca508b9d6184beba2050e6ab3a44ac5be137aAdrian Roos
441c0ca508b9d6184beba2050e6ab3a44ac5be137aAdrian Roos    public void addRemoteInput(NotificationData.Entry entry) {
451c0ca508b9d6184beba2050e6ab3a44ac5be137aAdrian Roos        Preconditions.checkNotNull(entry);
461c0ca508b9d6184beba2050e6ab3a44ac5be137aAdrian Roos
471c0ca508b9d6184beba2050e6ab3a44ac5be137aAdrian Roos        boolean found = pruneWeakThenRemoveAndContains(
481c0ca508b9d6184beba2050e6ab3a44ac5be137aAdrian Roos                entry /* contains */, null /* remove */);
491c0ca508b9d6184beba2050e6ab3a44ac5be137aAdrian Roos        if (!found) {
50c0a579e5752b38257c31acb2f0b5b08447355205Adrian Roos            mOpen.add(new WeakReference<>(entry));
511c0ca508b9d6184beba2050e6ab3a44ac5be137aAdrian Roos        }
521c0ca508b9d6184beba2050e6ab3a44ac5be137aAdrian Roos
531c0ca508b9d6184beba2050e6ab3a44ac5be137aAdrian Roos        apply(entry);
541c0ca508b9d6184beba2050e6ab3a44ac5be137aAdrian Roos    }
551c0ca508b9d6184beba2050e6ab3a44ac5be137aAdrian Roos
561c0ca508b9d6184beba2050e6ab3a44ac5be137aAdrian Roos    public void removeRemoteInput(NotificationData.Entry entry) {
571c0ca508b9d6184beba2050e6ab3a44ac5be137aAdrian Roos        Preconditions.checkNotNull(entry);
581c0ca508b9d6184beba2050e6ab3a44ac5be137aAdrian Roos
591c0ca508b9d6184beba2050e6ab3a44ac5be137aAdrian Roos        pruneWeakThenRemoveAndContains(null /* contains */, entry /* remove */);
601c0ca508b9d6184beba2050e6ab3a44ac5be137aAdrian Roos
611c0ca508b9d6184beba2050e6ab3a44ac5be137aAdrian Roos        apply(entry);
621c0ca508b9d6184beba2050e6ab3a44ac5be137aAdrian Roos    }
631c0ca508b9d6184beba2050e6ab3a44ac5be137aAdrian Roos
64c0a579e5752b38257c31acb2f0b5b08447355205Adrian Roos    public void addSpinning(String key) {
65c0a579e5752b38257c31acb2f0b5b08447355205Adrian Roos        mSpinning.add(key);
66c0a579e5752b38257c31acb2f0b5b08447355205Adrian Roos    }
67c0a579e5752b38257c31acb2f0b5b08447355205Adrian Roos
68c0a579e5752b38257c31acb2f0b5b08447355205Adrian Roos    public void removeSpinning(String key) {
69c0a579e5752b38257c31acb2f0b5b08447355205Adrian Roos        mSpinning.remove(key);
70c0a579e5752b38257c31acb2f0b5b08447355205Adrian Roos    }
71c0a579e5752b38257c31acb2f0b5b08447355205Adrian Roos
72c0a579e5752b38257c31acb2f0b5b08447355205Adrian Roos    public boolean isSpinning(String key) {
73c0a579e5752b38257c31acb2f0b5b08447355205Adrian Roos        return mSpinning.contains(key);
74c0a579e5752b38257c31acb2f0b5b08447355205Adrian Roos    }
75c0a579e5752b38257c31acb2f0b5b08447355205Adrian Roos
761c0ca508b9d6184beba2050e6ab3a44ac5be137aAdrian Roos    private void apply(NotificationData.Entry entry) {
771c0ca508b9d6184beba2050e6ab3a44ac5be137aAdrian Roos        mHeadsUpManager.setRemoteInputActive(entry, isRemoteInputActive(entry));
78d28ccd7cab8e1a187dfd6c5733efe7cdfb652202Adrian Roos        boolean remoteInputActive = isRemoteInputActive();
79d28ccd7cab8e1a187dfd6c5733efe7cdfb652202Adrian Roos        int N = mCallbacks.size();
80d28ccd7cab8e1a187dfd6c5733efe7cdfb652202Adrian Roos        for (int i = 0; i < N; i++) {
81d28ccd7cab8e1a187dfd6c5733efe7cdfb652202Adrian Roos            mCallbacks.get(i).onRemoteInputActive(remoteInputActive);
82d28ccd7cab8e1a187dfd6c5733efe7cdfb652202Adrian Roos        }
831c0ca508b9d6184beba2050e6ab3a44ac5be137aAdrian Roos    }
841c0ca508b9d6184beba2050e6ab3a44ac5be137aAdrian Roos
851c0ca508b9d6184beba2050e6ab3a44ac5be137aAdrian Roos    /**
861c0ca508b9d6184beba2050e6ab3a44ac5be137aAdrian Roos     * @return true if {@param entry} has an active RemoteInput
871c0ca508b9d6184beba2050e6ab3a44ac5be137aAdrian Roos     */
881c0ca508b9d6184beba2050e6ab3a44ac5be137aAdrian Roos    public boolean isRemoteInputActive(NotificationData.Entry entry) {
891c0ca508b9d6184beba2050e6ab3a44ac5be137aAdrian Roos        return pruneWeakThenRemoveAndContains(entry /* contains */, null /* remove */);
901c0ca508b9d6184beba2050e6ab3a44ac5be137aAdrian Roos    }
911c0ca508b9d6184beba2050e6ab3a44ac5be137aAdrian Roos
921c0ca508b9d6184beba2050e6ab3a44ac5be137aAdrian Roos    /**
931c0ca508b9d6184beba2050e6ab3a44ac5be137aAdrian Roos     * @return true if any entry has an active RemoteInput
941c0ca508b9d6184beba2050e6ab3a44ac5be137aAdrian Roos     */
951c0ca508b9d6184beba2050e6ab3a44ac5be137aAdrian Roos    public boolean isRemoteInputActive() {
961c0ca508b9d6184beba2050e6ab3a44ac5be137aAdrian Roos        pruneWeakThenRemoveAndContains(null /* contains */, null /* remove */);
97c0a579e5752b38257c31acb2f0b5b08447355205Adrian Roos        return !mOpen.isEmpty();
981c0ca508b9d6184beba2050e6ab3a44ac5be137aAdrian Roos    }
991c0ca508b9d6184beba2050e6ab3a44ac5be137aAdrian Roos
1001c0ca508b9d6184beba2050e6ab3a44ac5be137aAdrian Roos    /**
1011c0ca508b9d6184beba2050e6ab3a44ac5be137aAdrian Roos     * Prunes dangling weak references, removes entries referring to {@param remove} and returns
1021c0ca508b9d6184beba2050e6ab3a44ac5be137aAdrian Roos     * whether {@param contains} is part of the array in a single loop.
1031c0ca508b9d6184beba2050e6ab3a44ac5be137aAdrian Roos     * @param remove if non-null, removes this entry from the active remote inputs
1041c0ca508b9d6184beba2050e6ab3a44ac5be137aAdrian Roos     * @return true if {@param contains} is in the set of active remote inputs
1051c0ca508b9d6184beba2050e6ab3a44ac5be137aAdrian Roos     */
1061c0ca508b9d6184beba2050e6ab3a44ac5be137aAdrian Roos    private boolean pruneWeakThenRemoveAndContains(
1071c0ca508b9d6184beba2050e6ab3a44ac5be137aAdrian Roos            NotificationData.Entry contains, NotificationData.Entry remove) {
1081c0ca508b9d6184beba2050e6ab3a44ac5be137aAdrian Roos        boolean found = false;
109c0a579e5752b38257c31acb2f0b5b08447355205Adrian Roos        for (int i = mOpen.size() - 1; i >= 0; i--) {
110c0a579e5752b38257c31acb2f0b5b08447355205Adrian Roos            NotificationData.Entry item = mOpen.get(i).get();
1111c0ca508b9d6184beba2050e6ab3a44ac5be137aAdrian Roos            if (item == null || item == remove) {
112c0a579e5752b38257c31acb2f0b5b08447355205Adrian Roos                mOpen.remove(i);
1131c0ca508b9d6184beba2050e6ab3a44ac5be137aAdrian Roos            } else if (item == contains) {
1141c0ca508b9d6184beba2050e6ab3a44ac5be137aAdrian Roos                found = true;
1151c0ca508b9d6184beba2050e6ab3a44ac5be137aAdrian Roos            }
1161c0ca508b9d6184beba2050e6ab3a44ac5be137aAdrian Roos        }
1171c0ca508b9d6184beba2050e6ab3a44ac5be137aAdrian Roos        return found;
1181c0ca508b9d6184beba2050e6ab3a44ac5be137aAdrian Roos    }
1191c0ca508b9d6184beba2050e6ab3a44ac5be137aAdrian Roos
1201c0ca508b9d6184beba2050e6ab3a44ac5be137aAdrian Roos
121d28ccd7cab8e1a187dfd6c5733efe7cdfb652202Adrian Roos    public void addCallback(Callback callback) {
122d28ccd7cab8e1a187dfd6c5733efe7cdfb652202Adrian Roos        Preconditions.checkNotNull(callback);
123d28ccd7cab8e1a187dfd6c5733efe7cdfb652202Adrian Roos        mCallbacks.add(callback);
124d28ccd7cab8e1a187dfd6c5733efe7cdfb652202Adrian Roos    }
125d28ccd7cab8e1a187dfd6c5733efe7cdfb652202Adrian Roos
126c0a579e5752b38257c31acb2f0b5b08447355205Adrian Roos    public void remoteInputSent(NotificationData.Entry entry) {
127c0a579e5752b38257c31acb2f0b5b08447355205Adrian Roos        int N = mCallbacks.size();
128c0a579e5752b38257c31acb2f0b5b08447355205Adrian Roos        for (int i = 0; i < N; i++) {
129c0a579e5752b38257c31acb2f0b5b08447355205Adrian Roos            mCallbacks.get(i).onRemoteInputSent(entry);
130c0a579e5752b38257c31acb2f0b5b08447355205Adrian Roos        }
131c0a579e5752b38257c31acb2f0b5b08447355205Adrian Roos    }
132c0a579e5752b38257c31acb2f0b5b08447355205Adrian Roos
13307215350f97c46b3c74d0d8757ec12ea61ced258Adrian Roos    public void closeRemoteInputs() {
13407215350f97c46b3c74d0d8757ec12ea61ced258Adrian Roos        if (mOpen.size() == 0) {
13507215350f97c46b3c74d0d8757ec12ea61ced258Adrian Roos            return;
13607215350f97c46b3c74d0d8757ec12ea61ced258Adrian Roos        }
13707215350f97c46b3c74d0d8757ec12ea61ced258Adrian Roos
13807215350f97c46b3c74d0d8757ec12ea61ced258Adrian Roos        // Make a copy because closing the remote inputs will modify mOpen.
13907215350f97c46b3c74d0d8757ec12ea61ced258Adrian Roos        ArrayList<NotificationData.Entry> list = new ArrayList<>(mOpen.size());
14007215350f97c46b3c74d0d8757ec12ea61ced258Adrian Roos        for (int i = mOpen.size() - 1; i >= 0; i--) {
14107215350f97c46b3c74d0d8757ec12ea61ced258Adrian Roos            NotificationData.Entry item = mOpen.get(i).get();
14207215350f97c46b3c74d0d8757ec12ea61ced258Adrian Roos            if (item != null && item.row != null) {
14307215350f97c46b3c74d0d8757ec12ea61ced258Adrian Roos                list.add(item);
14407215350f97c46b3c74d0d8757ec12ea61ced258Adrian Roos            }
14507215350f97c46b3c74d0d8757ec12ea61ced258Adrian Roos        }
14607215350f97c46b3c74d0d8757ec12ea61ced258Adrian Roos
14707215350f97c46b3c74d0d8757ec12ea61ced258Adrian Roos        for (int i = list.size() - 1; i >= 0; i--) {
14807215350f97c46b3c74d0d8757ec12ea61ced258Adrian Roos            NotificationData.Entry item = list.get(i);
14907215350f97c46b3c74d0d8757ec12ea61ced258Adrian Roos            if (item.row != null) {
15007215350f97c46b3c74d0d8757ec12ea61ced258Adrian Roos                item.row.closeRemoteInput();
15107215350f97c46b3c74d0d8757ec12ea61ced258Adrian Roos            }
15207215350f97c46b3c74d0d8757ec12ea61ced258Adrian Roos        }
15307215350f97c46b3c74d0d8757ec12ea61ced258Adrian Roos    }
15407215350f97c46b3c74d0d8757ec12ea61ced258Adrian Roos
155d28ccd7cab8e1a187dfd6c5733efe7cdfb652202Adrian Roos    public interface Callback {
156c0a579e5752b38257c31acb2f0b5b08447355205Adrian Roos        default void onRemoteInputActive(boolean active) {}
157c0a579e5752b38257c31acb2f0b5b08447355205Adrian Roos
158c0a579e5752b38257c31acb2f0b5b08447355205Adrian Roos        default void onRemoteInputSent(NotificationData.Entry entry) {}
159d28ccd7cab8e1a187dfd6c5733efe7cdfb652202Adrian Roos    }
1601c0ca508b9d6184beba2050e6ab3a44ac5be137aAdrian Roos}
161