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 android.net.wifi.aware;
  
  import android.annotation.IntDef;
  import android.annotation.NonNull;
  import android.annotation.Nullable;
  import android.annotation.SdkConstant;
  import android.annotation.SdkConstant.SdkConstantType;
  import android.annotation.SystemService;
  import android.content.Context;
  import android.net.ConnectivityManager;
  import android.net.NetworkRequest;
  import android.net.NetworkSpecifier;
  import android.os.Binder;
  import android.os.Build;
  import android.os.Bundle;
  import android.os.Handler;
  import android.os.Looper;
  import android.os.Message;
  import android.os.Process;
  import android.os.RemoteException;
  import android.util.Log;
  
  import libcore.util.HexEncoding;
  
  import java.lang.annotation.Retention;
  import java.lang.annotation.RetentionPolicy;
  import java.lang.ref.WeakReference;
  import java.nio.BufferOverflowException;
  import java.util.List;
  
  /**
   * This class provides the primary API for managing Wi-Fi Aware operations:
   * discovery and peer-to-peer data connections.
   * <p>
   * The class provides access to:
   * <ul>
   * <li>Initialize a Aware cluster (peer-to-peer synchronization). Refer to
   * {@link #attach(AttachCallback, Handler)}.
   * <li>Create discovery sessions (publish or subscribe sessions). Refer to
   * {@link WifiAwareSession#publish(PublishConfig, DiscoverySessionCallback, Handler)} and
   * {@link WifiAwareSession#subscribe(SubscribeConfig, DiscoverySessionCallback, Handler)}.
   * <li>Create a Aware network specifier to be used with
   * {@link ConnectivityManager#requestNetwork(NetworkRequest, ConnectivityManager.NetworkCallback)}
   * to set-up a Aware connection with a peer. Refer to {@link WifiAwareNetworkSpecifier.Builder}.
   * </ul>
   * <p>
   *     Aware may not be usable when Wi-Fi is disabled (and other conditions). To validate that
   *     the functionality is available use the {@link #isAvailable()} function. To track
   *     changes in Aware usability register for the {@link #ACTION_WIFI_AWARE_STATE_CHANGED}
   *     broadcast. Note that this broadcast is not sticky - you should register for it and then
   *     check the above API to avoid a race condition.
   * <p>
   *     An application must use {@link #attach(AttachCallback, Handler)} to initialize a
   *     Aware cluster - before making any other Aware operation. Aware cluster membership is a
   *     device-wide operation - the API guarantees that the device is in a cluster or joins a
   *     Aware cluster (or starts one if none can be found). Information about attach success (or
   *     failure) are returned in callbacks of {@link AttachCallback}. Proceed with Aware
   *     discovery or connection setup only after receiving confirmation that Aware attach
   *     succeeded - {@link AttachCallback#onAttached(WifiAwareSession)}. When an
   *     application is finished using Aware it <b>must</b> use the
   *     {@link WifiAwareSession#close()} API to indicate to the Aware service that the device
   *     may detach from the Aware cluster. The device will actually disable Aware once the last
   *     application detaches.
   * <p>
   *     Once a Aware attach is confirmed use the
   *     {@link WifiAwareSession#publish(PublishConfig, DiscoverySessionCallback, Handler)}
   *     or
   *     {@link WifiAwareSession#subscribe(SubscribeConfig, DiscoverySessionCallback,
   *     Handler)} to create publish or subscribe Aware discovery sessions. Events are called on the
   *     provided callback object {@link DiscoverySessionCallback}. Specifically, the
   *     {@link DiscoverySessionCallback#onPublishStarted(PublishDiscoverySession)}
   *     and
   *     {@link DiscoverySessionCallback#onSubscribeStarted(
   *SubscribeDiscoverySession)}
   *     return {@link PublishDiscoverySession} and
   *     {@link SubscribeDiscoverySession}
   *     objects respectively on which additional session operations can be performed, e.g. updating
   *     the session {@link PublishDiscoverySession#updatePublish(PublishConfig)} and
   *     {@link SubscribeDiscoverySession#updateSubscribe(SubscribeConfig)}. Sessions can
   *     also be used to send messages using the
   *     {@link DiscoverySession#sendMessage(PeerHandle, int, byte[])} APIs. When an
   *     application is finished with a discovery session it <b>must</b> terminate it using the
   *     {@link DiscoverySession#close()} API.
   * <p>
   *    Creating connections between Aware devices is managed by the standard
   *    {@link ConnectivityManager#requestNetwork(NetworkRequest,
   *    ConnectivityManager.NetworkCallback)}.
   *    The {@link NetworkRequest} object should be constructed with:
   *    <ul>
   *        <li>{@link NetworkRequest.Builder#addTransportType(int)} of
   *        {@link android.net.NetworkCapabilities#TRANSPORT_WIFI_AWARE}.
   *        <li>{@link NetworkRequest.Builder#setNetworkSpecifier(String)} using
   *        {@link WifiAwareNetworkSpecifier.Builder}.
   *    </ul>
   */
  @SystemService(Context.WIFI_AWARE_SERVICE)
  public class WifiAwareManager {
      private static final String TAG = "WifiAwareManager";
      private static final boolean DBG = false;
      private static final boolean VDBG = false; // STOPSHIP if true
  
      /**
       * Broadcast intent action to indicate that the state of Wi-Fi Aware availability has changed.
       * Use the {@link #isAvailable()} to query the current status.
       * This broadcast is <b>not</b> sticky, use the {@link #isAvailable()} API after registering
       * the broadcast to check the current state of Wi-Fi Aware.
       * <p>Note: The broadcast is only delivered to registered receivers - no manifest registered
       * components will be launched.
       */
      @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
      public static final String ACTION_WIFI_AWARE_STATE_CHANGED =
              "android.net.wifi.aware.action.WIFI_AWARE_STATE_CHANGED";
  
      /** @hide */
      @IntDef({
              WIFI_AWARE_DATA_PATH_ROLE_INITIATOR, WIFI_AWARE_DATA_PATH_ROLE_RESPONDER})
      @Retention(RetentionPolicy.SOURCE)
      public @interface DataPathRole {
      }
  
      /**
       * Connection creation role is that of INITIATOR. Used to create a network specifier string
       * when requesting a Aware network.
       *
       * @see WifiAwareSession#createNetworkSpecifierOpen(int, byte[])
       * @see WifiAwareSession#createNetworkSpecifierPassphrase(int, byte[], String)
       */
      public static final int WIFI_AWARE_DATA_PATH_ROLE_INITIATOR = 0;
  
      /**
       * Connection creation role is that of RESPONDER. Used to create a network specifier string
       * when requesting a Aware network.
       *
       * @see WifiAwareSession#createNetworkSpecifierOpen(int, byte[])
       * @see WifiAwareSession#createNetworkSpecifierPassphrase(int, byte[], String)
       */
      public static final int WIFI_AWARE_DATA_PATH_ROLE_RESPONDER = 1;
  
      private final Context mContext;
      private final IWifiAwareManager mService;
  
      private final Object mLock = new Object(); // lock access to the following vars
  
      /** @hide */
      public WifiAwareManager(Context context, IWifiAwareManager service) {
          mContext = context;
          mService = service;
      }
  
      /**
       * Returns the current status of Aware API: whether or not Aware is available. To track
       * changes in the state of Aware API register for the
       * {@link #ACTION_WIFI_AWARE_STATE_CHANGED} broadcast.
       *
       * @return A boolean indicating whether the app can use the Aware API at this time (true) or
       * not (false).
       */
      public boolean isAvailable() {
          try {
              return mService.isUsageEnabled();
          } catch (RemoteException e) {
              throw e.rethrowFromSystemServer();
          }
      }
  
      /**
       * Returns the characteristics of the Wi-Fi Aware interface: a set of parameters which specify
       * limitations on configurations, e.g. the maximum service name length.
       *
       * @return An object specifying configuration limitations of Aware.
       */
      public Characteristics getCharacteristics() {
          try {
              return mService.getCharacteristics();
          } catch (RemoteException e) {
              throw e.rethrowFromSystemServer();
          }
      }
  
      /**
       * Attach to the Wi-Fi Aware service - enabling the application to create discovery sessions or
       * create connections to peers. The device will attach to an existing cluster if it can find
       * one or create a new cluster (if it is the first to enable Aware in its vicinity). Results
       * (e.g. successful attach to a cluster) are provided to the {@code attachCallback} object.
       * An application <b>must</b> call {@link WifiAwareSession#close()} when done with the
       * Wi-Fi Aware object.
       * <p>
       * Note: a Aware cluster is a shared resource - if the device is already attached to a cluster
       * then this function will simply indicate success immediately using the same {@code
       * attachCallback}.
       *
       * @param attachCallback A callback for attach events, extended from
       * {@link AttachCallback}.
       * @param handler The Handler on whose thread to execute the callbacks of the {@code
       * attachCallback} object. If a null is provided then the application's main thread will be
       *                used.
       */
      public void attach(@NonNull AttachCallback attachCallback, @Nullable Handler handler) {
          attach(handler, null, attachCallback, null);
      }
  
      /**
       * Attach to the Wi-Fi Aware service - enabling the application to create discovery sessions or
       * create connections to peers. The device will attach to an existing cluster if it can find
       * one or create a new cluster (if it is the first to enable Aware in its vicinity). Results
       * (e.g. successful attach to a cluster) are provided to the {@code attachCallback} object.
       * An application <b>must</b> call {@link WifiAwareSession#close()} when done with the
       * Wi-Fi Aware object.
       * <p>
       * Note: a Aware cluster is a shared resource - if the device is already attached to a cluster
       * then this function will simply indicate success immediately using the same {@code
       * attachCallback}.
       * <p>
       * This version of the API attaches a listener to receive the MAC address of the Aware interface
       * on startup and whenever it is updated (it is randomized at regular intervals for privacy).
       * The application must have the {@link android.Manifest.permission#ACCESS_FINE_LOCATION}
       * permission to execute this attach request. Otherwise, use the
       * {@link #attach(AttachCallback, Handler)} version. Note that aside from permission
       * requirements this listener will wake up the host at regular intervals causing higher power
       * consumption, do not use it unless the information is necessary (e.g. for OOB discovery).
       *
       * @param attachCallback A callback for attach events, extended from
       * {@link AttachCallback}.
       * @param identityChangedListener A listener for changed identity, extended from
       * {@link IdentityChangedListener}.
       * @param handler The Handler on whose thread to execute the callbacks of the {@code
       * attachCallback} and {@code identityChangedListener} objects. If a null is provided then the
       *                application's main thread will be used.
       */
      public void attach(@NonNull AttachCallback attachCallback,
              @NonNull IdentityChangedListener identityChangedListener,
              @Nullable Handler handler) {
          attach(handler, null, attachCallback, identityChangedListener);
      }
  
      /** @hide */
      public void attach(Handler handler, ConfigRequest configRequest,
              AttachCallback attachCallback,
              IdentityChangedListener identityChangedListener) {
          if (VDBG) {
              Log.v(TAG, "attach(): handler=" + handler + ", callback=" + attachCallback
                      + ", configRequest=" + configRequest + ", identityChangedListener="
                      + identityChangedListener);
          }
  
          if (attachCallback == null) {
              throw new IllegalArgumentException("Null callback provided");
          }
  
          synchronized (mLock) {
              Looper looper = (handler == null) ? Looper.getMainLooper() : handler.getLooper();
  
              try {
                  Binder binder = new Binder();
                  mService.connect(binder, mContext.getOpPackageName(),
                          new WifiAwareEventCallbackProxy(this, looper, binder, attachCallback,
                                  identityChangedListener), configRequest,
                          identityChangedListener != null);
              } catch (RemoteException e) {
                  throw e.rethrowFromSystemServer();
              }
          }
      }
  
      /** @hide */
      public void disconnect(int clientId, Binder binder) {
          if (VDBG) Log.v(TAG, "disconnect()");
  
          try {
              mService.disconnect(clientId, binder);
          } catch (RemoteException e) {
              throw e.rethrowFromSystemServer();
          }
      }
  
      /** @hide */
      public void publish(int clientId, Looper looper, PublishConfig publishConfig,
              DiscoverySessionCallback callback) {
          if (VDBG) Log.v(TAG, "publish(): clientId=" + clientId + ", config=" + publishConfig);
  
          if (callback == null) {
              throw new IllegalArgumentException("Null callback provided");
          }
  
          try {
              mService.publish(mContext.getOpPackageName(), clientId, publishConfig,
                      new WifiAwareDiscoverySessionCallbackProxy(this, looper, true, callback,
                              clientId));
          } catch (RemoteException e) {
              throw e.rethrowFromSystemServer();
          }
      }
  
      /** @hide */
      public void updatePublish(int clientId, int sessionId, PublishConfig publishConfig) {
          if (VDBG) {
              Log.v(TAG, "updatePublish(): clientId=" + clientId + ",sessionId=" + sessionId
                      + ", config=" + publishConfig);
          }
  
          try {
              mService.updatePublish(clientId, sessionId, publishConfig);
          } catch (RemoteException e) {
              throw e.rethrowFromSystemServer();
          }
      }
  
      /** @hide */
      public void subscribe(int clientId, Looper looper, SubscribeConfig subscribeConfig,
              DiscoverySessionCallback callback) {
          if (VDBG) {
              if (VDBG) {
                  Log.v(TAG,
                          "subscribe(): clientId=" + clientId + ", config=" + subscribeConfig);
              }
          }
  
          if (callback == null) {
              throw new IllegalArgumentException("Null callback provided");
          }
  
          try {
              mService.subscribe(mContext.getOpPackageName(), clientId, subscribeConfig,
                      new WifiAwareDiscoverySessionCallbackProxy(this, looper, false, callback,
                              clientId));
          } catch (RemoteException e) {
              throw e.rethrowFromSystemServer();
          }
      }
  
      /** @hide */
      public void updateSubscribe(int clientId, int sessionId, SubscribeConfig subscribeConfig) {
          if (VDBG) {
              Log.v(TAG, "updateSubscribe(): clientId=" + clientId + ",sessionId=" + sessionId
                      + ", config=" + subscribeConfig);
          }
  
          try {
              mService.updateSubscribe(clientId, sessionId, subscribeConfig);
          } catch (RemoteException e) {
              throw e.rethrowFromSystemServer();
          }
      }
  
      /** @hide */
      public void terminateSession(int clientId, int sessionId) {
          if (VDBG) {
              Log.d(TAG,
                      "terminateSession(): clientId=" + clientId + ", sessionId=" + sessionId);
          }
  
          try {
              mService.terminateSession(clientId, sessionId);
          } catch (RemoteException e) {
              throw e.rethrowFromSystemServer();
          }
      }
  
      /** @hide */
      public void sendMessage(int clientId, int sessionId, PeerHandle peerHandle, byte[] message,
              int messageId, int retryCount) {
          if (peerHandle == null) {
              throw new IllegalArgumentException(
                      "sendMessage: invalid peerHandle - must be non-null");
          }
  
          if (VDBG) {
              Log.v(TAG, "sendMessage(): clientId=" + clientId + ", sessionId=" + sessionId
                      + ", peerHandle=" + peerHandle.peerId + ", messageId="
                      + messageId + ", retryCount=" + retryCount);
          }
  
          try {
              mService.sendMessage(clientId, sessionId, peerHandle.peerId, message, messageId,
                      retryCount);
          } catch (RemoteException e) {
              throw e.rethrowFromSystemServer();
          }
      }
  
      /** @hide */
      public NetworkSpecifier createNetworkSpecifier(int clientId, int role, int sessionId,
              @NonNull PeerHandle peerHandle, @Nullable byte[] pmk, @Nullable String passphrase) {
          if (VDBG) {
              Log.v(TAG, "createNetworkSpecifier: role=" + role + ", sessionId=" + sessionId
                      + ", peerHandle=" + ((peerHandle == null) ? peerHandle : peerHandle.peerId)
                      + ", pmk=" + ((pmk == null) ? "null" : "non-null")
                      + ", passphrase=" + ((passphrase == null) ? "null" : "non-null"));
          }
  
          if (!WifiAwareUtils.isLegacyVersion(mContext, Build.VERSION_CODES.Q)) {
              throw new UnsupportedOperationException(
                      "API deprecated - use WifiAwareNetworkSpecifier.Builder");
          }
  
          if (role != WIFI_AWARE_DATA_PATH_ROLE_INITIATOR
                  && role != WIFI_AWARE_DATA_PATH_ROLE_RESPONDER) {
              throw new IllegalArgumentException(
                      "createNetworkSpecifier: Invalid 'role' argument when creating a network "
                              + "specifier");
          }
          if (role == WIFI_AWARE_DATA_PATH_ROLE_INITIATOR || !WifiAwareUtils.isLegacyVersion(mContext,
                  Build.VERSION_CODES.P)) {
              if (peerHandle == null) {
                  throw new IllegalArgumentException(
                          "createNetworkSpecifier: Invalid peer handle - cannot be null");
              }
          }
  
          return new WifiAwareNetworkSpecifier(
                  (peerHandle == null) ? WifiAwareNetworkSpecifier.NETWORK_SPECIFIER_TYPE_IB_ANY_PEER
                          : WifiAwareNetworkSpecifier.NETWORK_SPECIFIER_TYPE_IB,
                  role,
                  clientId,
                  sessionId,
                  peerHandle != null ? peerHandle.peerId : 0, // 0 is an invalid peer ID
                  null, // peerMac (not used in this method)
                  pmk,
                  passphrase,
                  0, // no port info for deprecated IB APIs
                  -1, // no transport info for deprecated IB APIs
                  Process.myUid());
      }
  
      /** @hide */
      public NetworkSpecifier createNetworkSpecifier(int clientId, @DataPathRole int role,
              @NonNull byte[] peer, @Nullable byte[] pmk, @Nullable String passphrase) {
          if (VDBG) {
              Log.v(TAG, "createNetworkSpecifier: role=" + role
                      + ", pmk=" + ((pmk == null) ? "null" : "non-null")
                      + ", passphrase=" + ((passphrase == null) ? "null" : "non-null"));
          }
  
          if (role != WIFI_AWARE_DATA_PATH_ROLE_INITIATOR
                  && role != WIFI_AWARE_DATA_PATH_ROLE_RESPONDER) {
              throw new IllegalArgumentException(
                      "createNetworkSpecifier: Invalid 'role' argument when creating a network "
                              + "specifier");
          }
          if (role == WIFI_AWARE_DATA_PATH_ROLE_INITIATOR || !WifiAwareUtils.isLegacyVersion(mContext,
                  Build.VERSION_CODES.P)) {
              if (peer == null) {
                  throw new IllegalArgumentException(
                          "createNetworkSpecifier: Invalid peer MAC - cannot be null");
              }
          }
          if (peer != null && peer.length != 6) {
              throw new IllegalArgumentException("createNetworkSpecifier: Invalid peer MAC address");
          }
  
          return new WifiAwareNetworkSpecifier(
                  (peer == null) ? WifiAwareNetworkSpecifier.NETWORK_SPECIFIER_TYPE_OOB_ANY_PEER
                          : WifiAwareNetworkSpecifier.NETWORK_SPECIFIER_TYPE_OOB,
                  role,
                  clientId,
                  0, // 0 is an invalid session ID
                  0, // 0 is an invalid peer ID
                  peer,
                  pmk,
                  passphrase,
                  0, // no port info for OOB APIs
                  -1, // no transport protocol info for OOB APIs
                  Process.myUid());
      }
  
      private static class WifiAwareEventCallbackProxy extends IWifiAwareEventCallback.Stub {
          private static final int CALLBACK_CONNECT_SUCCESS = 0;
          private static final int CALLBACK_CONNECT_FAIL = 1;
          private static final int CALLBACK_IDENTITY_CHANGED = 2;
  
          private final Handler mHandler;
          private final WeakReference<WifiAwareManager> mAwareManager;
          private final Binder mBinder;
          private final Looper mLooper;
  
          /**
           * Constructs a {@link AttachCallback} using the specified looper.
           * All callbacks will delivered on the thread of the specified looper.
           *
           * @param looper The looper on which to execute the callbacks.
           */
          WifiAwareEventCallbackProxy(WifiAwareManager mgr, Looper looper, Binder binder,
                  final AttachCallback attachCallback,
                  final IdentityChangedListener identityChangedListener) {
              mAwareManager = new WeakReference<>(mgr);
              mLooper = looper;
              mBinder = binder;
  
              if (VDBG) Log.v(TAG, "WifiAwareEventCallbackProxy ctor: looper=" + looper);
              mHandler = new Handler(looper) {
                  @Override
                  public void handleMessage(Message msg) {
                      if (DBG) {
                          Log.d(TAG, "WifiAwareEventCallbackProxy: What=" + msg.what + ", msg="
                                  + msg);
                      }
  
                      WifiAwareManager mgr = mAwareManager.get();
                      if (mgr == null) {
                          Log.w(TAG, "WifiAwareEventCallbackProxy: handleMessage post GC");
                          return;
                      }
  
                      switch (msg.what) {
                          case CALLBACK_CONNECT_SUCCESS:
                              attachCallback.onAttached(
                                      new WifiAwareSession(mgr, mBinder, msg.arg1));
                              break;
                          case CALLBACK_CONNECT_FAIL:
                              mAwareManager.clear();
                              attachCallback.onAttachFailed();
                              break;
                          case CALLBACK_IDENTITY_CHANGED:
                              if (identityChangedListener == null) {
                                  Log.e(TAG, "CALLBACK_IDENTITY_CHANGED: null listener.");
                              } else {
                                  identityChangedListener.onIdentityChanged((byte[]) msg.obj);
                              }
                              break;
                      }
                  }
              };
          }
  
          @Override
          public void onConnectSuccess(int clientId) {
              if (VDBG) Log.v(TAG, "onConnectSuccess");
  
              Message msg = mHandler.obtainMessage(CALLBACK_CONNECT_SUCCESS);
              msg.arg1 = clientId;
              mHandler.sendMessage(msg);
          }
  
          @Override
          public void onConnectFail(int reason) {
              if (VDBG) Log.v(TAG, "onConnectFail: reason=" + reason);
  
              Message msg = mHandler.obtainMessage(CALLBACK_CONNECT_FAIL);
              msg.arg1 = reason;
              mHandler.sendMessage(msg);
          }
  
          @Override
          public void onIdentityChanged(byte[] mac) {
              if (VDBG) Log.v(TAG, "onIdentityChanged: mac=" + new String(HexEncoding.encode(mac)));
  
              Message msg = mHandler.obtainMessage(CALLBACK_IDENTITY_CHANGED);
              msg.obj = mac;
              mHandler.sendMessage(msg);
          }
      }
  
      private static class WifiAwareDiscoverySessionCallbackProxy extends
              IWifiAwareDiscoverySessionCallback.Stub {
          private static final int CALLBACK_SESSION_STARTED = 0;
          private static final int CALLBACK_SESSION_CONFIG_SUCCESS = 1;
          private static final int CALLBACK_SESSION_CONFIG_FAIL = 2;
          private static final int CALLBACK_SESSION_TERMINATED = 3;
          private static final int CALLBACK_MATCH = 4;
          private static final int CALLBACK_MESSAGE_SEND_SUCCESS = 5;
          private static final int CALLBACK_MESSAGE_SEND_FAIL = 6;
          private static final int CALLBACK_MESSAGE_RECEIVED = 7;
          private static final int CALLBACK_MATCH_WITH_DISTANCE = 8;
  
          private static final String MESSAGE_BUNDLE_KEY_MESSAGE = "message";
          private static final String MESSAGE_BUNDLE_KEY_MESSAGE2 = "message2";
  
          private final WeakReference<WifiAwareManager> mAwareManager;
          private final boolean mIsPublish;
          private final DiscoverySessionCallback mOriginalCallback;
          private final int mClientId;
  
          private final Handler mHandler;
          private DiscoverySession mSession;
  
          WifiAwareDiscoverySessionCallbackProxy(WifiAwareManager mgr, Looper looper,
                  boolean isPublish, DiscoverySessionCallback originalCallback,
                  int clientId) {
              mAwareManager = new WeakReference<>(mgr);
              mIsPublish = isPublish;
              mOriginalCallback = originalCallback;
              mClientId = clientId;
  
              if (VDBG) {
                  Log.v(TAG, "WifiAwareDiscoverySessionCallbackProxy ctor: isPublish=" + isPublish);
              }
  
              mHandler = new Handler(looper) {
                  @Override
                  public void handleMessage(Message msg) {
                      if (DBG) Log.d(TAG, "What=" + msg.what + ", msg=" + msg);
  
                      if (mAwareManager.get() == null) {
                          Log.w(TAG, "WifiAwareDiscoverySessionCallbackProxy: handleMessage post GC");
                          return;
                      }
  
                      switch (msg.what) {
                          case CALLBACK_SESSION_STARTED:
                              onProxySessionStarted(msg.arg1);
                              break;
                          case CALLBACK_SESSION_CONFIG_SUCCESS:
                              mOriginalCallback.onSessionConfigUpdated();
                              break;
                          case CALLBACK_SESSION_CONFIG_FAIL:
                              mOriginalCallback.onSessionConfigFailed();
                              if (mSession == null) {
                                  /*
                                   * creation failed (as opposed to update
                                   * failing)
                                   */
                                  mAwareManager.clear();
                              }
                              break;
                          case CALLBACK_SESSION_TERMINATED:
                              onProxySessionTerminated(msg.arg1);
                              break;
                          case CALLBACK_MATCH:
                          case CALLBACK_MATCH_WITH_DISTANCE:
                              {
                              List<byte[]> matchFilter = null;
                              byte[] arg = msg.getData().getByteArray(MESSAGE_BUNDLE_KEY_MESSAGE2);
                              try {
                                  matchFilter = new TlvBufferUtils.TlvIterable(0, 1, arg).toList();
                              } catch (BufferOverflowException e) {
                                  matchFilter = null;
                                  Log.e(TAG, "onServiceDiscovered: invalid match filter byte array '"
                                          + new String(HexEncoding.encode(arg))
                                          + "' - cannot be parsed: e=" + e);
                              }
                              if (msg.what == CALLBACK_MATCH) {
                                  mOriginalCallback.onServiceDiscovered(new PeerHandle(msg.arg1),
                                          msg.getData().getByteArray(MESSAGE_BUNDLE_KEY_MESSAGE),
                                          matchFilter);
                              } else {
                                  mOriginalCallback.onServiceDiscoveredWithinRange(
                                          new PeerHandle(msg.arg1),
                                          msg.getData().getByteArray(MESSAGE_BUNDLE_KEY_MESSAGE),
                                          matchFilter, msg.arg2);
                              }
                              break;
                          }
                          case CALLBACK_MESSAGE_SEND_SUCCESS:
                              mOriginalCallback.onMessageSendSucceeded(msg.arg1);
                              break;
                          case CALLBACK_MESSAGE_SEND_FAIL:
                              mOriginalCallback.onMessageSendFailed(msg.arg1);
                              break;
                          case CALLBACK_MESSAGE_RECEIVED:
                              mOriginalCallback.onMessageReceived(new PeerHandle(msg.arg1),
                                      (byte[]) msg.obj);
                              break;
                      }
                  }
              };
          }
  
          @Override
          public void onSessionStarted(int sessionId) {
              if (VDBG) Log.v(TAG, "onSessionStarted: sessionId=" + sessionId);
  
              Message msg = mHandler.obtainMessage(CALLBACK_SESSION_STARTED);
              msg.arg1 = sessionId;
              mHandler.sendMessage(msg);
          }
  
          @Override
          public void onSessionConfigSuccess() {
              if (VDBG) Log.v(TAG, "onSessionConfigSuccess");
  
              Message msg = mHandler.obtainMessage(CALLBACK_SESSION_CONFIG_SUCCESS);
              mHandler.sendMessage(msg);
          }
  
          @Override
          public void onSessionConfigFail(int reason) {
              if (VDBG) Log.v(TAG, "onSessionConfigFail: reason=" + reason);
  
              Message msg = mHandler.obtainMessage(CALLBACK_SESSION_CONFIG_FAIL);
              msg.arg1 = reason;
              mHandler.sendMessage(msg);
          }
  
          @Override
          public void onSessionTerminated(int reason) {
              if (VDBG) Log.v(TAG, "onSessionTerminated: reason=" + reason);
  
              Message msg = mHandler.obtainMessage(CALLBACK_SESSION_TERMINATED);
              msg.arg1 = reason;
              mHandler.sendMessage(msg);
          }
  
          private void onMatchCommon(int messageType, int peerId, byte[] serviceSpecificInfo,
                  byte[] matchFilter, int distanceMm) {
              Bundle data = new Bundle();
              data.putByteArray(MESSAGE_BUNDLE_KEY_MESSAGE, serviceSpecificInfo);
              data.putByteArray(MESSAGE_BUNDLE_KEY_MESSAGE2, matchFilter);
  
              Message msg = mHandler.obtainMessage(messageType);
              msg.arg1 = peerId;
              msg.arg2 = distanceMm;
              msg.setData(data);
              mHandler.sendMessage(msg);
          }
  
          @Override
          public void onMatch(int peerId, byte[] serviceSpecificInfo, byte[] matchFilter) {
              if (VDBG) Log.v(TAG, "onMatch: peerId=" + peerId);
  
              onMatchCommon(CALLBACK_MATCH, peerId, serviceSpecificInfo, matchFilter, 0);
          }
  
          @Override
          public void onMatchWithDistance(int peerId, byte[] serviceSpecificInfo, byte[] matchFilter,
                  int distanceMm) {
              if (VDBG) {
                  Log.v(TAG, "onMatchWithDistance: peerId=" + peerId + ", distanceMm=" + distanceMm);
              }
  
              onMatchCommon(CALLBACK_MATCH_WITH_DISTANCE, peerId, serviceSpecificInfo, matchFilter,
                      distanceMm);
          }
  
          @Override
          public void onMessageSendSuccess(int messageId) {
              if (VDBG) Log.v(TAG, "onMessageSendSuccess");
  
              Message msg = mHandler.obtainMessage(CALLBACK_MESSAGE_SEND_SUCCESS);
              msg.arg1 = messageId;
              mHandler.sendMessage(msg);
          }
  
          @Override
          public void onMessageSendFail(int messageId, int reason) {
              if (VDBG) Log.v(TAG, "onMessageSendFail: reason=" + reason);
  
              Message msg = mHandler.obtainMessage(CALLBACK_MESSAGE_SEND_FAIL);
              msg.arg1 = messageId;
              msg.arg2 = reason;
              mHandler.sendMessage(msg);
          }
  
          @Override
          public void onMessageReceived(int peerId, byte[] message) {
              if (VDBG) {
                  Log.v(TAG, "onMessageReceived: peerId=" + peerId);
              }
  
              Message msg = mHandler.obtainMessage(CALLBACK_MESSAGE_RECEIVED);
              msg.arg1 = peerId;
              msg.obj = message;
              mHandler.sendMessage(msg);
          }
  
          /*
           * Proxied methods
           */
          public void onProxySessionStarted(int sessionId) {
              if (VDBG) Log.v(TAG, "Proxy: onSessionStarted: sessionId=" + sessionId);
              if (mSession != null) {
                  Log.e(TAG,
                          "onSessionStarted: sessionId=" + sessionId + ": session already created!?");
                  throw new IllegalStateException(
                          "onSessionStarted: sessionId=" + sessionId + ": session already created!?");
              }
  
              WifiAwareManager mgr = mAwareManager.get();
              if (mgr == null) {
                  Log.w(TAG, "onProxySessionStarted: mgr GC'd");
                  return;
              }
  
              if (mIsPublish) {
                  PublishDiscoverySession session = new PublishDiscoverySession(mgr,
                          mClientId, sessionId);
                  mSession = session;
                  mOriginalCallback.onPublishStarted(session);
              } else {
                  SubscribeDiscoverySession
                          session = new SubscribeDiscoverySession(mgr, mClientId, sessionId);
                  mSession = session;
                  mOriginalCallback.onSubscribeStarted(session);
              }
          }
  
          public void onProxySessionTerminated(int reason) {
              if (VDBG) Log.v(TAG, "Proxy: onSessionTerminated: reason=" + reason);
              if (mSession != null) {
                  mSession.setTerminated();
                  mSession = null;
              } else {
                  Log.w(TAG, "Proxy: onSessionTerminated called but mSession is null!?");
              }
              mAwareManager.clear();
              mOriginalCallback.onSessionTerminated();
          }
      }
  }
  