1 /* 2 * Copyright (C) 2018 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.server.deviceidle; 18 19 import android.bluetooth.BluetoothAdapter; 20 import android.bluetooth.BluetoothDevice; 21 import android.bluetooth.BluetoothManager; 22 import android.bluetooth.BluetoothProfile; 23 import android.content.BroadcastReceiver; 24 import android.content.Context; 25 import android.content.Intent; 26 import android.content.IntentFilter; 27 import android.os.Handler; 28 import android.os.Message; 29 30 import com.android.internal.annotations.GuardedBy; 31 import com.android.internal.annotations.VisibleForTesting; 32 import com.android.server.DeviceIdleInternal; 33 34 // TODO: Should we part of the apex, or the platform?? 35 36 /** 37 * Track whether there are any active Bluetooth devices connected. 38 */ 39 public class BluetoothConstraint implements IDeviceIdleConstraint { 40 private static final String TAG = BluetoothConstraint.class.getSimpleName(); 41 private static final long INACTIVITY_TIMEOUT_MS = 20 * 60 * 1000L; 42 43 private final Context mContext; 44 private final Handler mHandler; 45 private final DeviceIdleInternal mLocalService; 46 private final BluetoothManager mBluetoothManager; 47 48 private volatile boolean mConnected = true; 49 private volatile boolean mMonitoring = false; 50 BluetoothConstraint( Context context, Handler handler, DeviceIdleInternal localService)51 public BluetoothConstraint( 52 Context context, Handler handler, DeviceIdleInternal localService) { 53 mContext = context; 54 mHandler = handler; 55 mLocalService = localService; 56 mBluetoothManager = mContext.getSystemService(BluetoothManager.class); 57 } 58 59 @Override startMonitoring()60 public synchronized void startMonitoring() { 61 // Start by assuming we have a connected bluetooth device. 62 mConnected = true; 63 mMonitoring = true; 64 65 // Register a receiver to get updates on bluetooth devices disconnecting or the 66 // adapter state changing. 67 IntentFilter filter = new IntentFilter(); 68 filter.addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED); 69 filter.addAction(BluetoothDevice.ACTION_ACL_CONNECTED); 70 filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED); 71 mContext.registerReceiver(mReceiver, filter); 72 73 // Some devices will try to stay connected indefinitely. Set a timeout to ignore them. 74 mHandler.sendMessageDelayed( 75 Message.obtain(mHandler, mTimeoutCallback), INACTIVITY_TIMEOUT_MS); 76 77 // Now we have the receiver registered, make a direct check for connected devices. 78 updateAndReportActiveLocked(); 79 } 80 81 @Override stopMonitoring()82 public synchronized void stopMonitoring() { 83 mContext.unregisterReceiver(mReceiver); 84 mHandler.removeCallbacks(mTimeoutCallback); 85 mMonitoring = false; 86 } 87 cancelMonitoringDueToTimeout()88 private synchronized void cancelMonitoringDueToTimeout() { 89 if (mMonitoring) { 90 mMonitoring = false; 91 mLocalService.onConstraintStateChanged(this, /* active= */ false); 92 } 93 } 94 95 /** 96 * Check the latest data from BluetoothManager and let DeviceIdleController know whether we 97 * have connected devices (for example TV remotes / gamepads) and thus want to stay awake. 98 */ 99 @GuardedBy("this") updateAndReportActiveLocked()100 private void updateAndReportActiveLocked() { 101 final boolean connected = isBluetoothConnected(mBluetoothManager); 102 if (connected != mConnected) { 103 mConnected = connected; 104 // If we lost all of our connections, we are on track to going into idle state. 105 mLocalService.onConstraintStateChanged(this, /* active= */ mConnected); 106 } 107 } 108 109 /** 110 * True if the bluetooth adapter exists, is enabled, and has at least one GATT device connected. 111 */ 112 @VisibleForTesting isBluetoothConnected(BluetoothManager bluetoothManager)113 static boolean isBluetoothConnected(BluetoothManager bluetoothManager) { 114 BluetoothAdapter adapter = bluetoothManager.getAdapter(); 115 if (adapter != null && adapter.isEnabled()) { 116 return bluetoothManager.getConnectedDevices(BluetoothProfile.GATT).size() > 0; 117 } 118 return false; 119 } 120 121 /** 122 * Registered in {@link #startMonitoring()}, unregistered in {@link #stopMonitoring()}. 123 */ 124 @VisibleForTesting 125 final BroadcastReceiver mReceiver = new BroadcastReceiver() { 126 @Override 127 public void onReceive(Context context, Intent intent) { 128 if (BluetoothDevice.ACTION_ACL_CONNECTED.equals(intent.getAction())) { 129 mLocalService.exitIdle("bluetooth"); 130 } else { 131 updateAndReportActiveLocked(); 132 } 133 } 134 }; 135 136 private final Runnable mTimeoutCallback = () -> cancelMonitoringDueToTimeout(); 137 } 138