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