• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2021 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.libraries.testing.deviceshadower.internal.bluetooth.connection;
18 
19 import com.android.internal.annotations.VisibleForTesting;
20 import com.android.libraries.testing.deviceshadower.internal.DeviceShadowEnvironmentImpl;
21 import com.android.libraries.testing.deviceshadower.internal.utils.Logger;
22 
23 import com.google.common.collect.Sets;
24 
25 import java.io.FileDescriptor;
26 import java.util.HashMap;
27 import java.util.Map;
28 import java.util.Map.Entry;
29 import java.util.Set;
30 import java.util.concurrent.BlockingQueue;
31 import java.util.concurrent.ConcurrentHashMap;
32 import java.util.concurrent.LinkedBlockingQueue;
33 import java.util.concurrent.atomic.AtomicBoolean;
34 
35 /**
36  * A class represents a physical link for communications between two Bluetooth devices.
37  */
38 public class PhysicalLink {
39 
40     // Intended to use RfcommDelegate
41     private static final Logger LOGGER = Logger.create("RfcommDelegate");
42 
43     private final Object mLock;
44     // Every socket has unique FileDescriptor, so use it as socket identifier during communication
45     private final Map<FileDescriptor, RfcommSocketConnection> mConnectionLookup;
46     // Map fd of a socket to the fd of the other socket it connects to
47     private final Map<FileDescriptor, FileDescriptor> mFdMap;
48     private final Set<RfcommSocketConnection> mConnections;
49     private final AtomicBoolean mIsEncrypted;
50     private final Map<String, RfcommDelegate.Callback> mCallbacks = new HashMap<>();
51 
PhysicalLink(String address1, String address2)52     public PhysicalLink(String address1, String address2) {
53         this(address1,
54                 DeviceShadowEnvironmentImpl.getBlueletImpl(address1).getRfcommDelegate().mCallback,
55                 address2,
56                 DeviceShadowEnvironmentImpl.getBlueletImpl(address2).getRfcommDelegate().mCallback,
57                 new ConcurrentHashMap<FileDescriptor, RfcommSocketConnection>(),
58                 new ConcurrentHashMap<FileDescriptor, FileDescriptor>(),
59                 Sets.<RfcommSocketConnection>newConcurrentHashSet());
60     }
61 
62     @VisibleForTesting
PhysicalLink(String address1, RfcommDelegate.Callback callback1, String address2, RfcommDelegate.Callback callback2, Map<FileDescriptor, RfcommSocketConnection> connectionLookup, Map<FileDescriptor, FileDescriptor> fdMap, Set<RfcommSocketConnection> connections)63     PhysicalLink(String address1, RfcommDelegate.Callback callback1,
64             String address2, RfcommDelegate.Callback callback2,
65             Map<FileDescriptor, RfcommSocketConnection> connectionLookup,
66             Map<FileDescriptor, FileDescriptor> fdMap,
67             Set<RfcommSocketConnection> connections) {
68         mLock = new Object();
69         mCallbacks.put(address1, callback1);
70         mCallbacks.put(address2, callback2);
71         this.mConnectionLookup = connectionLookup;
72         this.mFdMap = fdMap;
73         this.mConnections = connections;
74         mIsEncrypted = new AtomicBoolean(false);
75     }
76 
addConnection(FileDescriptor fd1, FileDescriptor fd2)77     public void addConnection(FileDescriptor fd1, FileDescriptor fd2) {
78         synchronized (mLock) {
79             int oldSize = mConnections.size();
80             RfcommSocketConnection connection = new RfcommSocketConnection(
81                     FileDescriptorFactory.getInstance().getAddress(fd1),
82                     FileDescriptorFactory.getInstance().getAddress(fd2)
83             );
84             mConnections.add(connection);
85             mConnectionLookup.put(fd1, connection);
86             mConnectionLookup.put(fd2, connection);
87             mFdMap.put(fd1, fd2);
88             mFdMap.put(fd2, fd1);
89             if (oldSize == 0) {
90                 onConnectionStateChange(true);
91             }
92         }
93     }
94 
95     // TODO(b/79994182): see go/objecttostring-lsc
96     @SuppressWarnings("ObjectToString")
closeConnection(FileDescriptor fd)97     public void closeConnection(FileDescriptor fd) {
98         // check for early return without locking
99         if (!mConnectionLookup.containsKey(fd)) {
100             // TODO(b/79994182): FileDescriptor does not implement toString() in fd
101             LOGGER.d("Connection doesn't exist, FileDescriptor: " + fd);
102             return;
103         }
104         synchronized (mLock) {
105             RfcommSocketConnection connection = mConnectionLookup.get(fd);
106             if (connection == null) {
107                 // TODO(b/79994182): FileDescriptor does not implement toString() in fd
108                 LOGGER.d("Connection doesn't exist, FileDescriptor: " + fd);
109                 return;
110             }
111             int oldSize = mConnections.size();
112             FileDescriptor connectingFd = mFdMap.get(fd);
113             mConnectionLookup.remove(fd);
114             mConnectionLookup.remove(connectingFd);
115             mFdMap.remove(fd);
116             mFdMap.remove(connectingFd);
117             mConnections.remove(connection);
118             if (oldSize == 1) {
119                 onConnectionStateChange(false);
120             }
121         }
122     }
123 
getConnection(FileDescriptor fd)124     public RfcommSocketConnection getConnection(FileDescriptor fd) {
125         return mConnectionLookup.get(fd);
126     }
127 
encrypt()128     public void encrypt() {
129         mIsEncrypted.set(true);
130     }
131 
isEncrypted()132     public boolean isEncrypted() {
133         return mIsEncrypted.get();
134     }
135 
isConnected()136     public boolean isConnected() {
137         return !mConnections.isEmpty();
138     }
139 
onConnectionStateChange(boolean isConnected)140     private void onConnectionStateChange(boolean isConnected) {
141         for (Entry<String, RfcommDelegate.Callback> entry : mCallbacks.entrySet()) {
142             RfcommDelegate.Callback callback = entry.getValue();
143             String localAddress = entry.getKey();
144             callback.onConnectionStateChange(getRemoteAddress(localAddress), isConnected);
145         }
146     }
147 
getRemoteAddress(String address)148     private String getRemoteAddress(String address) {
149         String remoteAddress = null;
150         for (String addr : mCallbacks.keySet()) {
151             if (!addr.equals(address)) {
152                 remoteAddress = addr;
153                 break;
154             }
155         }
156         return remoteAddress;
157     }
158 
159     /**
160      * Represents a Rfcomm socket connection between two {@link android.bluetooth.BluetoothSocket}.
161      */
162     public static class RfcommSocketConnection {
163 
164         final Map<String, BlockingQueue<Integer>> mIncomingDataMap; // address : incomingData
165 
RfcommSocketConnection(String address1, String address2)166         public RfcommSocketConnection(String address1, String address2) {
167             mIncomingDataMap = new ConcurrentHashMap<>();
168             mIncomingDataMap.put(address1, new LinkedBlockingQueue<Integer>());
169             mIncomingDataMap.put(address2, new LinkedBlockingQueue<Integer>());
170         }
171 
write(String address, int b)172         public void write(String address, int b) throws InterruptedException {
173             mIncomingDataMap.get(address).put(b);
174         }
175 
read(String address)176         public int read(String address) throws InterruptedException {
177             return mIncomingDataMap.get(address).take();
178         }
179     }
180 }
181