content: /*
   * Copyright (C) 2018 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.rtt;
  
  import static android.Manifest.permission.ACCESS_FINE_LOCATION;
  import static android.Manifest.permission.ACCESS_WIFI_STATE;
  import static android.Manifest.permission.CHANGE_WIFI_STATE;
  import static android.Manifest.permission.LOCATION_HARDWARE;
  
  import android.annotation.CallbackExecutor;
  import android.annotation.NonNull;
  import android.annotation.Nullable;
  import android.annotation.RequiresPermission;
  import android.annotation.SdkConstant;
  import android.annotation.SystemApi;
  import android.annotation.SystemService;
  import android.content.Context;
  import android.os.Binder;
  import android.os.RemoteException;
  import android.os.WorkSource;
  import android.util.Log;
  
  import java.util.List;
  import java.util.concurrent.Executor;
  
  /**
   * This class provides the primary API for measuring distance (range) to other devices using the
   * IEEE 802.11mc Wi-Fi Round Trip Time (RTT) technology.
   * <p>
   * The devices which can be ranged include:
   * <li>Access Points (APs)
   * <li>Wi-Fi Aware peers
   * <p>
   * Ranging requests are triggered using
   * {@link #startRanging(RangingRequest, Executor, RangingResultCallback)}. Results (in case of
   * successful operation) are returned in the {@link RangingResultCallback#onRangingResults(List)}
   * callback.
   * <p>
   *     Wi-Fi RTT may not be usable at some points, e.g. when Wi-Fi is disabled. To validate that
   *     the functionality is available use the {@link #isAvailable()} function. To track
   *     changes in RTT usability register for the {@link #ACTION_WIFI_RTT_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.
   */
  @SystemService(Context.WIFI_RTT_RANGING_SERVICE)
  public class WifiRttManager {
      private static final String TAG = "WifiRttManager";
      private static final boolean VDBG = false;
  
      private final Context mContext;
      private final IWifiRttManager mService;
  
      /**
       * Broadcast intent action to indicate that the state of Wi-Fi RTT 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 RTT.
       * <p>Note: The broadcast is only delivered to registered receivers - no manifest registered
       * components will be launched.
       */
      @SdkConstant(SdkConstant.SdkConstantType.BROADCAST_INTENT_ACTION)
      public static final String ACTION_WIFI_RTT_STATE_CHANGED =
              "android.net.wifi.rtt.action.WIFI_RTT_STATE_CHANGED";
  
      /** @hide */
      public WifiRttManager(Context context, IWifiRttManager service) {
          mContext = context;
          mService = service;
      }
  
      /**
       * Returns the current status of RTT API: whether or not RTT is available. To track
       * changes in the state of RTT API register for the
       * {@link #ACTION_WIFI_RTT_STATE_CHANGED} broadcast.
       * <p>Note: availability of RTT does not mean that the app can use the API. The app's
       * permissions and platform Location Mode are validated at run-time.
       *
       * @return A boolean indicating whether the app can use the RTT API at this time (true) or
       * not (false).
       */
      public boolean isAvailable() {
          try {
              return mService.isAvailable();
          } catch (RemoteException e) {
              throw e.rethrowFromSystemServer();
          }
      }
  
      /**
       * Initiate a request to range to a set of devices specified in the {@link RangingRequest}.
       * Results will be returned in the {@link RangingResultCallback} set of callbacks.
       *
       * @param request  A request specifying a set of devices whose distance measurements are
       *                 requested.
       * @param executor The Executor on which to run the callback.
       * @param callback A callback for the result of the ranging request.
       */
      @RequiresPermission(allOf = {ACCESS_FINE_LOCATION, CHANGE_WIFI_STATE, ACCESS_WIFI_STATE})
      public void startRanging(@NonNull RangingRequest request,
              @NonNull @CallbackExecutor Executor executor, @NonNull RangingResultCallback callback) {
          startRanging(null, request, executor, callback);
      }
  
      /**
       * Initiate a request to range to a set of devices specified in the {@link RangingRequest}.
       * Results will be returned in the {@link RangingResultCallback} set of callbacks.
       *
       * @param workSource A mechanism to specify an alternative work-source for the request.
       * @param request  A request specifying a set of devices whose distance measurements are
       *                 requested.
       * @param executor The Executor on which to run the callback.
       * @param callback A callback for the result of the ranging request.
       *
       * @hide
       */
      @SystemApi
      @RequiresPermission(allOf = {LOCATION_HARDWARE, ACCESS_FINE_LOCATION, CHANGE_WIFI_STATE,
              ACCESS_WIFI_STATE})
      public void startRanging(@Nullable WorkSource workSource, @NonNull RangingRequest request,
              @NonNull @CallbackExecutor Executor executor, @NonNull RangingResultCallback callback) {
          if (VDBG) {
              Log.v(TAG, "startRanging: workSource=" + workSource + ", request=" + request
                      + ", callback=" + callback + ", executor=" + executor);
          }
  
          if (executor == null) {
              throw new IllegalArgumentException("Null executor provided");
          }
          if (callback == null) {
              throw new IllegalArgumentException("Null callback provided");
          }
  
          Binder binder = new Binder();
          try {
              mService.startRanging(binder, mContext.getOpPackageName(), workSource, request,
                      new IRttCallback.Stub() {
                          @Override
                          public void onRangingFailure(int status) throws RemoteException {
                              clearCallingIdentity();
                              executor.execute(() -> callback.onRangingFailure(status));
                          }
  
                          @Override
                          public void onRangingResults(List<RangingResult> results)
                                  throws RemoteException {
                              clearCallingIdentity();
                              executor.execute(() -> callback.onRangingResults(results));
                          }
                      });
          } catch (RemoteException e) {
              throw e.rethrowFromSystemServer();
          }
      }
  
      /**
       * Cancel all ranging requests for the specified work sources. The requests have been requested
       * using {@link #startRanging(WorkSource, RangingRequest, Executor, RangingResultCallback)}.
       *
       * @param workSource The work-sources of the requesters.
       *
       * @hide
       */
      @SystemApi
      @RequiresPermission(allOf = {LOCATION_HARDWARE})
      public void cancelRanging(@Nullable WorkSource workSource) {
          if (VDBG) {
              Log.v(TAG, "cancelRanging: workSource=" + workSource);
          }
  
          try {
              mService.cancelRanging(workSource);
          } catch (RemoteException e) {
              throw e.rethrowFromSystemServer();
          }
      }
  }
  