/* * 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 "" 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 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 "); pw.println(" Sets the interval between RSSI polls to 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 yes|no"); pw.println(" Sets whether network suggestions from the app is approved or not."); pw.println(" network-suggestions-has-user-approved "); pw.println(" Queries whether network suggestions from the app is approved or not."); pw.println(" network-requests-remove-user-approved-access-points "); 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 | disabled"); pw.println(" Sets whether soft AP channel is forced to MHz"); pw.println(" or left for normal operation."); pw.println(" force-country-code enabled | disabled "); pw.println(" Sets country code to or left for normal value"); pw.println(" get-country-code"); pw.println(" Gets country code as a two-letter string"); pw.println(); } }