• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.ims;
18 
19 import android.annotation.NonNull;
20 import android.annotation.Nullable;
21 import android.os.RemoteException;
22 import android.telephony.ims.ImsService;
23 import android.telephony.ims.feature.ImsFeature;
24 import android.util.LocalLog;
25 import android.util.Log;
26 
27 import com.android.ims.internal.IImsServiceFeatureCallback;
28 import com.android.internal.annotations.GuardedBy;
29 
30 import java.io.PrintWriter;
31 import java.util.ArrayList;
32 import java.util.List;
33 import java.util.Objects;
34 import java.util.Optional;
35 import java.util.concurrent.Executor;
36 import java.util.stream.Collectors;
37 
38 /**
39  * A repository of ImsFeature connections made available by an ImsService once it has been
40  * successfully bound.
41  *
42  * Provides the ability for listeners to register callbacks and the repository notify registered
43  * listeners when a connection has been created/removed for a specific connection type.
44  */
45 public class ImsFeatureBinderRepository {
46 
47     private static final String TAG = "ImsFeatureBinderRepo";
48 
49     /**
50      * Internal class representing a listener that is listening for changes to specific
51      * ImsFeature instances.
52      */
53     private static class ListenerContainer {
54         private final IImsServiceFeatureCallback mCallback;
55         private final Executor mExecutor;
56 
ListenerContainer(@onNull IImsServiceFeatureCallback c, @NonNull Executor e)57         public ListenerContainer(@NonNull IImsServiceFeatureCallback c, @NonNull Executor e) {
58             mCallback = c;
59             mExecutor = e;
60         }
61 
notifyFeatureCreatedOrRemoved(ImsFeatureContainer connector)62         public void notifyFeatureCreatedOrRemoved(ImsFeatureContainer connector) {
63             if (connector == null) {
64                 mExecutor.execute(() -> {
65                     try {
66                         mCallback.imsFeatureRemoved(
67                                 FeatureConnector.UNAVAILABLE_REASON_DISCONNECTED);
68                     } catch (RemoteException e) {
69                         // This listener will eventually be caught and removed during stale checks.
70                     }
71                 });
72             }
73             else {
74                 mExecutor.execute(() -> {
75                     try {
76                         mCallback.imsFeatureCreated(connector);
77                     } catch (RemoteException e) {
78                         // This listener will eventually be caught and removed during stale checks.
79                     }
80                 });
81             }
82         }
83 
notifyStateChanged(int state)84         public void notifyStateChanged(int state) {
85             mExecutor.execute(() -> {
86                 try {
87                     mCallback.imsStatusChanged(state);
88                 } catch (RemoteException e) {
89                     // This listener will eventually be caught and removed during stale checks.
90                 }
91             });
92         }
93 
notifyUpdateCapabilties(long caps)94         public void notifyUpdateCapabilties(long caps) {
95             mExecutor.execute(() -> {
96                 try {
97                     mCallback.updateCapabilities(caps);
98                 } catch (RemoteException e) {
99                     // This listener will eventually be caught and removed during stale checks.
100                 }
101             });
102         }
103 
isStale()104         public boolean isStale() {
105             return !mCallback.asBinder().isBinderAlive();
106         }
107 
108         @Override
equals(Object o)109         public boolean equals(Object o) {
110             if (this == o) return true;
111             if (o == null || getClass() != o.getClass()) return false;
112             ListenerContainer that = (ListenerContainer) o;
113             // Do not count executor for equality.
114             return mCallback.equals(that.mCallback);
115         }
116 
117         @Override
hashCode()118         public int hashCode() {
119             // Do not use executor for hash.
120             return Objects.hash(mCallback);
121         }
122 
123         @Override
toString()124         public String toString() {
125             return "ListenerContainer{" + "cb=" + mCallback + '}';
126         }
127     }
128 
129     /**
130      * Contains the mapping from ImsFeature type (MMTEL/RCS) to List of listeners listening for
131      * updates to the ImsFeature instance contained in the ImsFeatureContainer.
132      */
133     private static final class UpdateMapper {
134         public final int phoneId;
135         public final @ImsFeature.FeatureType int imsFeatureType;
136         private final List<ListenerContainer> mListeners = new ArrayList<>();
137         private ImsFeatureContainer mFeatureContainer;
138         private final Object mLock = new Object();
139 
140 
UpdateMapper(int pId, @ImsFeature.FeatureType int t)141         public UpdateMapper(int pId, @ImsFeature.FeatureType int t) {
142             phoneId = pId;
143             imsFeatureType = t;
144         }
145 
addFeatureContainer(ImsFeatureContainer c)146         public void addFeatureContainer(ImsFeatureContainer c) {
147             List<ListenerContainer> listeners;
148             synchronized (mLock) {
149                 if (Objects.equals(c, mFeatureContainer)) return;
150                 mFeatureContainer = c;
151                 listeners = copyListenerList(mListeners);
152             }
153             listeners.forEach(l -> l.notifyFeatureCreatedOrRemoved(mFeatureContainer));
154         }
155 
removeFeatureContainer()156         public ImsFeatureContainer removeFeatureContainer() {
157             ImsFeatureContainer oldContainer;
158             List<ListenerContainer> listeners;
159             synchronized (mLock) {
160                 if (mFeatureContainer == null) return null;
161                 oldContainer = mFeatureContainer;
162                 mFeatureContainer = null;
163                 listeners = copyListenerList(mListeners);
164             }
165             listeners.forEach(l -> l.notifyFeatureCreatedOrRemoved(mFeatureContainer));
166             return oldContainer;
167         }
168 
getFeatureContainer()169         public ImsFeatureContainer getFeatureContainer() {
170             synchronized(mLock) {
171                 return mFeatureContainer;
172             }
173         }
174 
addListener(ListenerContainer c)175         public void addListener(ListenerContainer c) {
176             ImsFeatureContainer featureContainer;
177             synchronized (mLock) {
178                 removeStaleListeners();
179                 if (mListeners.contains(c)) {
180                     return;
181                 }
182                 featureContainer = mFeatureContainer;
183                 mListeners.add(c);
184             }
185             // Do not call back until the feature container has been set.
186             if (featureContainer != null) {
187                 c.notifyFeatureCreatedOrRemoved(featureContainer);
188             }
189         }
190 
removeListener(IImsServiceFeatureCallback callback)191         public void removeListener(IImsServiceFeatureCallback callback) {
192             synchronized (mLock) {
193                 removeStaleListeners();
194                 List<ListenerContainer> oldListeners = mListeners.stream()
195                         .filter((c) -> Objects.equals(c.mCallback, callback))
196                         .collect(Collectors.toList());
197                 mListeners.removeAll(oldListeners);
198             }
199         }
200 
notifyStateUpdated(int newState)201         public void notifyStateUpdated(int newState) {
202             ImsFeatureContainer featureContainer;
203             List<ListenerContainer> listeners;
204             synchronized (mLock) {
205                 removeStaleListeners();
206                 featureContainer = mFeatureContainer;
207                 listeners = copyListenerList(mListeners);
208                 if (mFeatureContainer != null) {
209                     if (mFeatureContainer.getState() != newState) {
210                         mFeatureContainer.setState(newState);
211                     }
212                 }
213             }
214             // Only update if the feature container is set.
215             if (featureContainer != null) {
216                 listeners.forEach(l -> l.notifyStateChanged(newState));
217             }
218         }
219 
notifyUpdateCapabilities(long caps)220         public void notifyUpdateCapabilities(long caps) {
221             ImsFeatureContainer featureContainer;
222             List<ListenerContainer> listeners;
223             synchronized (mLock) {
224                 removeStaleListeners();
225                 featureContainer = mFeatureContainer;
226                 listeners = copyListenerList(mListeners);
227                 if (mFeatureContainer != null) {
228                     if (mFeatureContainer.getCapabilities() != caps) {
229                         mFeatureContainer.setCapabilities(caps);
230                     }
231                 }
232             }
233             // Only update if the feature container is set.
234             if (featureContainer != null) {
235                 listeners.forEach(l -> l.notifyUpdateCapabilties(caps));
236             }
237         }
238 
239         @GuardedBy("mLock")
removeStaleListeners()240         private void removeStaleListeners() {
241             List<ListenerContainer> staleListeners = mListeners.stream().filter(
242                     ListenerContainer::isStale)
243                     .collect(Collectors.toList());
244             mListeners.removeAll(staleListeners);
245         }
246 
247         @Override
toString()248         public String toString() {
249             synchronized (mLock) {
250                 return "UpdateMapper{" + "phoneId=" + phoneId + ", type="
251                         + ImsFeature.FEATURE_LOG_MAP.get(imsFeatureType) + ", container="
252                         + mFeatureContainer + '}';
253             }
254         }
255 
256 
copyListenerList(List<ListenerContainer> listeners)257         private List<ListenerContainer> copyListenerList(List<ListenerContainer> listeners) {
258             return new ArrayList<>(listeners);
259         }
260     }
261 
262     private final List<UpdateMapper> mFeatures = new ArrayList<>();
263     private final LocalLog mLocalLog = new LocalLog(50 /*lines*/);
264 
ImsFeatureBinderRepository()265     public ImsFeatureBinderRepository() {
266         logInfoLineLocked(-1, "FeatureConnectionRepository - created");
267     }
268 
269     /**
270      * Get the Container for a specific ImsFeature now if it exists.
271      *
272      * @param phoneId The phone ID that the connection is related to.
273      * @param type The ImsFeature type to get the cotnainr for (MMTEL/RCS).
274      * @return The Container containing the requested ImsFeature if it exists.
275      */
getIfExists( int phoneId, @ImsFeature.FeatureType int type)276     public Optional<ImsFeatureContainer> getIfExists(
277             int phoneId, @ImsFeature.FeatureType int type) {
278         if (type < 0 || type >= ImsFeature.FEATURE_MAX) {
279             throw new IllegalArgumentException("Incorrect feature type");
280         }
281         UpdateMapper m;
282         m = getUpdateMapper(phoneId, type);
283         ImsFeatureContainer c = m.getFeatureContainer();
284         logVerboseLineLocked(phoneId, "getIfExists, type= " + ImsFeature.FEATURE_LOG_MAP.get(type)
285                 + ", result= " + c);
286         return Optional.ofNullable(c);
287     }
288 
289     /**
290      * Register a callback that will receive updates when the requested ImsFeature type becomes
291      * available or unavailable for the specified phone ID.
292      * <p>
293      * This callback will not be called the first time until there is a valid ImsFeature.
294      * @param phoneId The phone ID that the connection will be related to.
295      * @param type The ImsFeature type to get (MMTEL/RCS).
296      * @param callback The callback that will be used to notify when the callback is
297      *                 available/unavailable.
298      * @param executor The executor that the callback will be run on.
299      */
registerForConnectionUpdates(int phoneId, @ImsFeature.FeatureType int type, @NonNull IImsServiceFeatureCallback callback, @NonNull Executor executor)300     public void registerForConnectionUpdates(int phoneId,
301             @ImsFeature.FeatureType int type, @NonNull IImsServiceFeatureCallback callback,
302             @NonNull Executor executor) {
303         if (type < 0 || type >= ImsFeature.FEATURE_MAX || callback == null || executor == null) {
304             throw new IllegalArgumentException("One or more invalid arguments have been passed in");
305         }
306         ListenerContainer container = new ListenerContainer(callback, executor);
307         logInfoLineLocked(phoneId, "registerForConnectionUpdates, type= "
308                 + ImsFeature.FEATURE_LOG_MAP.get(type) +", conn= " + container);
309         UpdateMapper m = getUpdateMapper(phoneId, type);
310         m.addListener(container);
311     }
312 
313     /**
314      * Unregister for updates on a previously registered callback.
315      *
316      * @param callback The callback to unregister.
317      */
unregisterForConnectionUpdates(@onNull IImsServiceFeatureCallback callback)318     public void unregisterForConnectionUpdates(@NonNull IImsServiceFeatureCallback callback) {
319         if (callback == null) {
320             throw new IllegalArgumentException("this method does not accept null arguments");
321         }
322         logInfoLineLocked(-1, "unregisterForConnectionUpdates, callback= " + callback);
323         synchronized (mFeatures) {
324             for (UpdateMapper m : mFeatures) {
325                 // warning: no callbacks should be called while holding locks
326                 m.removeListener(callback);
327             }
328         }
329     }
330 
331     /**
332      * Add a Container containing the IBinder interfaces associated with a specific ImsFeature type
333      * (MMTEL/RCS). If one already exists, it will be replaced. This will notify listeners of the
334      * change.
335      * @param phoneId The phone ID associated with this Container.
336      * @param type The ImsFeature type to get (MMTEL/RCS).
337      * @param newConnection A Container containing the IBinder interface connections associated with
338      *                      the ImsFeature type.
339      */
addConnection(int phoneId, @ImsFeature.FeatureType int type, @Nullable ImsFeatureContainer newConnection)340     public void addConnection(int phoneId, @ImsFeature.FeatureType int type,
341             @Nullable ImsFeatureContainer newConnection) {
342         if (type < 0 || type >= ImsFeature.FEATURE_MAX) {
343             throw new IllegalArgumentException("The type must valid");
344         }
345         logInfoLineLocked(phoneId, "addConnection, type=" + ImsFeature.FEATURE_LOG_MAP.get(type)
346                 + ", conn=" + newConnection);
347         UpdateMapper m = getUpdateMapper(phoneId, type);
348         m.addFeatureContainer(newConnection);
349     }
350 
351     /**
352      * Remove the IBinder Container associated with a specific ImsService type. Listeners will be
353      * notified of this change.
354      * @param phoneId The phone ID associated with this connection.
355      * @param type The ImsFeature type to get (MMTEL/RCS).
356      */
removeConnection(int phoneId, @ImsFeature.FeatureType int type)357     public ImsFeatureContainer removeConnection(int phoneId, @ImsFeature.FeatureType int type) {
358         if (type < 0 || type >= ImsFeature.FEATURE_MAX) {
359             throw new IllegalArgumentException("The type must valid");
360         }
361         logInfoLineLocked(phoneId, "removeConnection, type="
362                 + ImsFeature.FEATURE_LOG_MAP.get(type));
363         UpdateMapper m = getUpdateMapper(phoneId, type);
364         return m.removeFeatureContainer();
365     }
366 
367     /**
368      * Notify listeners that the state of a specific ImsFeature that this repository is
369      * tracking has changed. Listeners will be notified of the change in the ImsFeature's state.
370      * @param phoneId The phoneId of the feature that has changed state.
371      * @param type The ImsFeature type to get (MMTEL/RCS).
372      * @param state The new state of the ImsFeature
373      */
notifyFeatureStateChanged(int phoneId, @ImsFeature.FeatureType int type, @ImsFeature.ImsState int state)374     public void notifyFeatureStateChanged(int phoneId, @ImsFeature.FeatureType int type,
375             @ImsFeature.ImsState int state) {
376         logInfoLineLocked(phoneId, "notifyFeatureStateChanged, type="
377                 + ImsFeature.FEATURE_LOG_MAP.get(type) + ", state="
378                 + ImsFeature.STATE_LOG_MAP.get(state));
379         UpdateMapper m = getUpdateMapper(phoneId, type);
380         m.notifyStateUpdated(state);
381     }
382 
383     /**
384      *  Notify listeners that the capabilities of a specific ImsFeature that this repository is
385      * tracking has changed. Listeners will be notified of the change in the ImsFeature's
386      * capabilities.
387      * @param phoneId The phoneId of the feature that has changed capabilities.
388      * @param type The ImsFeature type to get (MMTEL/RCS).
389      * @param capabilities The new capabilities of the ImsFeature
390      */
notifyFeatureCapabilitiesChanged(int phoneId, @ImsFeature.FeatureType int type, @ImsService.ImsServiceCapability long capabilities)391     public void notifyFeatureCapabilitiesChanged(int phoneId, @ImsFeature.FeatureType int type,
392             @ImsService.ImsServiceCapability long capabilities) {
393         logInfoLineLocked(phoneId, "notifyFeatureCapabilitiesChanged, type="
394                 + ImsFeature.FEATURE_LOG_MAP.get(type) + ", caps="
395                 + ImsService.getCapabilitiesString(capabilities));
396         UpdateMapper m = getUpdateMapper(phoneId, type);
397         m.notifyUpdateCapabilities(capabilities);
398     }
399 
400     /**
401      * Prints the dump of log events that have occurred on this repository.
402      */
dump(PrintWriter printWriter)403     public void dump(PrintWriter printWriter) {
404         synchronized (mLocalLog) {
405             mLocalLog.dump(printWriter);
406         }
407     }
408 
getUpdateMapper(int phoneId, int type)409     private UpdateMapper getUpdateMapper(int phoneId, int type) {
410         synchronized (mFeatures) {
411             UpdateMapper mapper = mFeatures.stream()
412                     .filter((c) -> ((c.phoneId == phoneId) && (c.imsFeatureType == type)))
413                     .findFirst().orElse(null);
414             if (mapper == null) {
415                 mapper = new UpdateMapper(phoneId, type);
416                 mFeatures.add(mapper);
417             }
418             return mapper;
419         }
420     }
421 
logVerboseLineLocked(int phoneId, String log)422     private void logVerboseLineLocked(int phoneId, String log) {
423         if (!Log.isLoggable(TAG, Log.VERBOSE)) return;
424         final String phoneIdPrefix = "[" + phoneId + "] ";
425         Log.v(TAG, phoneIdPrefix + log);
426         synchronized (mLocalLog) {
427             mLocalLog.log(phoneIdPrefix + log);
428         }
429     }
430 
logInfoLineLocked(int phoneId, String log)431     private void logInfoLineLocked(int phoneId, String log) {
432         final String phoneIdPrefix = "[" + phoneId + "] ";
433         Log.i(TAG, phoneIdPrefix + log);
434         synchronized (mLocalLog) {
435             mLocalLog.log(phoneIdPrefix + log);
436         }
437     }
438 }
439