content: /*
   * Copyright (C) 2016 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 com.android.server.wifi.aware;
  
  import android.annotation.NonNull;
  import android.hardware.wifi.V1_0.IWifiNanIface;
  import android.hardware.wifi.V1_0.IfaceType;
  import android.hardware.wifi.V1_0.WifiStatus;
  import android.hardware.wifi.V1_0.WifiStatusCode;
  import android.os.Handler;
  import android.os.RemoteException;
  import android.util.Log;
  
  import com.android.internal.annotations.VisibleForTesting;
  import com.android.server.wifi.HalDeviceManager;
  
  import java.io.FileDescriptor;
  import java.io.PrintWriter;
  
  /**
   * Manages the interface to Wi-Fi Aware HIDL (HAL).
   */
  public class WifiAwareNativeManager {
      private static final String TAG = "WifiAwareNativeManager";
      private static final boolean VDBG = false;
      /* package */ boolean mDbg = false;
  
      // to be used for synchronizing access to any of the WifiAwareNative objects
      private final Object mLock = new Object();
  
      private WifiAwareStateManager mWifiAwareStateManager;
      private HalDeviceManager mHalDeviceManager;
      private Handler mHandler;
      private WifiAwareNativeCallback mWifiAwareNativeCallback;
      private IWifiNanIface mWifiNanIface = null;
      private InterfaceDestroyedListener mInterfaceDestroyedListener;
      private InterfaceAvailableForRequestListener mInterfaceAvailableForRequestListener =
              new InterfaceAvailableForRequestListener();
      private int mReferenceCount = 0;
  
      WifiAwareNativeManager(WifiAwareStateManager awareStateManager,
              HalDeviceManager halDeviceManager,
              WifiAwareNativeCallback wifiAwareNativeCallback) {
          mWifiAwareStateManager = awareStateManager;
          mHalDeviceManager = halDeviceManager;
          mWifiAwareNativeCallback = wifiAwareNativeCallback;
      }
  
      /**
       * (HIDL) Cast the input to a 1.2 NAN interface (possibly resulting in a null).
       *
       * Separate function so can be mocked in unit tests.
       */
      public android.hardware.wifi.V1_2.IWifiNanIface mockableCastTo_1_2(IWifiNanIface iface) {
          return android.hardware.wifi.V1_2.IWifiNanIface.castFrom(iface);
      }
  
      /**
       * Initialize the class - intended for late initialization.
       *
       * @param handler Handler on which to execute interface available callbacks.
       */
      public void start(Handler handler) {
          mHandler = handler;
          mHalDeviceManager.initialize();
          mHalDeviceManager.registerStatusListener(
                  new HalDeviceManager.ManagerStatusListener() {
                      @Override
                      public void onStatusChanged() {
                          if (VDBG) Log.v(TAG, "onStatusChanged");
                          // only care about isStarted (Wi-Fi started) not isReady - since if not
                          // ready then Wi-Fi will also be down.
                          if (mHalDeviceManager.isStarted()) {
                              // 1. no problem registering duplicates - only one will be called
                              // 2. will be called immediately if available
                              mHalDeviceManager.registerInterfaceAvailableForRequestListener(
                                      IfaceType.NAN, mInterfaceAvailableForRequestListener, mHandler);
                          } else {
                              awareIsDown();
                          }
                      }
                  }, mHandler);
          if (mHalDeviceManager.isStarted()) {
              mHalDeviceManager.registerInterfaceAvailableForRequestListener(
                      IfaceType.NAN, mInterfaceAvailableForRequestListener, mHandler);
          }
      }
  
      /**
       * Returns the native HAL WifiNanIface through which commands to the NAN HAL are dispatched.
       * Return may be null if not initialized/available.
       */
      @VisibleForTesting
      public IWifiNanIface getWifiNanIface() {
          synchronized (mLock) {
              return mWifiNanIface;
          }
      }
  
      /**
       * Attempt to obtain the HAL NAN interface.
       */
      public void tryToGetAware() {
          synchronized (mLock) {
              if (mDbg) {
                  Log.d(TAG, "tryToGetAware: mWifiNanIface=" + mWifiNanIface + ", mReferenceCount="
                          + mReferenceCount);
              }
  
              if (mWifiNanIface != null) {
                  mReferenceCount++;
                  return;
              }
              if (mHalDeviceManager == null) {
                  Log.e(TAG, "tryToGetAware: mHalDeviceManager is null!?");
                  awareIsDown();
                  return;
              }
  
              mInterfaceDestroyedListener = new InterfaceDestroyedListener();
              IWifiNanIface iface = mHalDeviceManager.createNanIface(mInterfaceDestroyedListener,
                      mHandler);
              if (iface == null) {
                  Log.e(TAG, "Was not able to obtain an IWifiNanIface (even though enabled!?)");
                  awareIsDown();
              } else {
                  if (mDbg) Log.v(TAG, "Obtained an IWifiNanIface");
  
                  try {
                      android.hardware.wifi.V1_2.IWifiNanIface iface12 = mockableCastTo_1_2(iface);
                      WifiStatus status;
                      if (iface12 == null) {
                          mWifiAwareNativeCallback.mIsHal12OrLater = false;
                          status = iface.registerEventCallback(mWifiAwareNativeCallback);
                      } else {
                          mWifiAwareNativeCallback.mIsHal12OrLater = true;
                          status = iface12.registerEventCallback_1_2(mWifiAwareNativeCallback);
                      }
                      if (status.code != WifiStatusCode.SUCCESS) {
                          Log.e(TAG, "IWifiNanIface.registerEventCallback error: " + statusString(
                                  status));
                          mHalDeviceManager.removeIface(iface);
                          awareIsDown();
                          return;
                      }
                  } catch (RemoteException e) {
                      Log.e(TAG, "IWifiNanIface.registerEventCallback exception: " + e);
                      awareIsDown();
                      return;
                  }
                  mWifiNanIface = iface;
                  mReferenceCount = 1;
              }
          }
      }
  
      /**
       * Release the HAL NAN interface.
       */
      public void releaseAware() {
          if (mDbg) {
              Log.d(TAG, "releaseAware: mWifiNanIface=" + mWifiNanIface + ", mReferenceCount="
                      + mReferenceCount);
          }
  
          if (mWifiNanIface == null) {
              return;
          }
          if (mHalDeviceManager == null) {
              Log.e(TAG, "releaseAware: mHalDeviceManager is null!?");
              return;
          }
  
          synchronized (mLock) {
              mReferenceCount--;
              if (mReferenceCount != 0) {
                  return;
              }
              mInterfaceDestroyedListener.active = false;
              mInterfaceDestroyedListener = null;
              mHalDeviceManager.removeIface(mWifiNanIface);
              mWifiNanIface = null;
          }
      }
  
      private void awareIsDown() {
          synchronized (mLock) {
              if (mDbg) {
                  Log.d(TAG, "awareIsDown: mWifiNanIface=" + mWifiNanIface + ", mReferenceCount ="
                          + mReferenceCount);
              }
              mWifiNanIface = null;
              mReferenceCount = 0;
              mWifiAwareStateManager.disableUsage();
          }
      }
  
      private class InterfaceDestroyedListener implements
              HalDeviceManager.InterfaceDestroyedListener {
          public boolean active = true;
  
          @Override
          public void onDestroyed(@NonNull String ifaceName) {
              if (mDbg) {
                  Log.d(TAG, "Interface was destroyed: mWifiNanIface=" + mWifiNanIface + ", active="
                          + active);
              }
              if (active && mWifiNanIface != null) {
                  awareIsDown();
              } // else: we released it locally so no need to disable usage
          }
      }
  
      private class InterfaceAvailableForRequestListener implements
              HalDeviceManager.InterfaceAvailableForRequestListener {
          @Override
          public void onAvailabilityChanged(boolean isAvailable) {
              if (mDbg) {
                  Log.d(TAG, "Interface availability = " + isAvailable + ", mWifiNanIface="
                          + mWifiNanIface);
              }
              synchronized (mLock) {
                  if (isAvailable) {
                      mWifiAwareStateManager.enableUsage();
                  } else if (mWifiNanIface == null) { // not available could mean already have NAN
                      mWifiAwareStateManager.disableUsage();
                  }
              }
          }
      }
  
      private static String statusString(WifiStatus status) {
          if (status == null) {
              return "status=null";
          }
          StringBuilder sb = new StringBuilder();
          sb.append(status.code).append(" (").append(status.description).append(")");
          return sb.toString();
      }
  
      /**
       * Dump the internal state of the class.
       */
      public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
          pw.println("WifiAwareNativeManager:");
          pw.println("  mWifiNanIface: " + mWifiNanIface);
          pw.println("  mReferenceCount: " + mReferenceCount);
          mWifiAwareNativeCallback.dump(fd, pw, args);
          mHalDeviceManager.dump(fd, pw, args);
      }
  }
  