From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: pratyush <codelab@pratyush.dev>
Date: Thu, 1 Jul 2021 12:26:49 +0530
Subject: [PATCH] Bluetooth auto turn off

---
 core/java/android/provider/Settings.java      |  6 ++
 .../server/BluetoothManagerService.java       | 76 +++++++++++++++++++
 2 files changed, 82 insertions(+)

diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 0ac089362b92..d7e3e8f13acd 100755
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -14474,6 +14474,12 @@ public final class Settings {
         public static final String NR_NSA_TRACKING_SCREEN_OFF_MODE =
                 "nr_nsa_tracking_screen_off_mode";
 
+        /**
+         * The amount of time in milliseconds before bluetooth is turned off
+         * @hide
+         */
+        public static final String BLUETOOTH_OFF_TIMEOUT = "bluetooth_off_timeout";
+
         /**
          * Whether to automatically reboot the device after a user defined timeout
          *
diff --git a/services/core/java/com/android/server/BluetoothManagerService.java b/services/core/java/com/android/server/BluetoothManagerService.java
index 9ce7cf27f9ff..9598faa3a667 100644
--- a/services/core/java/com/android/server/BluetoothManagerService.java
+++ b/services/core/java/com/android/server/BluetoothManagerService.java
@@ -21,6 +21,7 @@ import static android.os.UserHandle.USER_SYSTEM;
 
 import android.Manifest;
 import android.app.ActivityManager;
+import android.app.AlarmManager;
 import android.app.AppGlobals;
 import android.app.AppOpsManager;
 import android.bluetooth.BluetoothAdapter;
@@ -512,6 +513,81 @@ class BluetoothManagerService extends IBluetoothManager.Stub {
             Slog.w(TAG, "Unable to resolve SystemUI's UID.");
         }
         mSystemUiUid = systemUiUid;
+
+        /*
+        * System sends ACTION_STATE_CHANGED broadcast soon as any state
+        * changes. what it means in action is we don't have to take care if
+        * device reboot while BT has not been turned off automatically.
+        *
+        * A word of warning though it does not check if device as been
+        * unlocked or not what it means in real life is if you have sometime
+        * like tile ble tracker configured it will turn off BT. As result tile
+        * tracking will fail because of auto timeout. this behaviour can be
+        * changed with UserManager.isUnlocked()
+        * */
+        IntentFilter btFilter = new IntentFilter();
+        btFilter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED);
+        btFilter.addAction(BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED);
+        btFilter.addAction(BluetoothAdapter.ACTION_LOCAL_NAME_CHANGED);
+        context.registerReceiver(new BroadcastReceiver() {
+            @Override
+            public void onReceive(Context broadcastContext, Intent intent) {
+                reconfigureBtTimeoutListener();
+            }
+        }, btFilter);
+
+        context.getContentResolver().registerContentObserver(
+                Settings.Global.getUriFor(Settings.Global.BLUETOOTH_OFF_TIMEOUT),
+                false,
+                new ContentObserver(new Handler(context.getMainLooper())) {
+                    @Override
+                    public void onChange(boolean selfChange) {
+                        super.onChange(selfChange);
+                        reconfigureBtTimeoutListener();
+                    }
+                });
+    }
+
+    private static final AlarmManager.OnAlarmListener listener = () -> {
+        BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
+        if (isBtOnAndDisconnected() && bluetoothAdapter != null) {
+            bluetoothAdapter.disable();
+        }
+    };
+
+    // If device is still connected cancel timeout for now and wait for disconnected signal
+    private void reconfigureBtTimeoutListener() {
+        AlarmManager alarmManager = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
+        if (isTimeoutEnabled(mContext) && isBtOnAndDisconnected()) {
+            final long timeout = SystemClock.elapsedRealtime() + btTimeoutDurationInMilli(mContext);
+            alarmManager.cancel(listener);
+            alarmManager.setExact(
+                    AlarmManager.ELAPSED_REALTIME_WAKEUP,
+                    timeout,
+                    "BT Idle Timeout",
+                    listener,
+                    new Handler(mContext.getMainLooper())
+            );
+        } else {
+            alarmManager.cancel(listener);
+        }
+    }
+
+    private static boolean isBtOnAndDisconnected() {
+        BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
+        return bluetoothAdapter != null && bluetoothAdapter.getState() == BluetoothAdapter.STATE_ON
+                && bluetoothAdapter.getState() == BluetoothAdapter.STATE_ON &&
+                bluetoothAdapter.getConnectionState() == BluetoothAdapter.STATE_DISCONNECTED;
+    }
+
+    private static long btTimeoutDurationInMilli(Context context) {
+        return Settings.Global.getLong(context.getContentResolver(),
+                Settings.Global.BLUETOOTH_OFF_TIMEOUT, 0);
+    }
+
+    /** Zero is default and means disabled */
+    private static boolean isTimeoutEnabled(Context context) {
+        return 0 != btTimeoutDurationInMilli(context);
     }
 
     /**
