diff --git a/core/api/current.txt b/core/api/current.txt index 3bb40b98eb050..6f58a386893b1 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -8742,6 +8742,7 @@ package android.bluetooth { method public android.bluetooth.le.BluetoothLeScanner getBluetoothLeScanner(); method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public java.util.Set getBondedDevices(); method @Deprecated public static android.bluetooth.BluetoothAdapter getDefaultAdapter(); + method @Nullable @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN) public java.time.Duration getDiscoverableTimeout(); method public int getLeMaximumAdvertisingDataLength(); method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public String getName(); method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public int getProfileConnectionState(int); diff --git a/core/api/system-current.txt b/core/api/system-current.txt index ef59b1a9bca11..24ea1842b5cfb 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -2002,6 +2002,8 @@ package android.bluetooth { method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED, android.Manifest.permission.MODIFY_PHONE_STATE}) public boolean removeActiveDevice(int); method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public boolean removeOnMetadataChangedListener(@NonNull android.bluetooth.BluetoothDevice, @NonNull android.bluetooth.BluetoothAdapter.OnMetadataChangedListener); method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED, android.Manifest.permission.MODIFY_PHONE_STATE}) public boolean setActiveDevice(@NonNull android.bluetooth.BluetoothDevice, int); + method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_SCAN, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public int setDiscoverableTimeout(@NonNull java.time.Duration); + method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_SCAN, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public int setScanMode(int); field public static final String ACTION_BLE_STATE_CHANGED = "android.bluetooth.adapter.action.BLE_STATE_CHANGED"; field public static final String ACTION_REQUEST_BLE_SCAN_ALWAYS_AVAILABLE = "android.bluetooth.adapter.action.REQUEST_BLE_SCAN_ALWAYS_AVAILABLE"; field public static final int ACTIVE_DEVICE_ALL = 2; // 0x2 diff --git a/core/java/android/bluetooth/BluetoothAdapter.java b/core/java/android/bluetooth/BluetoothAdapter.java index 2c875fee101b1..20122fb3ca7a8 100644 --- a/core/java/android/bluetooth/BluetoothAdapter.java +++ b/core/java/android/bluetooth/BluetoothAdapter.java @@ -67,6 +67,7 @@ import com.android.internal.annotations.GuardedBy; import java.io.IOException; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.time.Duration; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -399,6 +400,16 @@ public final class BluetoothAdapter { @Retention(RetentionPolicy.SOURCE) public @interface ScanMode {} + /** @hide */ + @IntDef(value = { + BluetoothStatusCodes.SUCCESS, + BluetoothStatusCodes.ERROR_UNKNOWN, + BluetoothStatusCodes.ERROR_BLUETOOTH_NOT_ENABLED, + BluetoothStatusCodes.ERROR_MISSING_BLUETOOTH_SCAN_PERMISSION + }) + @Retention(RetentionPolicy.SOURCE) + public @interface ScanModeStatusCode {} + /** * Indicates that both inquiry scan and page scan are disabled on the local * Bluetooth adapter. Therefore this device is neither discoverable @@ -1615,7 +1626,7 @@ public final class BluetoothAdapter { return mService.getScanMode(mAttributionSource); } } catch (RemoteException e) { - Log.e(TAG, "", e); + throw e.rethrowFromSystemServer(); } finally { mServiceLock.readLock().unlock(); } @@ -1623,143 +1634,110 @@ public final class BluetoothAdapter { } /** - * Set the Bluetooth scan mode of the local Bluetooth adapter. - *

The Bluetooth scan mode determines if the local adapter is - * connectable and/or discoverable from remote Bluetooth devices. - *

For privacy reasons, discoverable mode is automatically turned off - * after durationMillis milliseconds. For example, 120000 milliseconds should be - * enough for a remote device to initiate and complete its discovery process. - *

