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 */
16package android.databinding.adapters;
17
18import android.os.Build.VERSION;
19import android.os.Build.VERSION_CODES;
20import android.util.SparseArray;
21import android.view.View;
22
23import java.lang.ref.WeakReference;
24import java.util.WeakHashMap;
25
26public class ListenerUtil {
27    private static SparseArray<WeakHashMap<View, WeakReference<?>>> sListeners =
28            new SparseArray<WeakHashMap<View, WeakReference<?>>>();
29
30    /**
31     * This method tracks listeners for a View. Only one listener per listenerResourceId
32     * can be tracked at a time. This is useful for add*Listener and remove*Listener methods
33     * when used with BindingAdapters. This guarantees not to leak the listener or the View,
34     * so will not keep a strong reference to either.
35     *
36     * Example usage:
37     *<pre><code>@BindingAdapter("onFoo")
38     * public static void addFooListener(MyView view, OnFooListener listener) {
39     *     OnFooListener oldValue = ListenerUtil.trackListener(view, listener, R.id.fooListener);
40     *     if (oldValue != null) {
41     *         view.removeOnFooListener(oldValue);
42     *     }
43     *     if (listener != null) {
44     *         view.addOnFooListener(listener);
45     *     }
46     * }</code></pre>
47     *
48     * @param view The View that will have this listener
49     * @param listener The listener to keep track of. May be null if the listener is being removed.
50     * @param listenerResourceId A unique resource ID associated with the listener type.
51     * @return The previously tracked listener. This will be null if the View did not have
52     * a previously-tracked listener.
53     */
54    @SuppressWarnings("unchecked")
55    public static <T> T trackListener(View view, T listener, int listenerResourceId) {
56        if (VERSION.SDK_INT >= VERSION_CODES.ICE_CREAM_SANDWICH) {
57            final T oldValue = (T) view.getTag(listenerResourceId);
58            view.setTag(listenerResourceId, listener);
59            return oldValue;
60        } else {
61            synchronized (sListeners) {
62                WeakHashMap<View, WeakReference<?>> listeners = sListeners.get(listenerResourceId);
63                if (listeners == null) {
64                    listeners = new WeakHashMap<View, WeakReference<?>>();
65                    sListeners.put(listenerResourceId, listeners);
66                }
67                final WeakReference<T> oldValue;
68                if (listener == null) {
69                    oldValue = (WeakReference<T>) listeners.remove(view);
70                } else {
71                    oldValue = (WeakReference<T>) listeners.put(view, new WeakReference(listener));
72                }
73                if (oldValue == null) {
74                    return null;
75                } else {
76                    return oldValue.get();
77                }
78            }
79        }
80    }
81
82    /**
83     * Returns the previous value for a listener if one was stored previously with
84     * {@link #trackListener(View, Object, int)}
85     * @param view The View to check for a listener previously stored with
86     * {@link #trackListener(View, Object, int)}
87     * @param listenerResourceId A unique resource ID associated with the listener type.
88     * @return The previously tracked listener. This will be null if the View did not have
89     * a previously-tracked listener.
90     */
91    public static <T> T getListener(View view, int listenerResourceId) {
92        if (VERSION.SDK_INT >= VERSION_CODES.ICE_CREAM_SANDWICH) {
93            return (T) view.getTag(listenerResourceId);
94        } else {
95            synchronized (sListeners) {
96                WeakHashMap<View, WeakReference<?>> listeners = sListeners.get(listenerResourceId);
97                if (listeners == null) {
98                    return null;
99                }
100                final WeakReference<T> oldValue = (WeakReference<T>) listeners.get(view);
101                if (oldValue == null) {
102                    return null;
103                } else {
104                    return oldValue.get();
105                }
106            }
107        }
108    }
109}
110