• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.bluetooth.pbapclient;
18 
19 import static android.Manifest.permission.BLUETOOTH_CONNECT;
20 import static android.Manifest.permission.BLUETOOTH_PRIVILEGED;
21 
22 import static java.util.Objects.requireNonNull;
23 
24 import android.annotation.RequiresPermission;
25 import android.bluetooth.BluetoothDevice;
26 import android.bluetooth.BluetoothSocket;
27 
28 import com.android.internal.annotations.VisibleForTesting;
29 
30 import java.io.IOException;
31 import java.io.InputStream;
32 import java.io.OutputStream;
33 
34 /**
35  * A testable object that wraps BluetoothSocket objects
36  *
37  * <p>To inject input and output streams in place of an underlying L2CAP or RFCOMM connection, use
38  * the inject(InputStream input, OutputStream output) function. All sockets created after injecting
39  * streams will use the injected streams instead.
40  */
41 public class PbapClientSocket {
42     private static final String TAG = PbapClientSocket.class.getSimpleName();
43 
44     // Houses the streams to be injected in place of the underlying BluetoothSocket
45     private static InputStream sInjectedInput;
46     private static OutputStream sInjectedOutput;
47 
48     // Houses the actual socket if used
49     private final BluetoothSocket mSocket;
50 
51     // Houses injected streams, if used for this object
52     private final BluetoothDevice mDevice;
53     private final int mType;
54     private final InputStream mInjectedInput;
55     private final OutputStream mInjectedOutput;
56 
57     @VisibleForTesting
inject(InputStream input, OutputStream output)58     static void inject(InputStream input, OutputStream output) {
59         sInjectedInput = requireNonNull(input);
60         sInjectedOutput = requireNonNull(output);
61     }
62 
63     /** A static utility to create an L2CAP based socket for a given device */
getL2capSocketForDevice(BluetoothDevice device, int psm)64     public static PbapClientSocket getL2capSocketForDevice(BluetoothDevice device, int psm)
65             throws IOException {
66         if (sInjectedInput != null || sInjectedOutput != null) {
67             return new PbapClientSocket(
68                     device, BluetoothSocket.TYPE_L2CAP, sInjectedInput, sInjectedOutput);
69         }
70 
71         BluetoothSocket socket = device.createL2capSocket(psm);
72         return new PbapClientSocket(socket);
73     }
74 
75     /** A static utility to create an RFCOMM based socket for a given device */
getRfcommSocketForDevice(BluetoothDevice device, int channel)76     public static PbapClientSocket getRfcommSocketForDevice(BluetoothDevice device, int channel)
77             throws IOException {
78         if (sInjectedInput != null || sInjectedOutput != null) {
79             return new PbapClientSocket(
80                     device, BluetoothSocket.TYPE_RFCOMM, sInjectedInput, sInjectedOutput);
81         }
82         BluetoothSocket socket = device.createRfcommSocket(channel);
83         return new PbapClientSocket(socket);
84     }
85 
PbapClientSocket(BluetoothSocket socket)86     private PbapClientSocket(BluetoothSocket socket) {
87         mSocket = socket;
88 
89         mDevice = null;
90         mType = -1;
91         mInjectedInput = null;
92         mInjectedOutput = null;
93     }
94 
PbapClientSocket( BluetoothDevice device, int type, InputStream input, OutputStream output)95     private PbapClientSocket(
96             BluetoothDevice device, int type, InputStream input, OutputStream output) {
97         mSocket = null;
98 
99         mDevice = device;
100         mType = type;
101         mInjectedInput = input;
102         mInjectedOutput = output;
103     }
104 
105     /** Invokes the underlying BluetoothSocket#connect(), or does nothing if a socket is injected */
106     @RequiresPermission(
107             allOf = {BLUETOOTH_CONNECT, BLUETOOTH_PRIVILEGED},
108             conditional = true)
connect()109     public void connect() throws IOException {
110         if (mSocket != null) {
111             mSocket.connect();
112         }
113     }
114 
115     /** Invokes the underlying BluetoothSocket#getRemoteDevice(), or the injected device */
getRemoteDevice()116     public BluetoothDevice getRemoteDevice() {
117         if (mSocket != null) {
118             return mSocket.getRemoteDevice();
119         }
120         return mDevice;
121     }
122 
123     /** Invokes the underlying BluetoothSocket#getConnectionType(), or the injected type */
getConnectionType()124     public int getConnectionType() {
125         if (mSocket != null) {
126             return mSocket.getConnectionType();
127         }
128         return mType;
129     }
130 
131     /**
132      * Invokes the underlying BluetoothSocket#getMaxTransmitPacketSize(), or returns the spec
133      * minimum 255 when a socket is injected
134      */
getMaxTransmitPacketSize()135     public int getMaxTransmitPacketSize() {
136         if (mSocket != null) {
137             return mSocket.getMaxTransmitPacketSize();
138         }
139         return 255; // Minimum by specification
140     }
141 
142     /**
143      * Invokes the underlying BluetoothSocket#getMaxReceivePacketSize(), or returns the spec minimum
144      * 255 when a socket is injected
145      */
getMaxReceivePacketSize()146     public int getMaxReceivePacketSize() {
147         if (mSocket != null) {
148             return mSocket.getMaxReceivePacketSize();
149         }
150         return 255; // Minimum by specification
151     }
152 
153     /** Invokes the underlying BluetoothSocket#getInputStream(), or returns the injected input */
getInputStream()154     public InputStream getInputStream() throws IOException {
155         if (mInjectedInput != null) {
156             return mInjectedInput;
157         }
158         return mSocket.getInputStream();
159     }
160 
161     /** Invokes the underlying BluetoothSocket#getOutputStream(), or returns the injected output */
getOutputStream()162     public OutputStream getOutputStream() throws IOException {
163         if (mInjectedOutput != null) {
164             return mInjectedOutput;
165         }
166         return mSocket.getOutputStream();
167     }
168 
169     /** Invokes the underlying BluetoothSocket#close(), or the injected sockets's close() */
close()170     public void close() throws IOException {
171         if (mSocket != null) {
172             mSocket.close();
173             return;
174         }
175 
176         if (sInjectedInput != null) {
177             sInjectedInput.close();
178         }
179 
180         if (mInjectedOutput != null) {
181             mInjectedOutput.close();
182         }
183     }
184 
185     @Override
toString()186     public String toString() {
187         if (mSocket != null) {
188             return mSocket.toString();
189         }
190         return "FakeBluetoothSocket";
191     }
192 }
193