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.app.AppOpsManager;
  import android.content.Context;
  import android.net.wifi.aware.ConfigRequest;
  import android.net.wifi.aware.IWifiAwareEventCallback;
  import android.os.RemoteException;
  import android.util.Log;
  import android.util.SparseArray;
  
  import com.android.server.wifi.util.WifiPermissionsUtil;
  
  import libcore.util.HexEncoding;
  
  import java.io.FileDescriptor;
  import java.io.PrintWriter;
  import java.util.Arrays;
  
  /**
   * Manages the service-side Aware state of an individual "client". A client
   * corresponds to a single instantiation of the WifiAwareManager - there could be
   * multiple ones per UID/process (each of which is a separate client with its
   * own session namespace). The client state is primarily: (1) callback (a
   * singleton per client) through which Aware-wide events are called, and (2) a set
   * of discovery sessions (publish and/or subscribe) which are created through
   * this client and whose lifetime is tied to the lifetime of the client.
   */
  public class WifiAwareClientState {
      private static final String TAG = "WifiAwareClientState";
      private static final boolean VDBG = false; // STOPSHIP if true
      /* package */ boolean mDbg = false;
  
      /* package */ static final int CLUSTER_CHANGE_EVENT_STARTED = 0;
      /* package */ static final int CLUSTER_CHANGE_EVENT_JOINED = 1;
  
      private final Context mContext;
      private final IWifiAwareEventCallback mCallback;
      private final SparseArray<WifiAwareDiscoverySessionState> mSessions = new SparseArray<>();
  
      private final int mClientId;
      private ConfigRequest mConfigRequest;
      private final int mUid;
      private final int mPid;
      private final String mCallingPackage;
      private final boolean mNotifyIdentityChange;
      private final WifiPermissionsUtil mWifiPermissionsUtil;
  
      private final AppOpsManager mAppOps;
      private final long mCreationTime;
  
      private static final byte[] ALL_ZERO_MAC = new byte[] {0, 0, 0, 0, 0, 0};
      private byte[] mLastDiscoveryInterfaceMac = ALL_ZERO_MAC;
  
      public WifiAwareClientState(Context context, int clientId, int uid, int pid,
              String callingPackage, IWifiAwareEventCallback callback, ConfigRequest configRequest,
              boolean notifyIdentityChange, long creationTime,
              WifiPermissionsUtil wifiPermissionsUtil) {
          mContext = context;
          mClientId = clientId;
          mUid = uid;
          mPid = pid;
          mCallingPackage = callingPackage;
          mCallback = callback;
          mConfigRequest = configRequest;
          mNotifyIdentityChange = notifyIdentityChange;
  
          mAppOps = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
          mCreationTime = creationTime;
          mWifiPermissionsUtil = wifiPermissionsUtil;
      }
  
      /**
       * Destroy the current client - corresponds to a disconnect() request from
       * the client. Destroys all discovery sessions belonging to this client.
       */
      public void destroy() {
          for (int i = 0; i < mSessions.size(); ++i) {
              mSessions.valueAt(i).terminate();
          }
          mSessions.clear();
          mConfigRequest = null;
      }
  
      public ConfigRequest getConfigRequest() {
          return mConfigRequest;
      }
  
      public int getClientId() {
          return mClientId;
      }
  
      public int getUid() {
          return mUid;
      }
  
      public String getCallingPackage() {
          return mCallingPackage;
      }
  
      public boolean getNotifyIdentityChange() {
          return mNotifyIdentityChange;
      }
  
      public long getCreationTime() {
          return mCreationTime;
      }
  
      public SparseArray<WifiAwareDiscoverySessionState> getSessions() {
          return mSessions;
      }
  
      /**
       * Searches the discovery sessions of this client and returns the one
       * corresponding to the publish/subscribe ID. Used on callbacks from HAL to
       * map callbacks to the correct discovery session.
       *
       * @param pubSubId The publish/subscribe match session ID.
       * @return Aware session corresponding to the requested ID.
       */
      public WifiAwareDiscoverySessionState getAwareSessionStateForPubSubId(int pubSubId) {
          for (int i = 0; i < mSessions.size(); ++i) {
              WifiAwareDiscoverySessionState session = mSessions.valueAt(i);
              if (session.isPubSubIdSession(pubSubId)) {
                  return session;
              }
          }
  
          return null;
      }
  
      /**
       * Add the session to the client database.
       *
       * @param session Session to be added.
       */
      public void addSession(WifiAwareDiscoverySessionState session) {
          int sessionId = session.getSessionId();
          if (mSessions.get(sessionId) != null) {
              Log.w(TAG, "createSession: sessionId already exists (replaced) - " + sessionId);
          }
  
          mSessions.put(sessionId, session);
      }
  
      /**
       * Remove the specified session from the client database - without doing a
       * terminate on the session. The assumption is that it is already
       * terminated.
       *
       * @param sessionId The session ID of the session to be removed.
       */
      public void removeSession(int sessionId) {
          if (mSessions.get(sessionId) == null) {
              Log.e(TAG, "removeSession: sessionId doesn't exist - " + sessionId);
              return;
          }
  
          mSessions.delete(sessionId);
      }
  
      /**
       * Destroy the discovery session: terminates discovery and frees up
       * resources.
       *
       * @param sessionId The session ID of the session to be destroyed.
       */
      public WifiAwareDiscoverySessionState terminateSession(int sessionId) {
          WifiAwareDiscoverySessionState session = mSessions.get(sessionId);
          if (session == null) {
              Log.e(TAG, "terminateSession: sessionId doesn't exist - " + sessionId);
              return null;
          }
  
          session.terminate();
          mSessions.delete(sessionId);
  
          return session;
      }
  
      /**
       * Retrieve a session.
       *
       * @param sessionId Session ID of the session to be retrieved.
       * @return Session or null if there's no session corresponding to the
       *         sessionId.
       */
      public WifiAwareDiscoverySessionState getSession(int sessionId) {
          return mSessions.get(sessionId);
      }
  
      /**
       * Called to dispatch the Aware interface address change to the client - as an
       * identity change (interface address information not propagated to client -
       * privacy concerns).
       *
       * @param mac The new MAC address of the discovery interface - optionally propagated to the
       *            client.
       */
      public void onInterfaceAddressChange(byte[] mac) {
          if (VDBG) {
              Log.v(TAG,
                      "onInterfaceAddressChange: mClientId=" + mClientId + ", mNotifyIdentityChange="
                              + mNotifyIdentityChange + ", mac=" + String.valueOf(
                              HexEncoding.encode(mac)) + ", mLastDiscoveryInterfaceMac="
                              + String.valueOf(HexEncoding.encode(mLastDiscoveryInterfaceMac)));
          }
          if (mNotifyIdentityChange && !Arrays.equals(mac, mLastDiscoveryInterfaceMac)) {
              try {
                  boolean hasPermission = mWifiPermissionsUtil.checkCallersLocationPermission(
                          mCallingPackage, mUid, /* coarseForTargetSdkLessThanQ */ true);
                  if (VDBG) Log.v(TAG, "hasPermission=" + hasPermission);
                  mCallback.onIdentityChanged(hasPermission ? mac : ALL_ZERO_MAC);
              } catch (RemoteException e) {
                  Log.w(TAG, "onIdentityChanged: RemoteException - ignored: " + e);
              }
          }
  
          mLastDiscoveryInterfaceMac = mac;
      }
  
      /**
       * Called to dispatch the Aware cluster change (due to joining of a new
       * cluster or starting a cluster) to the client - as an identity change
       * (interface address information not propagated to client - privacy
       * concerns). Dispatched if the client registered for the identity changed
       * event.
       *
       * @param mac The cluster ID of the cluster started or joined.
       * @param currentDiscoveryInterfaceMac The MAC address of the discovery interface.
       */
      public void onClusterChange(int flag, byte[] mac, byte[] currentDiscoveryInterfaceMac) {
          if (VDBG) {
              Log.v(TAG,
                      "onClusterChange: mClientId=" + mClientId + ", mNotifyIdentityChange="
                              + mNotifyIdentityChange + ", mac=" + String.valueOf(
                              HexEncoding.encode(mac)) + ", currentDiscoveryInterfaceMac="
                              + String.valueOf(HexEncoding.encode(currentDiscoveryInterfaceMac))
                              + ", mLastDiscoveryInterfaceMac=" + String.valueOf(
                              HexEncoding.encode(mLastDiscoveryInterfaceMac)));
          }
          if (mNotifyIdentityChange && !Arrays.equals(currentDiscoveryInterfaceMac,
                  mLastDiscoveryInterfaceMac)) {
              try {
                  boolean hasPermission = mWifiPermissionsUtil.checkCallersLocationPermission(
                          mCallingPackage, mUid, /* coarseForTargetSdkLessThanQ */ true);
                  if (VDBG) Log.v(TAG, "hasPermission=" + hasPermission);
                  mCallback.onIdentityChanged(
                          hasPermission ? currentDiscoveryInterfaceMac : ALL_ZERO_MAC);
              } catch (RemoteException e) {
                  Log.w(TAG, "onIdentityChanged: RemoteException - ignored: " + e);
              }
          }
  
          mLastDiscoveryInterfaceMac = currentDiscoveryInterfaceMac;
      }
  
      /**
       * Dump the internal state of the class.
       */
      public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
          pw.println("AwareClientState:");
          pw.println("  mClientId: " + mClientId);
          pw.println("  mConfigRequest: " + mConfigRequest);
          pw.println("  mNotifyIdentityChange: " + mNotifyIdentityChange);
          pw.println("  mCallback: " + mCallback);
          pw.println("  mSessions: [" + mSessions + "]");
          for (int i = 0; i < mSessions.size(); ++i) {
              mSessions.valueAt(i).dump(fd, pw, args);
          }
      }
  }
  