1 /*
2  * Copyright (C) 2020 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.internal.listeners;
18 
19 import android.os.RemoteException;
20 import android.util.ArrayMap;
21 
22 import com.android.internal.annotations.GuardedBy;
23 
24 import java.lang.ref.WeakReference;
25 import java.util.Map;
26 import java.util.WeakHashMap;
27 
28 /**
29  * A listener transport manager which handles mappings between the client facing listener and system
30  * server facing transport. Supports transports which may be removed either from the client side or
31  * from the system server side without leaking memory.
32  *
33  * @param <TTransport>> transport type
34  */
35 public abstract class ListenerTransportManager<TTransport extends ListenerTransport<?>> {
36 
37     @GuardedBy("mRegistrations")
38     private final Map<Object, WeakReference<TTransport>> mRegistrations;
39 
ListenerTransportManager(boolean allowServerSideTransportRemoval)40     protected ListenerTransportManager(boolean allowServerSideTransportRemoval) {
41         // using weakhashmap means that the transport may be GCed if the server drops its reference,
42         // and thus the listener may be GCed as well if the client drops that reference. if the
43         // server will never drop a reference without warning (ie, transport removal may only be
44         // initiated from the client side), then arraymap or similar may be used without fear of
45         // memory leaks.
46         if (allowServerSideTransportRemoval) {
47             mRegistrations = new WeakHashMap<>();
48         } else {
49             mRegistrations = new ArrayMap<>();
50         }
51     }
52 
53     /**
54      * Adds a new transport with the given listener key.
55      */
addListener(Object key, TTransport transport)56     public final void addListener(Object key, TTransport transport) {
57         try {
58             synchronized (mRegistrations) {
59                 // ordering of operations is important so that if an error occurs at any point we
60                 // are left in a reasonable state
61                 TTransport oldTransport;
62                 WeakReference<TTransport> oldTransportRef = mRegistrations.get(key);
63                 if (oldTransportRef != null) {
64                     oldTransport = oldTransportRef.get();
65                 } else {
66                     oldTransport = null;
67                 }
68 
69                 if (oldTransport == null) {
70                     registerTransport(transport);
71                 } else {
72                     registerTransport(transport, oldTransport);
73                     oldTransport.unregister();
74                 }
75                 mRegistrations.put(key, new WeakReference<>(transport));
76             }
77         } catch (RemoteException e) {
78             throw e.rethrowFromSystemServer();
79         }
80     }
81 
82     /**
83      * Removes the transport with the given listener key.
84      */
removeListener(Object key)85     public final void removeListener(Object key) {
86         try {
87             synchronized (mRegistrations) {
88                 // ordering of operations is important so that if an error occurs at any point we
89                 // are left in a reasonable state
90                 WeakReference<TTransport> transportRef = mRegistrations.remove(key);
91                 if (transportRef != null) {
92                     TTransport transport = transportRef.get();
93                     if (transport != null) {
94                         transport.unregister();
95                         unregisterTransport(transport);
96                     }
97                 }
98             }
99         } catch (RemoteException e) {
100             throw e.rethrowFromSystemServer();
101         }
102     }
103 
104     /**
105      * Registers a new transport.
106      */
registerTransport(TTransport transport)107     protected abstract void registerTransport(TTransport transport) throws RemoteException;
108 
109     /**
110      * Registers a new transport that is replacing the given old transport. Implementations must
111      * ensure that if they throw a remote exception, the call does not have any side effects.
112      */
registerTransport(TTransport transport, TTransport oldTransport)113     protected void registerTransport(TTransport transport, TTransport oldTransport)
114             throws RemoteException {
115         registerTransport(transport);
116         try {
117             unregisterTransport(oldTransport);
118         } catch (RemoteException e) {
119             try {
120                 // best effort to ensure there are no side effects
121                 unregisterTransport(transport);
122             } catch (RemoteException suppressed) {
123                 e.addSuppressed(suppressed);
124             }
125             throw e;
126         }
127     }
128 
129     /**
130      * Unregisters an existing transport.
131      */
unregisterTransport(TTransport transport)132     protected abstract void unregisterTransport(TTransport transport) throws RemoteException;
133 }
134