• 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.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