• 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 android.nearby.fastpair.provider;
18 
19 import static com.google.common.io.BaseEncoding.base16;
20 
21 import static java.nio.charset.StandardCharsets.UTF_8;
22 
23 import android.bluetooth.BluetoothAdapter;
24 import android.bluetooth.BluetoothDevice;
25 import android.content.Context;
26 import android.content.SharedPreferences;
27 
28 import androidx.annotation.Nullable;
29 
30 import com.google.protobuf.ByteString;
31 
32 import java.util.HashSet;
33 import java.util.Set;
34 import java.util.StringTokenizer;
35 
36 /** Stores fast pair related information for each paired device */
37 public class FastPairSimulatorDatabase {
38 
39     private static final String SHARED_PREF_NAME =
40             "android.nearby.fastpair.provider.fastpairsimulator";
41     private static final String KEY_DEVICE_NAME = "DEVICE_NAME";
42     private static final String KEY_ACCOUNT_KEYS = "ACCOUNT_KEYS";
43     private static final int MAX_NUMBER_OF_ACCOUNT_KEYS = 8;
44 
45     // [for SASS]
46     private static final String KEY_FAST_PAIR_SEEKER_DEVICE = "FAST_PAIR_SEEKER_DEVICE";
47 
48     private final SharedPreferences mSharedPreferences;
49 
FastPairSimulatorDatabase(Context context)50     public FastPairSimulatorDatabase(Context context) {
51         mSharedPreferences = context.getSharedPreferences(SHARED_PREF_NAME, Context.MODE_PRIVATE);
52     }
53 
54     /** Adds single account key. */
addAccountKey(byte[] accountKey)55     public void addAccountKey(byte[] accountKey) {
56         if (mSharedPreferences == null) {
57             return;
58         }
59 
60         Set<ByteString> accountKeys = new HashSet<>(getAccountKeys());
61         if (accountKeys.size() >= MAX_NUMBER_OF_ACCOUNT_KEYS) {
62             Set<ByteString> removedKeys = new HashSet<>();
63             int removedCount = accountKeys.size() - MAX_NUMBER_OF_ACCOUNT_KEYS + 1;
64             for (ByteString key : accountKeys) {
65                 if (removedKeys.size() == removedCount) {
66                     break;
67                 }
68                 removedKeys.add(key);
69             }
70 
71             accountKeys.removeAll(removedKeys);
72         }
73 
74         // Just make sure the newest key will not be removed.
75         accountKeys.add(ByteString.copyFrom(accountKey));
76         setAccountKeys(accountKeys);
77     }
78 
79     /** Sets account keys, overrides all. */
setAccountKeys(Set<ByteString> accountKeys)80     public void setAccountKeys(Set<ByteString> accountKeys) {
81         if (mSharedPreferences == null) {
82             return;
83         }
84 
85         Set<String> keys = new HashSet<>();
86         for (ByteString item : accountKeys) {
87             keys.add(base16().encode(item.toByteArray()));
88         }
89 
90         mSharedPreferences.edit().putStringSet(KEY_ACCOUNT_KEYS, keys).apply();
91     }
92 
93     /** Gets all account keys. */
getAccountKeys()94     public Set<ByteString> getAccountKeys() {
95         if (mSharedPreferences == null) {
96             return new HashSet<>();
97         }
98 
99         Set<String> keys = mSharedPreferences.getStringSet(KEY_ACCOUNT_KEYS, new HashSet<>());
100         Set<ByteString> accountKeys = new HashSet<>();
101         // Add new account keys one by one.
102         for (String key : keys) {
103             accountKeys.add(ByteString.copyFrom(base16().decode(key)));
104         }
105 
106         return accountKeys;
107     }
108 
109     /** Sets local device name. */
setLocalDeviceName(byte[] deviceName)110     public void setLocalDeviceName(byte[] deviceName) {
111         if (mSharedPreferences == null) {
112             return;
113         }
114 
115         String humanReadableName = deviceName != null ? new String(deviceName, UTF_8) : null;
116         if (humanReadableName == null) {
117             mSharedPreferences.edit().remove(KEY_DEVICE_NAME).apply();
118         } else {
119             mSharedPreferences.edit().putString(KEY_DEVICE_NAME, humanReadableName).apply();
120         }
121     }
122 
123     /** Gets local device name. */
124     @Nullable
getLocalDeviceName()125     public byte[] getLocalDeviceName() {
126         if (mSharedPreferences == null) {
127             return null;
128         }
129 
130         String deviceName = mSharedPreferences.getString(KEY_DEVICE_NAME, null);
131         return deviceName != null ? deviceName.getBytes(UTF_8) : null;
132     }
133 
134     /**
135      * [for SASS] Adds seeker device info. <a
136      * href="http://go/smart-audio-source-switching-design">Sass design doc</a>
137      */
addFastPairSeekerDevice(@ullable BluetoothDevice device, byte[] accountKey)138     public void addFastPairSeekerDevice(@Nullable BluetoothDevice device, byte[] accountKey) {
139         if (mSharedPreferences == null) {
140             return;
141         }
142 
143         if (device == null) {
144             return;
145         }
146 
147         // When hitting size limitation, choose the existing items to delete.
148         Set<FastPairSeekerDevice> fastPairSeekerDevices = getFastPairSeekerDevices();
149         if (fastPairSeekerDevices.size() > MAX_NUMBER_OF_ACCOUNT_KEYS) {
150             int removedCount = fastPairSeekerDevices.size() - MAX_NUMBER_OF_ACCOUNT_KEYS + 1;
151             Set<FastPairSeekerDevice> removedFastPairDevices = new HashSet<>();
152             for (FastPairSeekerDevice fastPairDevice : fastPairSeekerDevices) {
153                 if (removedFastPairDevices.size() == removedCount) {
154                     break;
155                 }
156                 removedFastPairDevices.add(fastPairDevice);
157             }
158             fastPairSeekerDevices.removeAll(removedFastPairDevices);
159         }
160 
161         fastPairSeekerDevices.add(new FastPairSeekerDevice(device, accountKey));
162         setFastPairSeekerDevices(fastPairSeekerDevices);
163     }
164 
165     /** [for SASS] Sets all seeker device info, overrides all. */
setFastPairSeekerDevices(Set<FastPairSeekerDevice> fastPairSeekerDeviceSet)166     public void setFastPairSeekerDevices(Set<FastPairSeekerDevice> fastPairSeekerDeviceSet) {
167         if (mSharedPreferences == null) {
168             return;
169         }
170 
171         Set<String> rawStringSet = new HashSet<>();
172         for (FastPairSeekerDevice item : fastPairSeekerDeviceSet) {
173             rawStringSet.add(item.toRawString());
174         }
175 
176         mSharedPreferences.edit().putStringSet(KEY_FAST_PAIR_SEEKER_DEVICE, rawStringSet).apply();
177     }
178 
179     /** [for SASS] Gets all seeker device info. */
getFastPairSeekerDevices()180     public Set<FastPairSeekerDevice> getFastPairSeekerDevices() {
181         if (mSharedPreferences == null) {
182             return new HashSet<>();
183         }
184 
185         Set<FastPairSeekerDevice> fastPairSeekerDevices = new HashSet<>();
186         Set<String> rawStringSet =
187                 mSharedPreferences.getStringSet(KEY_FAST_PAIR_SEEKER_DEVICE, new HashSet<>());
188         for (String rawString : rawStringSet) {
189             FastPairSeekerDevice fastPairDevice = FastPairSeekerDevice.fromRawString(rawString);
190             if (fastPairDevice == null) {
191                 continue;
192             }
193             fastPairSeekerDevices.add(fastPairDevice);
194         }
195 
196         return fastPairSeekerDevices;
197     }
198 
199     /** Defines data structure for the paired Fast Pair device. */
200     public static class FastPairSeekerDevice {
201         private static final int INDEX_DEVICE = 0;
202         private static final int INDEX_ACCOUNT_KEY = 1;
203 
204         private final BluetoothDevice mDevice;
205         private final byte[] mAccountKey;
206 
FastPairSeekerDevice(BluetoothDevice device, byte[] accountKey)207         private FastPairSeekerDevice(BluetoothDevice device, byte[] accountKey) {
208             this.mDevice = device;
209             this.mAccountKey = accountKey;
210         }
211 
getBluetoothDevice()212         public BluetoothDevice getBluetoothDevice() {
213             return mDevice;
214         }
215 
getAccountKey()216         public byte[] getAccountKey() {
217             return mAccountKey;
218         }
219 
toRawString()220         public String toRawString() {
221             return String.format("%s,%s", mDevice, base16().encode(mAccountKey));
222         }
223 
224         /** Decodes the raw string if possible. */
225         @Nullable
fromRawString(String rawString)226         public static FastPairSeekerDevice fromRawString(String rawString) {
227             BluetoothDevice device = null;
228             byte[] accountKey = null;
229             int step = INDEX_DEVICE;
230 
231             StringTokenizer tokenizer = new StringTokenizer(rawString, ",");
232             while (tokenizer.hasMoreElements()) {
233                 boolean shouldStop = false;
234                 String token = tokenizer.nextToken();
235                 switch (step) {
236                     case INDEX_DEVICE:
237                         try {
238                             device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(token);
239                         } catch (IllegalArgumentException e) {
240                             device = null;
241                         }
242                         break;
243                     case INDEX_ACCOUNT_KEY:
244                         accountKey = base16().decode(token);
245                         if (accountKey.length != 16) {
246                             accountKey = null;
247                         }
248                         break;
249                     default:
250                         shouldStop = true;
251                 }
252 
253                 if (shouldStop) {
254                     break;
255                 }
256                 step++;
257             }
258             if (device != null && accountKey != null) {
259                 return new FastPairSeekerDevice(device, accountKey);
260             }
261             return null;
262         }
263     }
264 }
265