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