Valid scan mode values are: - * {@link #SCAN_MODE_NONE}, - * {@link #SCAN_MODE_CONNECTABLE}, - * {@link #SCAN_MODE_CONNECTABLE_DISCOVERABLE}. - *

If Bluetooth state is not {@link #STATE_ON}, this API - * will return false. After turning on Bluetooth, - * wait for {@link #ACTION_STATE_CHANGED} with {@link #STATE_ON} - * to get the updated value. + * Set the local Bluetooth adapter connectablility and discoverability. + *

If the scan mode is set to {@link #SCAN_MODE_CONNECTABLE_DISCOVERABLE}, + * it will change to {@link #SCAN_MODE_CONNECTABLE} after the discoverable timeout. + * The discoverable timeout can be set with {@link #setDiscoverableTimeout} and + * checked with {@link #getDiscoverableTimeout}. By default, the timeout is usually + * 120 seconds on phones which is enough for a remote device to initiate and complete + * its discovery process. *

Applications cannot set the scan mode. They should use - * startActivityForResult( - * BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE}) - * instead. + * {@link #ACTION_REQUEST_DISCOVERABLE} instead. * - * @param mode valid scan mode - * @param durationMillis time in milliseconds to apply scan mode, only used for {@link - * #SCAN_MODE_CONNECTABLE_DISCOVERABLE} - * @return true if the scan mode was set, false otherwise + * @param mode represents the desired state of the local device scan mode + * + * @return status code indicating whether the scan mode was successfully set * @hide */ - @UnsupportedAppUsage(publicAlternatives = "Use {@link #ACTION_REQUEST_DISCOVERABLE}, which " - + "shows UI that confirms the user wants to go into discoverable mode.") - @RequiresLegacyBluetoothPermission + @SystemApi @RequiresBluetoothScanPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN) - public boolean setScanMode(@ScanMode int mode, long durationMillis) { + @RequiresPermission(allOf = { + android.Manifest.permission.BLUETOOTH_SCAN, + android.Manifest.permission.BLUETOOTH_PRIVILEGED, + }) + @ScanModeStatusCode + public int setScanMode(@ScanMode int mode) { if (getState() != STATE_ON) { - return false; + return BluetoothStatusCodes.ERROR_BLUETOOTH_NOT_ENABLED; } try { mServiceLock.readLock().lock(); if (mService != null) { - int durationSeconds = Math.toIntExact(durationMillis / 1000); - return mService.setScanMode(mode, durationSeconds, mAttributionSource); + return mService.setScanMode(mode, mAttributionSource); } } catch (RemoteException e) { - Log.e(TAG, "", e); - } catch (ArithmeticException ex) { - Log.e(TAG, "setScanMode: Duration in seconds outside of the bounds of an int"); - throw new IllegalArgumentException("Duration not in bounds. In seconds, the " - + "durationMillis must be in the range of an int"); + throw e.rethrowFromSystemServer(); } finally { mServiceLock.readLock().unlock(); } - return false; + return BluetoothStatusCodes.ERROR_UNKNOWN; } /** - * Set the Bluetooth scan mode of the local Bluetooth adapter. - *

The Bluetooth scan mode determines if the local adapter is - * connectable and/or discoverable from remote Bluetooth devices. - *

For privacy reasons, discoverable mode is automatically turned off - * after duration seconds. For example, 120 seconds should be - * enough for a remote device to initiate and complete its discovery - * process. - *

Valid scan mode values are: - * {@link #SCAN_MODE_NONE}, - * {@link #SCAN_MODE_CONNECTABLE}, - * {@link #SCAN_MODE_CONNECTABLE_DISCOVERABLE}. - *

If Bluetooth state is not {@link #STATE_ON}, this API - * will return false. After turning on Bluetooth, - * wait for {@link #ACTION_STATE_CHANGED} with {@link #STATE_ON} - * to get the updated value. - *

Applications cannot set the scan mode. They should use - * startActivityForResult( - * BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE}) - * instead. + * Get the timeout duration of the {@link #SCAN_MODE_CONNECTABLE_DISCOVERABLE}. * - * @param mode valid scan mode - * @return true if the scan mode was set, false otherwise + * @return the duration of the discoverable timeout or null if an error has occurred + */ + @RequiresBluetoothScanPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN) + public @Nullable Duration getDiscoverableTimeout() { + if (getState() != STATE_ON) { + return null; + } + try { + mServiceLock.readLock().lock(); + if (mService != null) { + long timeout = mService.getDiscoverableTimeout(mAttributionSource); + return (timeout == -1) ? null : Duration.ofSeconds(timeout); + } + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } finally { + mServiceLock.readLock().unlock(); + } + return null; + } + + /** + * Set the total time the Bluetooth local adapter will stay discoverable when + * {@link #setScanMode} is called with {@link #SCAN_MODE_CONNECTABLE_DISCOVERABLE} mode. + * After this timeout, the scan mode will fallback to {@link #SCAN_MODE_CONNECTABLE}. + *

If timeout is set to 0, no timeout will occur and the scan mode will + * be persisted until a subsequent call to {@link #setScanMode}. + * + * @param timeout represents the total duration the local Bluetooth adapter will remain + * discoverable, or no timeout if set to 0 + * @return whether the timeout was successfully set + * @throws IllegalArgumentException if timeout duration in seconds is more + * than {@link Integer#MAX_VALUE} * @hide */ - @UnsupportedAppUsage - @RequiresLegacyBluetoothPermission + @SystemApi @RequiresBluetoothScanPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN) - public boolean setScanMode(@ScanMode int mode) { + @RequiresPermission(allOf = { + android.Manifest.permission.BLUETOOTH_SCAN, + android.Manifest.permission.BLUETOOTH_PRIVILEGED, + }) + @ScanModeStatusCode + public int setDiscoverableTimeout(@NonNull Duration timeout) { if (getState() != STATE_ON) { - return false; + return BluetoothStatusCodes.ERROR_BLUETOOTH_NOT_ENABLED; + } + if (timeout.toSeconds() > Integer.MAX_VALUE) { + throw new IllegalArgumentException("Timeout in seconds must be less or equal to " + + Integer.MAX_VALUE); } try { mServiceLock.readLock().lock(); if (mService != null) { - return mService.setScanMode(mode, getDiscoverableTimeout(), mAttributionSource); + return mService.setDiscoverableTimeout(timeout.toSeconds(), mAttributionSource); } } catch (RemoteException e) { - Log.e(TAG, "", e); - } finally { - mServiceLock.readLock().unlock(); - } - return false; - } - - /** @hide */ - @UnsupportedAppUsage - @RequiresBluetoothScanPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN) - public int getDiscoverableTimeout() { - if (getState() != STATE_ON) { - return -1; - } - try { - mServiceLock.readLock().lock(); - if (mService != null) { - return mService.getDiscoverableTimeout(mAttributionSource); - } - } catch (RemoteException e) { - Log.e(TAG, "", e); - } finally { - mServiceLock.readLock().unlock(); - } - return -1; - } - - /** @hide */ - @UnsupportedAppUsage - @RequiresBluetoothScanPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN) - public void setDiscoverableTimeout(int timeout) { - if (getState() != STATE_ON) { - return; - } - try { - mServiceLock.readLock().lock(); - if (mService != null) { - mService.setDiscoverableTimeout(timeout, mAttributionSource); - } - } catch (RemoteException e) { - Log.e(TAG, "", e); + throw e.rethrowFromSystemServer(); } finally { mServiceLock.readLock().unlock(); } + return BluetoothStatusCodes.ERROR_UNKNOWN; } /** diff --git a/core/java/android/bluetooth/BluetoothStatusCodes.java b/core/java/android/bluetooth/BluetoothStatusCodes.java index ca01784efd88c..5ba7bb1104498 100644 --- a/core/java/android/bluetooth/BluetoothStatusCodes.java +++ b/core/java/android/bluetooth/BluetoothStatusCodes.java @@ -40,7 +40,7 @@ public final class BluetoothStatusCodes { /** * Error code indicating that the API call was initiated by neither the system nor the active - * Zuser + * user */ public static final int ERROR_BLUETOOTH_NOT_ALLOWED = 2; diff --git a/core/tests/bluetoothtests/src/android/bluetooth/BluetoothTestUtils.java b/core/tests/bluetoothtests/src/android/bluetooth/BluetoothTestUtils.java index 409025bc670d7..8eb6ebcda8261 100644 --- a/core/tests/bluetoothtests/src/android/bluetooth/BluetoothTestUtils.java +++ b/core/tests/bluetoothtests/src/android/bluetooth/BluetoothTestUtils.java @@ -587,7 +587,8 @@ public class BluetoothTestUtils extends Assert { final IntentFilter filter = new IntentFilter(BluetoothAdapter.ACTION_SCAN_MODE_CHANGED); mContext.registerReceiver(receiver, filter); - assertTrue(adapter.setScanMode(BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE)); + assertEquals(adapter.setScanMode(BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE), + BluetoothStatusCodes.SUCCESS); boolean success = false; try { success = completionSemaphore.tryAcquire(DISCOVERABLE_UNDISCOVERABLE_TIMEOUT, @@ -637,7 +638,8 @@ public class BluetoothTestUtils extends Assert { final IntentFilter filter = new IntentFilter(BluetoothAdapter.ACTION_SCAN_MODE_CHANGED); mContext.registerReceiver(receiver, filter); - assertTrue(adapter.setScanMode(BluetoothAdapter.SCAN_MODE_CONNECTABLE)); + assertEquals(adapter.setScanMode(BluetoothAdapter.SCAN_MODE_CONNECTABLE), + BluetoothStatusCodes.SUCCESS); boolean success = false; try { success = completionSemaphore.tryAcquire(DISCOVERABLE_UNDISCOVERABLE_TIMEOUT, diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothAdapter.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothAdapter.java index 80b03a43b055c..e7a6b327bacd7 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothAdapter.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothAdapter.java @@ -19,11 +19,13 @@ package com.android.settingslib.bluetooth; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothProfile; +import android.bluetooth.BluetoothStatusCodes; import android.bluetooth.le.BluetoothLeScanner; import android.content.Context; import android.os.ParcelUuid; import android.util.Log; +import java.time.Duration; import java.util.List; import java.util.Set; @@ -140,7 +142,7 @@ public class LocalBluetoothAdapter { } public void setDiscoverableTimeout(int timeout) { - mAdapter.setDiscoverableTimeout(timeout); + mAdapter.setDiscoverableTimeout(Duration.ofSeconds(timeout)); } public long getDiscoveryEndMillis() { @@ -156,7 +158,9 @@ public class LocalBluetoothAdapter { } public boolean setScanMode(int mode, int duration) { - return mAdapter.setScanMode(mode, duration); + return (mAdapter.setDiscoverableTimeout(Duration.ofSeconds(duration)) + == BluetoothStatusCodes.SUCCESS + && mAdapter.setScanMode(mode) == BluetoothStatusCodes.SUCCESS); } public void startScanning(boolean force) {