• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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