content: /*
   * 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 com.android.server.wifi;
  
  import android.app.AppGlobals;
  import android.content.pm.IPackageManager;
  import android.net.wifi.WifiScanner;
  import android.os.Binder;
  import android.os.ShellCommand;
  
  import com.android.server.wifi.util.ApConfigUtil;
  
  import java.io.PrintWriter;
  import java.util.Arrays;
  import java.util.concurrent.ArrayBlockingQueue;
  import java.util.concurrent.TimeUnit;
  
  /**
   * Interprets and executes 'adb shell cmd wifi [args]'.
   *
   * To add new commands:
   * - onCommand: Add a case "<command>" execute. Return a 0
   *   if command executed successfully.
   * - onHelp: add a description string.
   *
   * If additional state objects are necessary add them to the
   * constructor.
   *
   * Permissions: currently root permission is required for all
   * commands. If the requirement needs to be relaxed then modify
   * the onCommand method to check for specific permissions on
   * individual commands.
   */
  public class WifiShellCommand extends ShellCommand {
      private final ClientModeImpl mClientModeImpl;
      private final WifiLockManager mWifiLockManager;
      private final WifiNetworkSuggestionsManager mWifiNetworkSuggestionsManager;
      private final WifiConfigManager mWifiConfigManager;
      private final IPackageManager mPM;
      private final WifiNative mWifiNative;
      private final HostapdHal mHostapdHal;
      private final WifiCountryCode mWifiCountryCode;
  
      WifiShellCommand(WifiInjector wifiInjector) {
          mClientModeImpl = wifiInjector.getClientModeImpl();
          mWifiLockManager = wifiInjector.getWifiLockManager();
          mWifiNetworkSuggestionsManager = wifiInjector.getWifiNetworkSuggestionsManager();
          mWifiConfigManager = wifiInjector.getWifiConfigManager();
          mPM = AppGlobals.getPackageManager();
          mHostapdHal = wifiInjector.getHostapdHal();
          mWifiNative = wifiInjector.getWifiNative();
          mWifiCountryCode = wifiInjector.getWifiCountryCode();
      }
  
      @Override
      public int onCommand(String cmd) {
          checkRootPermission();
  
          final PrintWriter pw = getOutPrintWriter();
          try {
              switch (cmd != null ? cmd : "") {
                  case "set-ipreach-disconnect": {
                      boolean enabled;
                      String nextArg = getNextArgRequired();
                      if ("enabled".equals(nextArg)) {
                          enabled = true;
                      } else if ("disabled".equals(nextArg)) {
                          enabled = false;
                      } else {
                          pw.println(
                                  "Invalid argument to 'set-ipreach-disconnect' - must be 'enabled'"
                                          + " or 'disabled'");
                          return -1;
                      }
                      mClientModeImpl.setIpReachabilityDisconnectEnabled(enabled);
                      return 0;
                  }
                  case "get-ipreach-disconnect":
                      pw.println("IPREACH_DISCONNECT state is "
                              + mClientModeImpl.getIpReachabilityDisconnectEnabled());
                      return 0;
                  case "set-poll-rssi-interval-msecs":
                      int newPollIntervalMsecs;
                      try {
                          newPollIntervalMsecs = Integer.parseInt(getNextArgRequired());
                      } catch (NumberFormatException e) {
                          pw.println(
                                  "Invalid argument to 'set-poll-rssi-interval-msecs' "
                                          + "- must be a positive integer");
                          return -1;
                      }
  
                      if (newPollIntervalMsecs < 1) {
                          pw.println(
                                  "Invalid argument to 'set-poll-rssi-interval-msecs' "
                                          + "- must be a positive integer");
                          return -1;
                      }
  
                      mClientModeImpl.setPollRssiIntervalMsecs(newPollIntervalMsecs);
                      return 0;
                  case "get-poll-rssi-interval-msecs":
                      pw.println("ClientModeImpl.mPollRssiIntervalMsecs = "
                              + mClientModeImpl.getPollRssiIntervalMsecs());
                      return 0;
                  case "force-hi-perf-mode": {
                      boolean enabled;
                      String nextArg = getNextArgRequired();
                      if ("enabled".equals(nextArg)) {
                          enabled = true;
                      } else if ("disabled".equals(nextArg)) {
                          enabled = false;
                      } else {
                          pw.println(
                                  "Invalid argument to 'force-hi-perf-mode' - must be 'enabled'"
                                          + " or 'disabled'");
                          return -1;
                      }
                      if (!mWifiLockManager.forceHiPerfMode(enabled)) {
                          pw.println("Command execution failed");
                      }
                      return 0;
                  }
                  case "force-low-latency-mode": {
                      boolean enabled;
                      String nextArg = getNextArgRequired();
                      if ("enabled".equals(nextArg)) {
                          enabled = true;
                      } else if ("disabled".equals(nextArg)) {
                          enabled = false;
                      } else {
                          pw.println(
                                  "Invalid argument to 'force-low-latency-mode' - must be 'enabled'"
                                          + " or 'disabled'");
                          return -1;
                      }
                      if (!mWifiLockManager.forceLowLatencyMode(enabled)) {
                          pw.println("Command execution failed");
                      }
                      return 0;
                  }
                  case "network-suggestions-set-user-approved": {
                      String packageName = getNextArgRequired();
                      boolean approved;
                      String nextArg = getNextArgRequired();
                      if ("yes".equals(nextArg)) {
                          approved = true;
                      } else if ("no".equals(nextArg)) {
                          approved = false;
                      } else {
                          pw.println(
                                  "Invalid argument to 'network-suggestions-set-user-approved' "
                                          + "- must be 'yes' or 'no'");
                          return -1;
                      }
                      mWifiNetworkSuggestionsManager.setHasUserApprovedForApp(approved, packageName);
                      return 0;
                  }
                  case "network-suggestions-has-user-approved": {
                      String packageName = getNextArgRequired();
                      boolean hasUserApproved =
                              mWifiNetworkSuggestionsManager.hasUserApprovedForApp(packageName);
                      pw.println(hasUserApproved ? "yes" : "no");
                      return 0;
                  }
                  case "network-requests-remove-user-approved-access-points": {
                      String packageName = getNextArgRequired();
                      mClientModeImpl.removeNetworkRequestUserApprovedAccessPointsForApp(packageName);
                      return 0;
                  }
                  case "clear-deleted-ephemeral-networks": {
                      mWifiConfigManager.clearDeletedEphemeralNetworks();
                      return 0;
                  }
                  case "send-link-probe": {
                      return sendLinkProbe(pw);
                  }
                  case "force-softap-channel": {
                      String nextArg = getNextArgRequired();
                      if ("enabled".equals(nextArg))  {
                          int apChannelMHz;
                          try {
                              apChannelMHz = Integer.parseInt(getNextArgRequired());
                          } catch (NumberFormatException e) {
                              pw.println("Invalid argument to 'force-softap-channel enabled' "
                                      + "- must be a positive integer");
                              return -1;
                          }
                          int apChannel = ApConfigUtil.convertFrequencyToChannel(apChannelMHz);
                          if (apChannel == -1 || !isApChannelMHzValid(apChannelMHz)) {
                              pw.println("Invalid argument to 'force-softap-channel enabled' "
                                      + "- must be a valid WLAN channel");
                              return -1;
                          }
                          mHostapdHal.enableForceSoftApChannel(apChannel);
                          return 0;
                      } else if ("disabled".equals(nextArg)) {
                          mHostapdHal.disableForceSoftApChannel();
                          return 0;
                      } else {
                          pw.println(
                                  "Invalid argument to 'force-softap-channel' - must be 'enabled'"
                                          + " or 'disabled'");
                          return -1;
                      }
                  }
                  case "force-country-code": {
                      String nextArg = getNextArgRequired();
                      if ("enabled".equals(nextArg))  {
                          String countryCode = getNextArgRequired();
                          if (!(countryCode.length() == 2
                                  && countryCode.chars().allMatch(Character::isLetter))) {
                              pw.println("Invalid argument to 'force-country-code enabled' "
                                      + "- must be a two-letter string");
                              return -1;
                          }
                          mWifiCountryCode.enableForceCountryCode(countryCode);
                          return 0;
                      } else if ("disabled".equals(nextArg)) {
                          mWifiCountryCode.disableForceCountryCode();
                          return 0;
                      } else {
                          pw.println(
                                  "Invalid argument to 'force-country-code' - must be 'enabled'"
                                          + " or 'disabled'");
                          return -1;
                      }
                  }
                  case "get-country-code": {
                      pw.println("Wifi Country Code = "
                              + mWifiCountryCode.getCountryCode());
                      return 0;
                  }
                  default:
                      return handleDefaultCommands(cmd);
              }
          } catch (Exception e) {
              pw.println("Exception while executing WifiShellCommand: ");
              e.printStackTrace(pw);
          }
          return -1;
      }
  
      private int sendLinkProbe(PrintWriter pw) throws InterruptedException {
          ArrayBlockingQueue<String> queue = new ArrayBlockingQueue<>(1);
          mClientModeImpl.probeLink(new WifiNative.SendMgmtFrameCallback() {
              @Override
              public void onAck(int elapsedTimeMs) {
                  queue.offer("Link probe succeeded after " + elapsedTimeMs + " ms");
              }
  
              @Override
              public void onFailure(int reason) {
                  queue.offer("Link probe failed with reason " + reason);
              }
          }, -1);
  
          // block until msg is received, or timed out
          String msg = queue.poll(WificondControl.SEND_MGMT_FRAME_TIMEOUT_MS + 1000,
                  TimeUnit.MILLISECONDS);
          if (msg == null) {
              pw.println("Link probe timed out");
          } else {
              pw.println(msg);
          }
          return 0;
      }
  
      private boolean isApChannelMHzValid(int apChannelMHz) {
          int[] allowed2gFreq = mWifiNative.getChannelsForBand(WifiScanner.WIFI_BAND_24_GHZ);
          int[] allowed5gFreq = mWifiNative.getChannelsForBand(WifiScanner.WIFI_BAND_5_GHZ);
          int[] allowed5gDfsFreq =
              mWifiNative.getChannelsForBand(WifiScanner.WIFI_BAND_5_GHZ_DFS_ONLY);
          if (allowed2gFreq == null) {
              allowed2gFreq = new int[0];
          }
          if (allowed5gFreq == null) {
              allowed5gFreq = new int[0];
          }
          if (allowed5gDfsFreq == null) {
              allowed5gDfsFreq = new int[0];
          }
          return (Arrays.binarySearch(allowed2gFreq, apChannelMHz) >= 0
                  || Arrays.binarySearch(allowed5gFreq, apChannelMHz) >= 0
                  || Arrays.binarySearch(allowed5gDfsFreq, apChannelMHz) >= 0);
      }
  
      private void checkRootPermission() {
          final int uid = Binder.getCallingUid();
          if (uid == 0) {
              // Root can do anything.
              return;
          }
          throw new SecurityException("Uid " + uid + " does not have access to wifi commands");
      }
  
      @Override
      public void onHelp() {
          final PrintWriter pw = getOutPrintWriter();
  
          pw.println("Wi-Fi (wifi) commands:");
          pw.println("  help");
          pw.println("    Print this help text.");
          pw.println("  set-ipreach-disconnect enabled|disabled");
          pw.println("    Sets whether CMD_IP_REACHABILITY_LOST events should trigger disconnects.");
          pw.println("  get-ipreach-disconnect");
          pw.println("    Gets setting of CMD_IP_REACHABILITY_LOST events triggering disconnects.");
          pw.println("  set-poll-rssi-interval-msecs <int>");
          pw.println("    Sets the interval between RSSI polls to <int> milliseconds.");
          pw.println("  get-poll-rssi-interval-msecs");
          pw.println("    Gets current interval between RSSI polls, in milliseconds.");
          pw.println("  force-hi-perf-mode enabled|disabled");
          pw.println("    Sets whether hi-perf mode is forced or left for normal operation.");
          pw.println("  force-low-latency-mode enabled|disabled");
          pw.println("    Sets whether low latency mode is forced or left for normal operation.");
          pw.println("  network-suggestions-set-user-approved <package name> yes|no");
          pw.println("    Sets whether network suggestions from the app is approved or not.");
          pw.println("  network-suggestions-has-user-approved <package name>");
          pw.println("    Queries whether network suggestions from the app is approved or not.");
          pw.println("  network-requests-remove-user-approved-access-points <package name>");
          pw.println("    Removes all user approved network requests for the app.");
          pw.println("  clear-deleted-ephemeral-networks");
          pw.println("    Clears the deleted ephemeral networks list.");
          pw.println("  send-link-probe");
          pw.println("    Manually triggers a link probe.");
          pw.println("  force-softap-channel enabled <int> | disabled");
          pw.println("    Sets whether soft AP channel is forced to <int> MHz");
          pw.println("    or left for normal   operation.");
          pw.println("  force-country-code enabled <two-letter code> | disabled ");
          pw.println("    Sets country code to <two-letter code> or left for normal value");
          pw.println("  get-country-code");
          pw.println("    Gets country code as a two-letter string");
          pw.println();
      }
  }
  