| /* |
| * Copyright (C) 2017 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package android.net.lowpan; |
| |
| import android.annotation.NonNull; |
| import android.annotation.Nullable; |
| import android.content.Context; |
| import android.os.Handler; |
| import android.os.IBinder; |
| import android.os.Looper; |
| import android.os.RemoteException; |
| import android.os.ServiceManager; |
| import java.lang.ref.WeakReference; |
| import java.util.HashMap; |
| import java.util.Map; |
| import java.util.WeakHashMap; |
| |
| /** |
| * Manager object for looking up LoWPAN interfaces. |
| * |
| * @hide |
| */ |
| // @SystemApi |
| public class LowpanManager { |
| private static final String TAG = LowpanManager.class.getSimpleName(); |
| |
| /** @hide */ |
| // @SystemApi |
| public abstract static class Callback { |
| public void onInterfaceAdded(LowpanInterface lowpanInterface) {} |
| |
| public void onInterfaceRemoved(LowpanInterface lowpanInterface) {} |
| } |
| |
| private final Map<Integer, ILowpanManagerListener> mListenerMap = new HashMap<>(); |
| private final Map<String, LowpanInterface> mInterfaceCache = new HashMap<>(); |
| |
| /* This is a WeakHashMap because we don't want to hold onto |
| * a strong reference to ILowpanInterface, so that it can be |
| * garbage collected if it isn't being used anymore. Since |
| * the value class holds onto this specific ILowpanInterface, |
| * we also need to have a weak reference to the value. |
| * This design pattern allows us to skip removal of items |
| * from this Map without leaking memory. |
| */ |
| private final Map<IBinder, WeakReference<LowpanInterface>> mBinderCache = |
| new WeakHashMap<>(); |
| |
| private final ILowpanManager mService; |
| private final Context mContext; |
| private final Looper mLooper; |
| |
| // Static Methods |
| |
| public static LowpanManager from(Context context) { |
| return (LowpanManager) context.getSystemService(Context.LOWPAN_SERVICE); |
| } |
| |
| /** @hide */ |
| public static LowpanManager getManager() { |
| IBinder binder = ServiceManager.getService(Context.LOWPAN_SERVICE); |
| |
| if (binder != null) { |
| ILowpanManager service = ILowpanManager.Stub.asInterface(binder); |
| return new LowpanManager(service); |
| } |
| |
| return null; |
| } |
| |
| // Constructors |
| |
| LowpanManager(ILowpanManager service) { |
| mService = service; |
| mContext = null; |
| mLooper = null; |
| } |
| |
| /** |
| * Create a new LowpanManager instance. Applications will almost always want to use {@link |
| * android.content.Context#getSystemService Context.getSystemService()} to retrieve the standard |
| * {@link android.content.Context#LOWPAN_SERVICE Context.LOWPAN_SERVICE}. |
| * |
| * @param context the application context |
| * @param service the Binder interface |
| * @param looper the default Looper to run callbacks on |
| * @hide - hide this because it takes in a parameter of type ILowpanManager, which is a system |
| * private class. |
| */ |
| public LowpanManager(Context context, ILowpanManager service, Looper looper) { |
| mContext = context; |
| mService = service; |
| mLooper = looper; |
| } |
| |
| /** @hide */ |
| @Nullable |
| public LowpanInterface getInterfaceNoCreate(@NonNull ILowpanInterface ifaceService) { |
| LowpanInterface iface = null; |
| |
| synchronized (mBinderCache) { |
| if (mBinderCache.containsKey(ifaceService.asBinder())) { |
| iface = mBinderCache.get(ifaceService.asBinder()).get(); |
| } |
| } |
| |
| return iface; |
| } |
| |
| /** @hide */ |
| @Nullable |
| public LowpanInterface getInterface(@NonNull ILowpanInterface ifaceService) { |
| LowpanInterface iface = null; |
| |
| try { |
| synchronized (mBinderCache) { |
| if (mBinderCache.containsKey(ifaceService.asBinder())) { |
| iface = mBinderCache.get(ifaceService.asBinder()).get(); |
| } |
| |
| if (iface == null) { |
| String ifaceName = ifaceService.getName(); |
| |
| iface = new LowpanInterface(mContext, ifaceService, mLooper); |
| |
| synchronized (mInterfaceCache) { |
| mInterfaceCache.put(iface.getName(), iface); |
| } |
| |
| mBinderCache.put(ifaceService.asBinder(), new WeakReference(iface)); |
| |
| /* Make sure we remove the object from the |
| * interface cache if the associated service |
| * dies. |
| */ |
| ifaceService |
| .asBinder() |
| .linkToDeath( |
| new IBinder.DeathRecipient() { |
| @Override |
| public void binderDied() { |
| synchronized (mInterfaceCache) { |
| LowpanInterface iface = |
| mInterfaceCache.get(ifaceName); |
| |
| if ((iface != null) |
| && (iface.getService() == ifaceService)) { |
| mInterfaceCache.remove(ifaceName); |
| } |
| } |
| } |
| }, |
| 0); |
| } |
| } |
| } catch (RemoteException x) { |
| throw x.rethrowAsRuntimeException(); |
| } |
| |
| return iface; |
| } |
| |
| /** |
| * Returns a reference to the requested LowpanInterface object. If the given interface doesn't |
| * exist, or it is not a LoWPAN interface, returns null. |
| */ |
| @Nullable |
| public LowpanInterface getInterface(@NonNull String name) { |
| LowpanInterface iface = null; |
| |
| try { |
| /* This synchronized block covers both branches of the enclosed |
| * if() statement in order to avoid a race condition. Two threads |
| * calling getInterface() with the same name would race to create |
| * the associated LowpanInterface object, creating two of them. |
| * Having the whole block be synchronized avoids that race. |
| */ |
| synchronized (mInterfaceCache) { |
| if (mInterfaceCache.containsKey(name)) { |
| iface = mInterfaceCache.get(name); |
| |
| } else { |
| ILowpanInterface ifaceService = mService.getInterface(name); |
| |
| if (ifaceService != null) { |
| iface = getInterface(ifaceService); |
| } |
| } |
| } |
| } catch (RemoteException x) { |
| throw x.rethrowFromSystemServer(); |
| } |
| |
| return iface; |
| } |
| |
| /** |
| * Returns a reference to the first registered LowpanInterface object. If there are no LoWPAN |
| * interfaces registered, returns null. |
| */ |
| @Nullable |
| public LowpanInterface getInterface() { |
| String[] ifaceList = getInterfaceList(); |
| if (ifaceList.length > 0) { |
| return getInterface(ifaceList[0]); |
| } |
| return null; |
| } |
| |
| /** |
| * Returns a string array containing the names of LoWPAN interfaces. This list may contain fewer |
| * interfaces if the calling process does not have permissions to see individual interfaces. |
| */ |
| @NonNull |
| public String[] getInterfaceList() { |
| try { |
| return mService.getInterfaceList(); |
| } catch (RemoteException x) { |
| throw x.rethrowFromSystemServer(); |
| } |
| } |
| |
| /** |
| * Registers a callback object to receive notifications when LoWPAN interfaces are added or |
| * removed. |
| * |
| * @hide |
| */ |
| public void registerCallback(@NonNull Callback cb, @Nullable Handler handler) |
| throws LowpanException { |
| ILowpanManagerListener.Stub listenerBinder = |
| new ILowpanManagerListener.Stub() { |
| private Handler mHandler; |
| |
| { |
| if (handler != null) { |
| mHandler = handler; |
| } else if (mLooper != null) { |
| mHandler = new Handler(mLooper); |
| } else { |
| mHandler = new Handler(); |
| } |
| } |
| |
| @Override |
| public void onInterfaceAdded(ILowpanInterface ifaceService) { |
| Runnable runnable = |
| () -> { |
| LowpanInterface iface = getInterface(ifaceService); |
| |
| if (iface != null) { |
| cb.onInterfaceAdded(iface); |
| } |
| }; |
| |
| mHandler.post(runnable); |
| } |
| |
| @Override |
| public void onInterfaceRemoved(ILowpanInterface ifaceService) { |
| Runnable runnable = |
| () -> { |
| LowpanInterface iface = getInterfaceNoCreate(ifaceService); |
| |
| if (iface != null) { |
| cb.onInterfaceRemoved(iface); |
| } |
| }; |
| |
| mHandler.post(runnable); |
| } |
| }; |
| try { |
| mService.addListener(listenerBinder); |
| } catch (RemoteException x) { |
| throw x.rethrowFromSystemServer(); |
| } |
| |
| synchronized (mListenerMap) { |
| mListenerMap.put(Integer.valueOf(System.identityHashCode(cb)), listenerBinder); |
| } |
| } |
| |
| /** @hide */ |
| public void registerCallback(@NonNull Callback cb) throws LowpanException { |
| registerCallback(cb, null); |
| } |
| |
| /** |
| * Unregisters a previously registered {@link LowpanManager.Callback} object. |
| * |
| * @hide |
| */ |
| public void unregisterCallback(@NonNull Callback cb) { |
| Integer hashCode = Integer.valueOf(System.identityHashCode(cb)); |
| ILowpanManagerListener listenerBinder = null; |
| |
| synchronized (mListenerMap) { |
| listenerBinder = mListenerMap.get(hashCode); |
| mListenerMap.remove(hashCode); |
| } |
| |
| if (listenerBinder != null) { |
| try { |
| mService.removeListener(listenerBinder); |
| } catch (RemoteException x) { |
| throw x.rethrowFromSystemServer(); |
| } |
| } else { |
| throw new RuntimeException("Attempt to unregister an unknown callback"); |
| } |
| } |
| } |