1 /* 2 * Copyright (C) 2024 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.nfc; 18 19 import android.content.Context; 20 import android.os.Binder; 21 import android.os.Process; 22 import android.os.RemoteException; 23 import android.text.TextUtils; 24 25 import com.android.modules.utils.BasicShellCommandHandler; 26 27 import java.io.PrintWriter; 28 29 /** 30 * Interprets and executes 'adb shell cmd nfc [args]'. 31 * 32 * To add new commands: 33 * - onCommand: Add a case "<command>" execute. Return a 0 34 * if command executed successfully. 35 * - onHelp: add a description string. 36 * 37 * Permissions: currently root permission is required for some commands. Others will 38 * enforce the corresponding API permissions. 39 */ 40 public class NfcShellCommand extends BasicShellCommandHandler { 41 private static final int DISABLE_POLLING_FLAGS = 0x1000; 42 private static final int ENABLE_POLLING_FLAGS = 0x0000; 43 44 // These don't require root access. However, these do perform permission checks in the 45 // corresponding binder methods in mNfcService.mNfcAdapter. 46 // Note: Any time you invoke a method from an internal class, consider making it privileged 47 // since these shell commands are available on production builds, we don't want apps to use 48 // this command to bypass security restrictions. mNfcService.mNfcAdapter binder 49 // methods already enforce permissions of the invoking shell (non-rooted shell has limited 50 // set of privileges). 51 private static final String[] NON_PRIVILEGED_COMMANDS = { 52 "help", 53 "disable-nfc", 54 "enable-nfc", 55 "status", 56 }; 57 private final NfcService mNfcService; 58 private final Context mContext; 59 NfcShellCommand(NfcService nfcService, Context context)60 NfcShellCommand(NfcService nfcService, Context context) { 61 mNfcService = nfcService; 62 mContext = context; 63 } 64 65 @Override onCommand(String cmd)66 public int onCommand(String cmd) { 67 // Treat no command as help command. 68 if (cmd == null || cmd.equals("")) { 69 cmd = "help"; 70 } 71 // Explicit exclusion from root permission 72 if (ArrayUtils.indexOf(NON_PRIVILEGED_COMMANDS, cmd) == -1) { 73 final int uid = Binder.getCallingUid(); 74 if (uid != Process.ROOT_UID) { 75 throw new SecurityException( 76 "Uid " + uid + " does not have access to " + cmd + " nfc command " 77 + "(or such command doesn't exist)"); 78 } 79 } 80 81 final PrintWriter pw = getOutPrintWriter(); 82 try { 83 switch (cmd) { 84 case "status": 85 printStatus(pw); 86 return 0; 87 case "disable-nfc": 88 String stringSaveState = getNextArg(); 89 boolean saveState = false; 90 if (TextUtils.equals(stringSaveState, "[persist]")) { 91 saveState = true; 92 } 93 mNfcService.mNfcAdapter.disable(saveState, mContext.getPackageName()); 94 return 0; 95 case "enable-nfc": 96 mNfcService.mNfcAdapter.enable(mContext.getPackageName()); 97 return 0; 98 case "set-reader-mode": 99 boolean enable_polling = 100 getNextArgRequiredTrueOrFalse("enable-polling", "disable-polling"); 101 int flags = enable_polling ? ENABLE_POLLING_FLAGS : DISABLE_POLLING_FLAGS; 102 mNfcService.mNfcAdapter.setReaderMode(new Binder(), null, flags, null); 103 return 0; 104 case "set-observe-mode": 105 boolean enable = getNextArgRequiredTrueOrFalse("enable", "disable"); 106 mNfcService.mNfcAdapter.setObserveMode(enable, mContext.getPackageName()); 107 return 0; 108 case "set-controller-always-on": 109 boolean enableAlwaysOn = getNextArgRequiredTrueOrFalse("enable", "disable"); 110 mNfcService.mNfcAdapter.setControllerAlwaysOn(enableAlwaysOn); 111 return 0; 112 case "set-discovery-tech": 113 int pollTech = Integer.parseInt(getNextArg()); 114 int listenTech = Integer.parseInt(getNextArg()); 115 mNfcService.mNfcAdapter.updateDiscoveryTechnology( 116 new Binder(), pollTech, listenTech); 117 return 0; 118 default: 119 return handleDefaultCommands(cmd); 120 } 121 } catch (IllegalArgumentException e) { 122 pw.println("Invalid args for " + cmd + ": "); 123 e.printStackTrace(pw); 124 return -1; 125 } catch (Exception e) { 126 pw.println("Exception while executing nfc shell command" + cmd + ": "); 127 e.printStackTrace(pw); 128 return -1; 129 } 130 } 131 argTrueOrFalse(String arg, String trueString, String falseString)132 private static boolean argTrueOrFalse(String arg, String trueString, String falseString) { 133 if (trueString.equals(arg)) { 134 return true; 135 } else if (falseString.equals(arg)) { 136 return false; 137 } else { 138 throw new IllegalArgumentException("Expected '" + trueString + "' or '" + falseString 139 + "' as next arg but got '" + arg + "'"); 140 } 141 142 } 143 getNextArgRequiredTrueOrFalse(String trueString, String falseString)144 private boolean getNextArgRequiredTrueOrFalse(String trueString, String falseString) 145 throws IllegalArgumentException { 146 String nextArg = getNextArgRequired(); 147 return argTrueOrFalse(nextArg, trueString, falseString); 148 } 149 printStatus(PrintWriter pw)150 private void printStatus(PrintWriter pw) throws RemoteException { 151 pw.println("Nfc is " + (mNfcService.isNfcEnabled() ? "enabled" : "disabled")); 152 } 153 onHelpNonPrivileged(PrintWriter pw)154 private void onHelpNonPrivileged(PrintWriter pw) { 155 pw.println(" status"); 156 pw.println(" Gets status of UWB stack"); 157 pw.println(" enable-nfc"); 158 pw.println(" Toggle NFC on"); 159 pw.println(" disable-nfc [persist]"); 160 pw.println(" Toggle NFC off (optionally make it persistent)"); 161 } 162 onHelpPrivileged(PrintWriter pw)163 private void onHelpPrivileged(PrintWriter pw) { 164 pw.println(" set-observe-mode enable|disable"); 165 pw.println(" Enable or disable observe mode."); 166 pw.println(" set-reader-mode enable-polling|disable-polling"); 167 pw.println(" Enable or reader mode polling"); 168 pw.println(" set-controller-always-on enable|disable"); 169 pw.println(" Enable or disable controller always on"); 170 pw.println(" set-discovery-tech poll-mask|listen-mask"); 171 } 172 173 @Override onHelp()174 public void onHelp() { 175 final PrintWriter pw = getOutPrintWriter(); 176 pw.println("NFC (Near-field communication) commands:"); 177 pw.println(" help or -h"); 178 pw.println(" Print this help text."); 179 onHelpNonPrivileged(pw); 180 if (Binder.getCallingUid() == Process.ROOT_UID) { 181 onHelpPrivileged(pw); 182 } 183 pw.println(); 184 } 185 } 186