1 /*
2  * Copyright (C) 2011 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.settingslib.bluetooth;
18 
19 import android.annotation.SuppressLint;
20 import android.bluetooth.BluetoothClass;
21 import android.bluetooth.BluetoothDevice;
22 import android.bluetooth.BluetoothUuid;
23 import android.os.ParcelUuid;
24 import android.util.Log;
25 
26 import com.android.internal.util.ArrayUtils;
27 
28 /**
29  * BluetoothDeviceFilter contains a static method that returns a
30  * Filter object that returns whether or not the BluetoothDevice
31  * passed to it matches the specified filter type constant from
32  * {@link android.bluetooth.BluetoothDevicePicker}.
33  */
34 public final class BluetoothDeviceFilter {
35     private static final String TAG = "BluetoothDeviceFilter";
36 
37     /** The filter interface to external classes. */
38     public interface Filter {
matches(BluetoothDevice device)39         boolean matches(BluetoothDevice device);
40     }
41 
42     /** All filter singleton (referenced directly). */
43     public static final Filter ALL_FILTER = new AllFilter();
44 
45     /** Bonded devices only filter (referenced directly). */
46     public static final Filter BONDED_DEVICE_FILTER = new BondedDeviceFilter();
47 
48     /** Unbonded devices only filter (referenced directly). */
49     public static final Filter UNBONDED_DEVICE_FILTER = new UnbondedDeviceFilter();
50 
51     /** Table of singleton filter objects. */
52     private static final Filter[] FILTERS = {
53             ALL_FILTER,             // FILTER_TYPE_ALL
54             new AudioFilter(),      // FILTER_TYPE_AUDIO
55             new TransferFilter(),   // FILTER_TYPE_TRANSFER
56             new PanuFilter(),       // FILTER_TYPE_PANU
57             new NapFilter()         // FILTER_TYPE_NAP
58     };
59 
60     /** Private constructor. */
BluetoothDeviceFilter()61     private BluetoothDeviceFilter() {
62     }
63 
64     /**
65      * Returns the singleton {@link Filter} object for the specified type,
66      * or {@link #ALL_FILTER} if the type value is out of range.
67      *
68      * @param filterType a constant from BluetoothDevicePicker
69      * @return a singleton object implementing the {@link Filter} interface.
70      */
getFilter(int filterType)71     public static Filter getFilter(int filterType) {
72         if (filterType >= 0 && filterType < FILTERS.length) {
73             return FILTERS[filterType];
74         } else {
75             Log.w(TAG, "Invalid filter type " + filterType + " for device picker");
76             return ALL_FILTER;
77         }
78     }
79 
80     /** Filter that matches all devices. */
81     private static final class AllFilter implements Filter {
matches(BluetoothDevice device)82         public boolean matches(BluetoothDevice device) {
83             return true;
84         }
85     }
86 
87     /** Filter that matches only bonded devices. */
88     private static final class BondedDeviceFilter implements Filter {
matches(BluetoothDevice device)89         public boolean matches(BluetoothDevice device) {
90             return device.getBondState() == BluetoothDevice.BOND_BONDED;
91         }
92     }
93 
94     /** Filter that matches only unbonded devices. */
95     private static final class UnbondedDeviceFilter implements Filter {
matches(BluetoothDevice device)96         public boolean matches(BluetoothDevice device) {
97             return device.getBondState() != BluetoothDevice.BOND_BONDED;
98         }
99     }
100 
101     /** Parent class of filters based on UUID and/or Bluetooth class. */
102     private abstract static class ClassUuidFilter implements Filter {
matches(ParcelUuid[] uuids, BluetoothClass btClass)103         abstract boolean matches(ParcelUuid[] uuids, BluetoothClass btClass);
104 
matches(BluetoothDevice device)105         public boolean matches(BluetoothDevice device) {
106             return matches(device.getUuids(), device.getBluetoothClass());
107         }
108     }
109 
110     /** Filter that matches devices that support AUDIO profiles. */
111     private static final class AudioFilter extends ClassUuidFilter {
112         @Override
matches(ParcelUuid[] uuids, BluetoothClass btClass)113         boolean matches(ParcelUuid[] uuids, BluetoothClass btClass) {
114             if (uuids != null) {
115                 if (BluetoothUuid.containsAnyUuid(uuids, A2dpProfile.SINK_UUIDS)) {
116                     return true;
117                 }
118                 if (BluetoothUuid.containsAnyUuid(uuids, HeadsetProfile.UUIDS)) {
119                     return true;
120                 }
121             } else if (btClass != null) {
122                 if (doesClassMatch(btClass, BluetoothClass.PROFILE_A2DP)
123                         || doesClassMatch(btClass, BluetoothClass.PROFILE_HEADSET)) {
124                     return true;
125                 }
126             }
127             return false;
128         }
129     }
130 
131     /** Filter that matches devices that support Object Transfer. */
132     private static final class TransferFilter extends ClassUuidFilter {
133         @Override
matches(ParcelUuid[] uuids, BluetoothClass btClass)134         boolean matches(ParcelUuid[] uuids, BluetoothClass btClass) {
135             if (uuids != null) {
136                 if (ArrayUtils.contains(uuids, BluetoothUuid.OBEX_OBJECT_PUSH)) {
137                     return true;
138                 }
139             }
140             return btClass != null
141                     && doesClassMatch(btClass, BluetoothClass.PROFILE_OPP);
142         }
143     }
144 
145     /** Filter that matches devices that support PAN User (PANU) profile. */
146     private static final class PanuFilter extends ClassUuidFilter {
147         @Override
matches(ParcelUuid[] uuids, BluetoothClass btClass)148         boolean matches(ParcelUuid[] uuids, BluetoothClass btClass) {
149             if (uuids != null) {
150                 if (ArrayUtils.contains(uuids, BluetoothUuid.PANU)) {
151                     return true;
152                 }
153             }
154             return btClass != null
155                     && doesClassMatch(btClass, BluetoothClass.PROFILE_PANU);
156         }
157     }
158 
159     /** Filter that matches devices that support NAP profile. */
160     private static final class NapFilter extends ClassUuidFilter {
161         @Override
matches(ParcelUuid[] uuids, BluetoothClass btClass)162         boolean matches(ParcelUuid[] uuids, BluetoothClass btClass) {
163             if (uuids != null) {
164                 if (ArrayUtils.contains(uuids, BluetoothUuid.NAP)) {
165                     return true;
166                 }
167             }
168             return btClass != null
169                     && doesClassMatch(btClass, BluetoothClass.PROFILE_NAP);
170         }
171     }
172 
173     @SuppressLint("NewApi") // Hidden API made public
doesClassMatch(BluetoothClass btClass, int classId)174     private static boolean doesClassMatch(BluetoothClass btClass, int classId) {
175         return btClass.doesClassMatch(classId);
176     }
177 }
178