1 /* 2 * Copyright (C) 2022 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.server.bluetooth; 18 19 import static java.util.Objects.requireNonNull; 20 21 import android.bluetooth.BluetoothAdapter; 22 import android.content.AttributionSource; 23 import android.os.Binder; 24 import android.os.Process; 25 import android.os.RemoteException; 26 27 import com.android.internal.annotations.VisibleForTesting; 28 import com.android.modules.utils.BasicShellCommandHandler; 29 30 import java.io.PrintWriter; 31 32 class BluetoothShellCommand extends BasicShellCommandHandler { 33 private static final String TAG = BluetoothShellCommand.class.getSimpleName(); 34 35 private final BluetoothManagerService mManagerService; 36 37 @VisibleForTesting 38 final BluetoothCommand[] mBluetoothCommands = { 39 new Enable(), new EnableBle(), new Disable(), new DisableBle(), new WaitForAdapterState(), 40 }; 41 42 @VisibleForTesting 43 abstract static class BluetoothCommand { 44 final boolean mIsPrivileged; 45 final String mName; 46 BluetoothCommand(boolean isPrivileged, String name)47 BluetoothCommand(boolean isPrivileged, String name) { 48 mIsPrivileged = isPrivileged; 49 mName = requireNonNull(name, "Command name cannot be null"); 50 } 51 getName()52 String getName() { 53 return mName; 54 } 55 isMatch(String cmd)56 boolean isMatch(String cmd) { 57 return mName.equals(cmd); 58 } 59 isPrivileged()60 boolean isPrivileged() { 61 return mIsPrivileged; 62 } 63 exec(String cmd)64 abstract int exec(String cmd) throws RemoteException; 65 onHelp(PrintWriter pw)66 abstract void onHelp(PrintWriter pw); 67 } 68 69 @VisibleForTesting 70 class EnableBle extends BluetoothCommand { EnableBle()71 EnableBle() { 72 super(true, "enableBle"); 73 } 74 75 @Override exec(String cmd)76 public int exec(String cmd) throws RemoteException { 77 return mManagerService 78 .getBinder() 79 .enableBle( 80 AttributionSource.myAttributionSource(), 81 mManagerService.getBinder()) 82 ? 0 83 : -1; 84 } 85 86 @Override onHelp(PrintWriter pw)87 public void onHelp(PrintWriter pw) { 88 pw.println(" " + getName()); 89 pw.println(" Call enableBle to activate ble only mode on this device."); 90 } 91 } 92 93 @VisibleForTesting 94 class DisableBle extends BluetoothCommand { DisableBle()95 DisableBle() { 96 super(true, "disableBle"); 97 } 98 99 @Override exec(String cmd)100 public int exec(String cmd) throws RemoteException { 101 return mManagerService 102 .getBinder() 103 .disableBle( 104 AttributionSource.myAttributionSource(), 105 mManagerService.getBinder()) 106 ? 0 107 : -1; 108 } 109 110 @Override onHelp(PrintWriter pw)111 public void onHelp(PrintWriter pw) { 112 pw.println(" " + getName()); 113 pw.println(" revoke the call to enableBle. No-op if enableBle wasn't call before"); 114 } 115 } 116 117 @VisibleForTesting 118 class Enable extends BluetoothCommand { Enable()119 Enable() { 120 super(false, "enable"); 121 } 122 123 @Override exec(String cmd)124 public int exec(String cmd) throws RemoteException { 125 return mManagerService.getBinder().enable(AttributionSource.myAttributionSource()) 126 ? 0 127 : -1; 128 } 129 130 @Override onHelp(PrintWriter pw)131 public void onHelp(PrintWriter pw) { 132 pw.println(" " + getName()); 133 pw.println(" Enable Bluetooth on this device."); 134 } 135 } 136 137 @VisibleForTesting 138 class Disable extends BluetoothCommand { Disable()139 Disable() { 140 super(false, "disable"); 141 } 142 143 @Override exec(String cmd)144 public int exec(String cmd) throws RemoteException { 145 return mManagerService 146 .getBinder() 147 .disable(AttributionSource.myAttributionSource(), true) 148 ? 0 149 : -1; 150 } 151 152 @Override onHelp(PrintWriter pw)153 public void onHelp(PrintWriter pw) { 154 pw.println(" " + getName()); 155 pw.println(" Disable Bluetooth on this device."); 156 } 157 } 158 159 @VisibleForTesting 160 class WaitForAdapterState extends BluetoothCommand { WaitForAdapterState()161 WaitForAdapterState() { 162 super(false, "wait-for-state"); 163 } 164 getWaitingState(String in)165 private int getWaitingState(String in) { 166 if (!in.startsWith(getName() + ":")) return -1; 167 String[] split = in.split(":", 2); 168 if (split.length != 2 || !getName().equals(split[0])) { 169 String msg = getName() + ": Invalid state format: " + in; 170 Log.e(TAG, msg); 171 PrintWriter pw = getErrPrintWriter(); 172 pw.println(TAG + ": " + msg); 173 printHelp(pw); 174 throw new IllegalArgumentException(); 175 } 176 switch (split[1]) { 177 case "STATE_OFF": 178 return BluetoothAdapter.STATE_OFF; 179 case "STATE_ON": 180 return BluetoothAdapter.STATE_ON; 181 default: 182 String msg = getName() + ": Invalid state value: " + split[1] + ". From: " + in; 183 Log.e(TAG, msg); 184 PrintWriter pw = getErrPrintWriter(); 185 pw.println(TAG + ": " + msg); 186 printHelp(pw); 187 throw new IllegalArgumentException(); 188 } 189 } 190 191 @Override isMatch(String cmd)192 boolean isMatch(String cmd) { 193 return getWaitingState(cmd) != -1; 194 } 195 196 @Override exec(String cmd)197 public int exec(String cmd) throws RemoteException { 198 int ret = mManagerService.waitForManagerState(getWaitingState(cmd)) ? 0 : -1; 199 Log.d(TAG, cmd + ": Return value is " + ret); // logging as this method can take time 200 return ret; 201 } 202 203 @Override onHelp(PrintWriter pw)204 public void onHelp(PrintWriter pw) { 205 pw.println(" " + getName() + ":<STATE>"); 206 pw.println( 207 " Wait until the adapter state is <STATE>." 208 + " <STATE> can be one of STATE_OFF | STATE_ON"); 209 pw.println(" Note: This command can timeout and failed"); 210 } 211 } 212 BluetoothShellCommand(BluetoothManagerService managerService)213 BluetoothShellCommand(BluetoothManagerService managerService) { 214 mManagerService = managerService; 215 } 216 217 @Override onCommand(String cmd)218 public int onCommand(String cmd) { 219 if (cmd == null) return handleDefaultCommands(null); 220 221 for (BluetoothCommand bt_cmd : mBluetoothCommands) { 222 if (!bt_cmd.isMatch(cmd)) continue; 223 if (bt_cmd.isPrivileged()) { 224 final int uid = Binder.getCallingUid(); 225 if (uid != Process.ROOT_UID) { 226 throw new SecurityException( 227 "Uid " 228 + uid 229 + " does not have access to " 230 + cmd 231 + " bluetooth command"); 232 } 233 } 234 try { 235 getOutPrintWriter().println(TAG + ": Exec " + cmd); 236 Log.d(TAG, "Exec " + cmd); 237 int ret = bt_cmd.exec(cmd); 238 if (ret == 0) { 239 String msg = cmd + ": Success"; 240 Log.d(TAG, msg); 241 getOutPrintWriter().println(msg); 242 } else { 243 String msg = cmd + ": Failed with status=" + ret; 244 Log.e(TAG, msg); 245 getErrPrintWriter().println(TAG + ": " + msg); 246 } 247 return ret; 248 } catch (RemoteException e) { 249 Log.w(TAG, cmd + ": error\nException: " + e.getMessage()); 250 getErrPrintWriter().println(cmd + ": error\nException: " + e.getMessage()); 251 e.rethrowFromSystemServer(); 252 } 253 } 254 return handleDefaultCommands(cmd); 255 } 256 printHelp(PrintWriter pw)257 private void printHelp(PrintWriter pw) { 258 pw.println("Bluetooth Manager Commands:"); 259 pw.println(" help or -h"); 260 pw.println(" Print this help text."); 261 for (BluetoothCommand bt_cmd : mBluetoothCommands) { 262 bt_cmd.onHelp(pw); 263 } 264 } 265 266 @Override onHelp()267 public void onHelp() { 268 printHelp(getOutPrintWriter()); 269 } 270 } 271