/* * Copyright (C) 2012 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.server.bluetooth; import static android.bluetooth.BluetoothAdapter.STATE_BLE_ON; import static android.bluetooth.BluetoothAdapter.STATE_BLE_TURNING_OFF; import static android.bluetooth.BluetoothAdapter.STATE_BLE_TURNING_ON; import static android.bluetooth.BluetoothAdapter.STATE_OFF; import static android.bluetooth.BluetoothAdapter.STATE_ON; import static android.bluetooth.BluetoothAdapter.STATE_TURNING_OFF; import static android.bluetooth.BluetoothAdapter.STATE_TURNING_ON; import static android.bluetooth.BluetoothProtoEnums.ENABLE_DISABLE_REASON_AIRPLANE_MODE; import static android.bluetooth.BluetoothProtoEnums.ENABLE_DISABLE_REASON_APPLICATION_REQUEST; import static android.bluetooth.BluetoothProtoEnums.ENABLE_DISABLE_REASON_CRASH; import static android.bluetooth.BluetoothProtoEnums.ENABLE_DISABLE_REASON_DISALLOWED; import static android.bluetooth.BluetoothProtoEnums.ENABLE_DISABLE_REASON_FACTORY_RESET; import static android.bluetooth.BluetoothProtoEnums.ENABLE_DISABLE_REASON_RESTARTED; import static android.bluetooth.BluetoothProtoEnums.ENABLE_DISABLE_REASON_RESTORE_USER_SETTING; import static android.bluetooth.BluetoothProtoEnums.ENABLE_DISABLE_REASON_SATELLITE_MODE; import static android.bluetooth.BluetoothProtoEnums.ENABLE_DISABLE_REASON_START_ERROR; import static android.bluetooth.BluetoothProtoEnums.ENABLE_DISABLE_REASON_SYSTEM_BOOT; import static android.bluetooth.BluetoothProtoEnums.ENABLE_DISABLE_REASON_USER_SWITCH; import static android.os.PowerExemptionManager.TEMPORARY_ALLOW_LIST_TYPE_FOREGROUND_SERVICE_ALLOWED; import static com.android.modules.utils.build.SdkLevel.isAtLeastV; import static java.util.Objects.requireNonNull; import android.annotation.NonNull; import android.app.ActivityManager; import android.app.BroadcastOptions; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothStatusCodes; import android.bluetooth.IBluetooth; import android.bluetooth.IBluetoothCallback; import android.bluetooth.IBluetoothManager; import android.bluetooth.IBluetoothManagerCallback; import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.ServiceConnection; import android.content.pm.ApplicationInfo; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.database.ContentObserver; import android.os.Binder; import android.os.Build; import android.os.Bundle; import android.os.Handler; import android.os.IBinder; import android.os.Looper; import android.os.Message; import android.os.PowerExemptionManager; import android.os.Process; import android.os.RemoteCallbackList; import android.os.RemoteException; import android.os.SystemClock; import android.os.SystemProperties; import android.os.UserHandle; import android.os.UserManager; import android.provider.Settings; import android.provider.Settings.SettingNotFoundException; import android.sysprop.BluetoothProperties; import android.util.proto.ProtoOutputStream; import androidx.annotation.RequiresApi; import com.android.bluetooth.BluetoothStatsLog; import com.android.bluetooth.flags.Flags; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.modules.expresslog.Counter; import com.android.server.BluetoothManagerServiceDumpProto; import com.android.server.bluetooth.airplane.AirplaneModeListener; import com.android.server.bluetooth.satellite.SatelliteModeListener; import libcore.util.SneakyThrow; import kotlin.Unit; import kotlin.time.TimeSource; import java.io.FileDescriptor; import java.io.FileOutputStream; import java.io.PrintWriter; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.time.Duration; import java.time.Instant; import java.time.ZoneId; import java.time.format.DateTimeFormatter; import java.util.Arrays; import java.util.LinkedList; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.concurrent.Callable; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutionException; import java.util.concurrent.Executors; import java.util.concurrent.FutureTask; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import java.util.concurrent.locks.ReentrantReadWriteLock; class BluetoothManagerService { private static final String TAG = BluetoothManagerService.class.getSimpleName(); private static final int ACTIVE_LOG_MAX_SIZE = 20; private static final int CRASH_LOG_MAX_SIZE = 100; // See android.os.Build.HW_TIMEOUT_MULTIPLIER. This should not be set on real hw private static final int HW_MULTIPLIER = SystemProperties.getInt("ro.hw_timeout_multiplier", 1); // Maximum msec to wait for a bind private static final int TIMEOUT_BIND_MS = 4000 * HW_MULTIPLIER; // Timeout value for synchronous binder call private static final Duration STATE_TIMEOUT = Duration.ofSeconds(4 * HW_MULTIPLIER); // Maximum msec to wait for service restart private static final int SERVICE_RESTART_TIME_MS = 400 * HW_MULTIPLIER; // Maximum msec to wait for restart due to error private static final int ERROR_RESTART_TIME_MS = 3000 * HW_MULTIPLIER; // Maximum msec to delay MESSAGE_USER_SWITCHED private static final int USER_SWITCHED_TIME_MS = 200 * HW_MULTIPLIER; // Delay for the addProxy function in msec private static final int ADD_PROXY_DELAY_MS = 100 * HW_MULTIPLIER; // Delay for retrying enable and disable in msec private static final int ENABLE_DISABLE_DELAY_MS = 300 * HW_MULTIPLIER; @VisibleForTesting static final int MESSAGE_ENABLE = 1; @VisibleForTesting static final int MESSAGE_DISABLE = 2; @VisibleForTesting static final int MESSAGE_HANDLE_ENABLE_DELAYED = 3; @VisibleForTesting static final int MESSAGE_HANDLE_DISABLE_DELAYED = 4; @VisibleForTesting static final int MESSAGE_BLUETOOTH_SERVICE_CONNECTED = 40; @VisibleForTesting static final int MESSAGE_BLUETOOTH_SERVICE_DISCONNECTED = 41; @VisibleForTesting static final int MESSAGE_RESTART_BLUETOOTH_SERVICE = 42; @VisibleForTesting static final int MESSAGE_BLUETOOTH_STATE_CHANGE = 60; @VisibleForTesting static final int MESSAGE_TIMEOUT_BIND = 100; @VisibleForTesting static final int MESSAGE_GET_NAME_AND_ADDRESS = 200; @VisibleForTesting static final int MESSAGE_USER_SWITCHED = 300; @VisibleForTesting static final int MESSAGE_USER_UNLOCKED = 301; @VisibleForTesting static final int MESSAGE_RESTORE_USER_SETTING = 500; private static final int RESTORE_SETTING_TO_ON = 1; private static final int RESTORE_SETTING_TO_OFF = 0; private static final int MAX_ERROR_RESTART_RETRIES = 6; private static final int MAX_WAIT_FOR_ENABLE_DISABLE_RETRIES = 10; // Bluetooth persisted setting is off @VisibleForTesting static final int BLUETOOTH_OFF = 0; // Bluetooth persisted setting is on // and Airplane mode won't affect Bluetooth state at start up // This is the default value @VisibleForTesting static final int BLUETOOTH_ON_BLUETOOTH = 1; // Bluetooth persisted setting is on // but Airplane mode will affect Bluetooth state at start up // and Airplane mode will have higher priority. @VisibleForTesting static final int BLUETOOTH_ON_AIRPLANE = 2; private final Context mContext; private final Looper mLooper; private final UserManager mUserManager; // Locks are not provided for mName and mAddress. // They are accessed in handler or broadcast receiver, same thread context. private String mAddress = null; private String mName = null; private final ContentResolver mContentResolver; private final RemoteCallbackList mCallbacks = new RemoteCallbackList(); private final BluetoothServiceBinder mBinder; private final ReentrantReadWriteLock mAdapterLock = new ReentrantReadWriteLock(); @GuardedBy("mAdapterLock") private AdapterBinder mAdapter = null; // used inside handler thread private boolean mQuietEnable = false; private boolean mEnable = false; private boolean mShutdownInProgress = false; private Context mCurrentUserContext = null; static String timeToLog(long timestamp) { return DateTimeFormatter.ofPattern("MM-dd HH:mm:ss.SSS") .withZone(ZoneId.systemDefault()) .format(Instant.ofEpochMilli(timestamp)); } // Used for tracking apps that enabled / disabled Bluetooth. private static class ActiveLog { private int mReason; private String mPackageName; private boolean mEnable; private boolean mIsBle; private long mTimestamp; ActiveLog(int reason, String packageName, boolean enable, boolean isBle, long timestamp) { mReason = reason; mPackageName = packageName; mEnable = enable; mIsBle = isBle; mTimestamp = timestamp; Log.d(TAG, this.toString()); } @Override public String toString() { return timeToLog(mTimestamp) + (" \tPackage [" + mPackageName + "]") + " requested to" + (" [" + (mEnable ? "Enable" : "Disable") + (mIsBle ? "Ble" : "") + "]") + (". \tReason is " + getEnableDisableReasonString(mReason)); } long getTimestamp() { return mTimestamp; } boolean getEnable() { return mEnable; } void dump(ProtoOutputStream proto) { proto.write(BluetoothManagerServiceDumpProto.ActiveLog.TIMESTAMP_MS, mTimestamp); proto.write(BluetoothManagerServiceDumpProto.ActiveLog.ENABLE, mEnable); proto.write(BluetoothManagerServiceDumpProto.ActiveLog.PACKAGE_NAME, mPackageName); proto.write(BluetoothManagerServiceDumpProto.ActiveLog.REASON, mReason); } } private final LinkedList mActiveLogs = new LinkedList<>(); private final LinkedList mCrashTimestamps = new LinkedList<>(); private int mCrashes = 0; private long mLastEnabledTime; // configuration from external IBinder call which is used to // synchronize with broadcast receiver. private boolean mQuietEnableExternal = false; private boolean mEnableExternal = false; // Map of apps registered to keep BLE scanning on. private Map mBleApps = new ConcurrentHashMap<>(); private final BluetoothAdapterState mState = new BluetoothAdapterState(); private final BluetoothHandler mHandler; private int mErrorRecoveryRetryCounter = 0; private final boolean mIsHearingAidProfileSupported; private final IBluetoothCallback mBluetoothCallback = new IBluetoothCallback.Stub() { @Override public void onBluetoothStateChange(int prevState, int newState) throws RemoteException { mHandler.obtainMessage(MESSAGE_BLUETOOTH_STATE_CHANGE, prevState, newState) .sendToTarget(); } }; public void onUserRestrictionsChanged(UserHandle userHandle) { final boolean newBluetoothDisallowed = mUserManager.hasUserRestrictionForUser(UserManager.DISALLOW_BLUETOOTH, userHandle); // Disallow Bluetooth sharing when either Bluetooth is disallowed or Bluetooth sharing // is disallowed final boolean newBluetoothSharingDisallowed = mUserManager.hasUserRestrictionForUser( UserManager.DISALLOW_BLUETOOTH_SHARING, userHandle) || newBluetoothDisallowed; // Disable OPP activities for this userHandle updateOppLauncherComponentState(userHandle, newBluetoothSharingDisallowed); // DISALLOW_BLUETOOTH can only be set by DO or PO on the system user. // Only trigger once instead of for all users if (UserHandle.SYSTEM.equals(userHandle) && newBluetoothDisallowed) { sendDisableMsg(ENABLE_DISABLE_REASON_DISALLOWED); } } boolean onFactoryReset() { // Wait for stable state if bluetooth is temporary state. int state = getState(); if (state == STATE_BLE_TURNING_ON || state == STATE_TURNING_ON || state == STATE_TURNING_OFF) { if (!waitForState(STATE_BLE_ON, STATE_ON)) { return false; } } // Clear registered LE apps to force shut-off Bluetooth clearBleApps(); state = getState(); mAdapterLock.readLock().lock(); try { if (mAdapter == null) { return false; } if (state == STATE_BLE_ON) { addActiveLog(ENABLE_DISABLE_REASON_FACTORY_RESET, false); mAdapter.stopBle(mContext.getAttributionSource()); return true; } else if (state == STATE_ON) { addActiveLog(ENABLE_DISABLE_REASON_FACTORY_RESET, false); mAdapter.disable(mContext.getAttributionSource()); return true; } } catch (RemoteException e) { Log.e(TAG, "Unable to shutdown Bluetooth", e); } finally { mAdapterLock.readLock().unlock(); } return false; } private int estimateBusyTime(int state) { if (state == STATE_BLE_ON && isBluetoothPersistedStateOn()) { // Bluetooth is in BLE and is starting classic return SERVICE_RESTART_TIME_MS; } else if (state != STATE_ON && state != STATE_OFF && state != STATE_BLE_ON) { // Bluetooth is turning state return ADD_PROXY_DELAY_MS; } else if (mHandler.hasMessages(MESSAGE_ENABLE) || mHandler.hasMessages(MESSAGE_DISABLE) || mHandler.hasMessages(MESSAGE_HANDLE_ENABLE_DELAYED) || mHandler.hasMessages(MESSAGE_HANDLE_DISABLE_DELAYED) || mHandler.hasMessages(MESSAGE_RESTART_BLUETOOTH_SERVICE) || mHandler.hasMessages(MESSAGE_TIMEOUT_BIND)) { Log.d( TAG, "Busy reason:" + " ENABLE=" + mHandler.hasMessages(MESSAGE_ENABLE) + " DISABLE=" + mHandler.hasMessages(MESSAGE_DISABLE) + " HANDLE_ENABLE_DELAYED=" + mHandler.hasMessages(MESSAGE_HANDLE_ENABLE_DELAYED) + " HANDLE_DISABLE_DELAYED=" + mHandler.hasMessages(MESSAGE_HANDLE_DISABLE_DELAYED) + " RESTART_BLUETOOTH_SERVICE=" + mHandler.hasMessages(MESSAGE_RESTART_BLUETOOTH_SERVICE) + " TIMEOUT_BIND=" + mHandler.hasMessages(MESSAGE_TIMEOUT_BIND)); // Bluetooth is restarting return SERVICE_RESTART_TIME_MS; } return 0; } private void delayModeChangedIfNeeded(Object token, Runnable r, String modechanged) { final int state = getState(); final int delayMs = estimateBusyTime(state); Log.d( TAG, ("delayModeChangedIfNeeded(" + modechanged + "):") + (" state=" + BluetoothAdapter.nameForState(state)) + (" Airplane.isOnOverrode=" + AirplaneModeListener.isOnOverrode()) + (" Airplane.isOn=" + AirplaneModeListener.isOn()) + (" isSatelliteModeOn()=" + isSatelliteModeOn()) + (" delayed=" + delayMs + "ms")); mHandler.removeCallbacksAndMessages(token); if (delayMs > 0) { mHandler.postDelayed( () -> delayModeChangedIfNeeded(token, r, modechanged), token, delayMs); } else { r.run(); } } /** Send Intent to the Notification Service in the Bluetooth app */ Unit sendToggleNotification(String notificationReason) { Intent intent = new Intent("android.bluetooth.notification.action.SEND_TOGGLE_NOTIFICATION"); intent.setComponent(resolveSystemService(intent)); intent.putExtra( "android.bluetooth.notification.extra.NOTIFICATION_REASON", notificationReason); mCurrentUserContext.startService(intent); return Unit.INSTANCE; } private static final Object ON_AIRPLANE_MODE_CHANGED_TOKEN = new Object(); private static final Object ON_SATELLITE_MODE_CHANGED_TOKEN = new Object(); private static final Object ON_SWITCH_USER_TOKEN = new Object(); Unit onAirplaneModeChanged(boolean isAirplaneModeOn) { mHandler.postDelayed( () -> delayModeChangedIfNeeded( ON_AIRPLANE_MODE_CHANGED_TOKEN, () -> handleAirplaneModeChanged(isAirplaneModeOn), "onAirplaneModeChanged"), ON_AIRPLANE_MODE_CHANGED_TOKEN, 0); return Unit.INSTANCE; } // TODO(b/289584302): Update to private once use_new_satellite_mode is enabled Unit onSatelliteModeChanged(boolean isSatelliteModeOn) { mHandler.postDelayed( () -> delayModeChangedIfNeeded( ON_SATELLITE_MODE_CHANGED_TOKEN, () -> handleSatelliteModeChanged(isSatelliteModeOn), "onSatelliteModeChanged"), ON_SATELLITE_MODE_CHANGED_TOKEN, 0); return Unit.INSTANCE; } void onSwitchUser(UserHandle userHandle) { mHandler.postDelayed( () -> delayModeChangedIfNeeded( ON_SWITCH_USER_TOKEN, () -> handleSwitchUser(userHandle), "onSwitchUser"), ON_SWITCH_USER_TOKEN, 0); } private void forceToOffFromModeChange(int currentState, int reason) { // Clear registered LE apps to force shut-off clearBleApps(); if (reason == ENABLE_DISABLE_REASON_SATELLITE_MODE || !AirplaneModeListener.hasUserToggledApm(mCurrentUserContext)) { // AirplaneMode can have a state where it does not impact the AutoOnFeature AutoOnFeature.pause(); } if (currentState == STATE_ON) { sendDisableMsg(reason); } else if (currentState == STATE_BLE_ON) { // If currentState is BLE_ON make sure we trigger stopBle mAdapterLock.readLock().lock(); try { if (mAdapter != null) { addActiveLog(reason, false); mAdapter.stopBle(mContext.getAttributionSource()); mEnable = false; mEnableExternal = false; } } catch (RemoteException e) { Log.e(TAG, "Unable to call stopBle", e); } finally { mAdapterLock.readLock().unlock(); } } } private void handleAirplaneModeChanged(boolean isAirplaneModeOn) { synchronized (this) { if (isBluetoothPersistedStateOn()) { if (isAirplaneModeOn) { setBluetoothPersistedState(BLUETOOTH_ON_AIRPLANE); } else { setBluetoothPersistedState(BLUETOOTH_ON_BLUETOOTH); } } int currentState = mState.get(); Log.d( TAG, ("handleAirplaneModeChanged(" + isAirplaneModeOn + "):") + (" currentState=" + BluetoothAdapter.nameForState(currentState))); if (isAirplaneModeOn) { forceToOffFromModeChange(currentState, ENABLE_DISABLE_REASON_AIRPLANE_MODE); } else if (mEnableExternal) { sendEnableMsg(mQuietEnableExternal, ENABLE_DISABLE_REASON_AIRPLANE_MODE); } else if (currentState != STATE_ON) { autoOnSetupTimer(); } } } private void handleSatelliteModeChanged(boolean isSatelliteModeOn) { final int currentState = mState.get(); if (shouldBluetoothBeOn(isSatelliteModeOn) && currentState != STATE_ON) { sendEnableMsg(mQuietEnableExternal, ENABLE_DISABLE_REASON_SATELLITE_MODE); } else if (!shouldBluetoothBeOn(isSatelliteModeOn) && currentState != STATE_OFF) { forceToOffFromModeChange(currentState, ENABLE_DISABLE_REASON_SATELLITE_MODE); } else if (!isSatelliteModeOn && !shouldBluetoothBeOn(isSatelliteModeOn) && currentState != STATE_ON) { autoOnSetupTimer(); } } private boolean shouldBluetoothBeOn(boolean isSatelliteModeOn) { if (!isBluetoothPersistedStateOn()) { Log.d(TAG, "shouldBluetoothBeOn: User want BT off."); return false; } if (isSatelliteModeOn) { Log.d(TAG, "shouldBluetoothBeOn: BT should be off as satellite mode is on."); return false; } if (AirplaneModeListener.isOnOverrode() && isBluetoothPersistedStateOnAirplane()) { Log.d(TAG, "shouldBluetoothBeOn: BT should be off as airplaneMode is on."); return false; } Log.d(TAG, "shouldBluetoothBeOn: BT should be on."); return true; } private final BroadcastReceiver mReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { String action = intent.getAction(); if (BluetoothAdapter.ACTION_LOCAL_NAME_CHANGED.equals(action)) { String newName = intent.getStringExtra(BluetoothAdapter.EXTRA_LOCAL_NAME); if (newName != null) { Log.d(TAG, "Bluetooth Adapter name changed to " + newName); storeNameAndAddress(newName, null); } } else if (BluetoothAdapter.ACTION_BLUETOOTH_ADDRESS_CHANGED.equals(action)) { String newAddress = intent.getStringExtra(BluetoothAdapter.EXTRA_BLUETOOTH_ADDRESS); if (newAddress != null) { Log.d(TAG, "Local address changed to …" + logAddress(newAddress)); storeNameAndAddress(null, newAddress); } else { Log.e(TAG, "No Bluetooth Adapter address parameter found"); } } else if (Intent.ACTION_SETTING_RESTORED.equals(action)) { final String name = intent.getStringExtra(Intent.EXTRA_SETTING_NAME); if (Settings.Global.BLUETOOTH_ON.equals(name)) { // The Bluetooth On state may be changed during system restore. final String prevValue = intent.getStringExtra(Intent.EXTRA_SETTING_PREVIOUS_VALUE); final String newValue = intent.getStringExtra(Intent.EXTRA_SETTING_NEW_VALUE); Log.d( TAG, "ACTION_SETTING_RESTORED with BLUETOOTH_ON" + (" prevValue=" + prevValue) + (" newValue=" + newValue)); if ((newValue != null) && (prevValue != null) && !prevValue.equals(newValue)) { mHandler.obtainMessage( MESSAGE_RESTORE_USER_SETTING, newValue.equals("0") ? RESTORE_SETTING_TO_OFF : RESTORE_SETTING_TO_ON, 0) .sendToTarget(); } } } else if (action.equals(Intent.ACTION_SHUTDOWN)) { Log.i(TAG, "Device is shutting down."); mShutdownInProgress = true; mAdapterLock.readLock().lock(); try { mEnable = false; mEnableExternal = false; if (mAdapter != null && mState.oneOf(STATE_BLE_ON)) { mAdapter.stopBle(mContext.getAttributionSource()); } else if (mAdapter != null && mState.oneOf(STATE_ON)) { mAdapter.disable(mContext.getAttributionSource()); } } catch (RemoteException e) { Log.e(TAG, "Unable to shutdown Bluetooth", e); } finally { mAdapterLock.readLock().unlock(); } } } }; BluetoothManagerService(@NonNull Context context, @NonNull Looper looper) { mContext = requireNonNull(context, "Context cannot be null"); mContentResolver = requireNonNull(mContext.getContentResolver(), "Resolver cannot be null"); mLooper = requireNonNull(looper, "Looper cannot be null"); mUserManager = requireNonNull( mContext.getSystemService(UserManager.class), "UserManager system service cannot be null"); mBinder = new BluetoothServiceBinder(this, mLooper, mContext, mUserManager); mHandler = new BluetoothHandler(mLooper); // Observe BLE scan only mode settings change. if (Flags.respectBleScanSetting()) { BleScanSettingListener.initialize(mLooper, mContentResolver, this::onBleScanDisabled); } else { registerForBleScanModeChange(); } // Disable ASHA if BLE is not supported, overriding any system property if (!isBleSupported(mContext)) { mIsHearingAidProfileSupported = false; } else { // ASHA default value is: // * disabled on Automotive, TV, and Watch. // * enabled for other form factor // This default value can be overridden with a system property final boolean isAshaEnabledByDefault = !(isAutomotive(mContext) || isWatch(mContext) || isTv(mContext)); mIsHearingAidProfileSupported = BluetoothProperties.isProfileAshaCentralEnabled() .orElse(isAshaEnabledByDefault); } IntentFilter filter = new IntentFilter(); filter.addAction(BluetoothAdapter.ACTION_LOCAL_NAME_CHANGED); filter.addAction(BluetoothAdapter.ACTION_BLUETOOTH_ADDRESS_CHANGED); filter.addAction(Intent.ACTION_SETTING_RESTORED); filter.addAction(Intent.ACTION_SHUTDOWN); filter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY); mContext.registerReceiver(mReceiver, filter, null, mHandler); IntentFilter filterUser = new IntentFilter(); filterUser.addAction(UserManager.ACTION_USER_RESTRICTIONS_CHANGED); filterUser.addAction(Intent.ACTION_USER_SWITCHED); filterUser.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY); mContext.registerReceiverForAllUsers( new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { switch (intent.getAction()) { case Intent.ACTION_USER_SWITCHED: int foregroundUserId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0); propagateForegroundUserId(foregroundUserId); break; case UserManager.ACTION_USER_RESTRICTIONS_CHANGED: onUserRestrictionsChanged(getSendingUser()); break; default: Log.e( TAG, "Unknown broadcast received in BluetoothManagerService" + " receiver registered across all users"); } } }, filterUser, null, mHandler); loadStoredNameAndAddress(); if (isBluetoothPersistedStateOn()) { Log.i(TAG, "Startup: Bluetooth persisted state is ON."); mEnableExternal = true; } { // AutoOn feature initialization of flag guarding final boolean autoOnFlag = Flags.autoOnFeature(); final boolean autoOnProperty = SystemProperties.getBoolean("bluetooth.server.automatic_turn_on", false); Log.d(TAG, "AutoOnFeature status: flag=" + autoOnFlag + ", property=" + autoOnProperty); mDeviceConfigAllowAutoOn = autoOnFlag && autoOnProperty; if (mDeviceConfigAllowAutoOn) { Counter.logIncrement("bluetooth.value_auto_on_supported"); } } } private Unit onBleScanDisabled() { if (mState.oneOf(STATE_OFF, STATE_BLE_TURNING_OFF)) { Log.i(TAG, "onBleScanDisabled: Nothing to do, Bluetooth is already turning off"); return Unit.INSTANCE; } clearBleApps(); try { mAdapter.unregAllGattClient(mContext.getAttributionSource()); } catch (RemoteException e) { Log.e(TAG, "onBleScanDisabled: unregAllGattClient failed", e); } if (mState.oneOf(STATE_BLE_ON)) { Log.i(TAG, "onBleScanDisabled: Shutting down BLE_ON mode"); try { mAdapter.stopBle(mContext.getAttributionSource()); } catch (RemoteException e) { Log.e(TAG, "onBleScanDisabled: stopBle failed", e); } } else { Log.i(TAG, "onBleScanDisabled: Bluetooth is not in BLE_ON, staying on"); } return Unit.INSTANCE; } IBluetoothManager.Stub getBinder() { return mBinder; } /** Returns true if satellite mode is turned on. */ private boolean isSatelliteModeOn() { return SatelliteModeListener.isOn(); } /** Returns true if the Bluetooth saved state is "on" */ private boolean isBluetoothPersistedStateOn() { final int state = BluetoothServerProxy.getInstance() .getBluetoothPersistedState(mContentResolver, BLUETOOTH_ON_BLUETOOTH); Log.d(TAG, "isBluetoothPersistedStateOn: " + state); return state != BLUETOOTH_OFF; } private boolean isBluetoothPersistedStateOnAirplane() { final int state = BluetoothServerProxy.getInstance() .getBluetoothPersistedState(mContentResolver, BLUETOOTH_ON_BLUETOOTH); Log.d(TAG, "isBluetoothPersistedStateOnAirplane: " + state); return state == BLUETOOTH_ON_AIRPLANE; } /** Returns true if the Bluetooth saved state is BLUETOOTH_ON_BLUETOOTH */ private boolean isBluetoothPersistedStateOnBluetooth() { final int state = BluetoothServerProxy.getInstance() .getBluetoothPersistedState(mContentResolver, BLUETOOTH_ON_BLUETOOTH); Log.d(TAG, "isBluetoothPersistedStateOnBluetooth: " + state); return state == BLUETOOTH_ON_BLUETOOTH; } private void setBluetoothPersistedState(int state) { BluetoothServerProxy.getInstance().setBluetoothPersistedState(mContentResolver, state); } /** Returns true if the Bluetooth Adapter's name and address is locally cached */ private boolean isNameAndAddressSet() { return mName != null && mAddress != null && mName.length() > 0 && mAddress.length() > 0; } /** Retrieve the Bluetooth Adapter's name and address and save it in the local cache */ private void loadStoredNameAndAddress() { if (BluetoothProperties.isAdapterAddressValidationEnabled().orElse(false) && Settings.Secure.getInt(mContentResolver, Settings.Secure.BLUETOOTH_ADDR_VALID, 0) == 0) { // if the valid flag is not set, don't load the address and name Log.w(TAG, "There is no valid bluetooth name and address stored"); return; } mName = BluetoothServerProxy.getInstance() .settingsSecureGetString(mContentResolver, Settings.Secure.BLUETOOTH_NAME); mAddress = BluetoothServerProxy.getInstance() .settingsSecureGetString( mContentResolver, Settings.Secure.BLUETOOTH_ADDRESS); Log.d(TAG, "loadStoredNameAndAddress: Name=" + mName + ", Address=" + logAddress(mAddress)); } private String logAddress(String address) { if (address == null) { return "[address is null]"; } if (address.length() != 17) { return "[address invalid]"; } return "XX:XX:XX:XX:" + address.substring(address.length() - 5); } /** * Save the Bluetooth name and address in the persistent store. Only non-null values will be * saved. */ private void storeNameAndAddress(String name, String address) { final String logHeader = "storeNameAndAddress(" + name + ", " + logAddress(address) + "): "; if (name != null) { if (Settings.Secure.putString(mContentResolver, Settings.Secure.BLUETOOTH_NAME, name)) { mName = name; } else { Log.e(TAG, logHeader + "Failed. Name is still " + mName); } } if (address != null) { if (Settings.Secure.putString( mContentResolver, Settings.Secure.BLUETOOTH_ADDRESS, address)) { mAddress = address; } else { Log.e(TAG, logHeader + "Failed. Address is still " + logAddress(mAddress)); } } if ((mName != null) && (mAddress != null)) { Settings.Secure.putInt(mContentResolver, Settings.Secure.BLUETOOTH_ADDR_VALID, 1); } Log.d(TAG, logHeader + "Completed successfully"); } IBluetooth registerAdapter(IBluetoothManagerCallback callback) { synchronized (mCallbacks) { mCallbacks.register(callback); } return mAdapter != null ? mAdapter.getAdapterBinder() : null; } void unregisterAdapter(IBluetoothManagerCallback callback) { synchronized (mCallbacks) { mCallbacks.unregister(callback); } } boolean isEnabled() { return getState() == STATE_ON; } /** * Sends the current foreground user id to the Bluetooth process. This user id is used to * determine if Binder calls are coming from the active user. * * @param userId is the foreground user id we are propagating to the Bluetooth process */ private void propagateForegroundUserId(int userId) { mAdapterLock.readLock().lock(); try { if (mAdapter != null) { mAdapter.setForegroundUserId(userId, mContext.getAttributionSource()); } } catch (RemoteException e) { Log.e(TAG, "Unable to set foreground user id", e); } finally { mAdapterLock.readLock().unlock(); } } int getState() { return mState.get(); } class ClientDeathRecipient implements IBinder.DeathRecipient { private String mPackageName; ClientDeathRecipient(String packageName) { mPackageName = packageName; } public void binderDied() { Log.w(TAG, "Binder is dead - unregister " + mPackageName); for (Map.Entry entry : mBleApps.entrySet()) { IBinder token = entry.getKey(); ClientDeathRecipient deathRec = entry.getValue(); if (deathRec.equals(this)) { updateBleAppCount(token, false, mPackageName); break; } } } public String getPackageName() { return mPackageName; } } boolean isBleScanAvailable() { if (Flags.airplaneModeXBleOn()) { if (AirplaneModeListener.isOn() && !mEnable) { return false; } } else { if (AirplaneModeListener.isOnOverrode() && !mEnable) { return false; } } if (Flags.respectBleScanSetting()) { if (SatelliteModeListener.isOn()) { return false; } return BleScanSettingListener.isScanAllowed(); } try { return Settings.Global.getInt( mContentResolver, Settings.Global.BLE_SCAN_ALWAYS_AVAILABLE) != 0; } catch (SettingNotFoundException e) { // The settings is considered as false by default. return false; } } boolean isHearingAidProfileSupported() { return mIsHearingAidProfileSupported; } Context getCurrentUserContext() { return mCurrentUserContext; } boolean isMediaProfileConnected() { if (mAdapter == null || !mState.oneOf(STATE_ON)) { return false; } return mAdapter.isMediaProfileConnected(mContext.getAttributionSource()); } // Monitor change of BLE scan only mode settings. private void registerForBleScanModeChange() { ContentObserver contentObserver = new ContentObserver(new Handler(mLooper)) { @Override public void onChange(boolean selfChange) { if (isBleScanAvailable()) { // Nothing to do return; } // BLE scan is not available. disableBleScanMode(); clearBleApps(); mAdapterLock.readLock().lock(); try { if (mAdapter != null) { addActiveLog(ENABLE_DISABLE_REASON_APPLICATION_REQUEST, false); mAdapter.stopBle(mContext.getAttributionSource()); } } catch (RemoteException e) { Log.e(TAG, "error when disabling bluetooth", e); } finally { mAdapterLock.readLock().unlock(); } } }; mContentResolver.registerContentObserver( Settings.Global.getUriFor(Settings.Global.BLE_SCAN_ALWAYS_AVAILABLE), false, contentObserver); } // Disable ble scan only mode. private void disableBleScanMode() { mAdapterLock.writeLock().lock(); try { if (mAdapter != null && mState.oneOf(STATE_ON)) { Log.d(TAG, "disableBleScanMode: Resetting the mEnable flag for clean disable"); mEnable = false; } } finally { mAdapterLock.writeLock().unlock(); } } private int updateBleAppCount(IBinder token, boolean enable, String packageName) { String header = "updateBleAppCount(" + token + ", " + enable + ", " + packageName + ")"; ClientDeathRecipient r = mBleApps.get(token); if (r == null && enable) { ClientDeathRecipient deathRec = new ClientDeathRecipient(packageName); try { token.linkToDeath(deathRec, 0); } catch (RemoteException ex) { throw new IllegalArgumentException("BLE app (" + packageName + ") already dead!"); } mBleApps.put(token, deathRec); Log.d(TAG, header + " linkToDeath"); } else if (!enable && r != null) { // Unregister death recipient as the app goes away. token.unlinkToDeath(r, 0); mBleApps.remove(token); Log.d(TAG, header + " unlinkToDeath"); } int appCount = mBleApps.size(); Log.d(TAG, header + " Number of BLE app registered: appCount=" + appCount); return appCount; } boolean enableBle(String packageName, IBinder token) { Log.i( TAG, ("enableBle(" + packageName + ", " + token + "):") + (" mAdapter=" + mAdapter) + (" isBinding=" + isBinding()) + (" mState=" + mState)); if (Flags.airplaneModeXBleOn()) { if (AirplaneModeListener.isOn() && !mEnable) { Log.d(TAG, "enableBle: not enabling - Airplane mode is ON on system"); return false; } } else { if (AirplaneModeListener.isOnOverrode()) { Log.d(TAG, "enableBle: not enabling - Airplane mode is ON"); return false; } } if (isSatelliteModeOn()) { Log.d(TAG, "enableBle: not enabling - Satellite mode is on."); return false; } if (Flags.respectBleScanSetting() && !BleScanSettingListener.isScanAllowed()) { Log.d(TAG, "enableBle: not enabling - Scan mode is not allowed."); return false; } // TODO(b/262605980): enableBle/disableBle should be on handler thread updateBleAppCount(token, true, packageName); if (mState.oneOf( STATE_ON, STATE_BLE_ON, STATE_TURNING_ON, STATE_TURNING_OFF, STATE_BLE_TURNING_ON)) { Log.i(TAG, "enableBle: Bluetooth is already in state" + mState); return true; } synchronized (mReceiver) { // waive WRITE_SECURE_SETTINGS permission check sendEnableMsg(false, ENABLE_DISABLE_REASON_APPLICATION_REQUEST, packageName, true); } return true; } boolean disableBle(String packageName, IBinder token) { Log.i( TAG, ("disableBle(" + packageName + ", " + token + "):") + (" mAdapter=" + mAdapter) + (" isBinding=" + isBinding()) + (" mState=" + mState)); // Remove this with flag, preventing a "disable" make no sense, even in satellite mode if (!Flags.respectBleScanSetting() && isSatelliteModeOn()) { Log.d(TAG, "disableBle: not disabling - satellite mode is on."); return false; } if (mState.oneOf(STATE_OFF)) { Log.i(TAG, "disableBle: Already disabled"); return false; } // TODO(b/262605980): enableBle/disableBle should be on handler thread updateBleAppCount(token, false, packageName); if (mState.oneOf(STATE_BLE_ON) && !isBleAppPresent()) { if (mEnable) { disableBleScanMode(); } if (!mEnableExternal) { addActiveLog(ENABLE_DISABLE_REASON_APPLICATION_REQUEST, packageName, false, true); sendBrEdrDownCallback(); } } return true; } // Clear all apps using BLE scan only mode. private void clearBleApps() { mBleApps.clear(); } private boolean isBleAppPresent() { Log.d(TAG, "isBleAppPresent(): Number of BLE app registered: " + mBleApps.size()); return mBleApps.size() > 0; } /** * Will call startBrEdr() if bluetooth classic should be on and will call stopBle if bluetooth * BLE should be off */ private void continueFromBleOnState() { mAdapterLock.readLock().lock(); try { if (mAdapter == null) { Log.e(TAG, "continueFromBleOnState: Adapter is null"); return; } if (!mEnableExternal && !isBleAppPresent()) { // TODO(b/262605980): this code is unlikely to be trigger and will never be once // enableBle & disableBle are executed on the handler Log.i(TAG, "continueFromBleOnState: Disabled while enabling BLE, disable BLE now"); mEnable = false; mAdapter.stopBle(mContext.getAttributionSource()); return; } if (isBluetoothPersistedStateOnBluetooth() || !isBleAppPresent()) { Log.i(TAG, "continueFromBleOnState: Starting br edr"); // This triggers transition to STATE_ON mAdapter.startBrEdr(mContext.getAttributionSource()); setBluetoothPersistedState(BLUETOOTH_ON_BLUETOOTH); } else { Log.i(TAG, "continueFromBleOnState: Staying in BLE_ON"); } } catch (RemoteException e) { Log.e(TAG, "Unable to call onServiceUp", e); } finally { mAdapterLock.readLock().unlock(); } } /** * Inform BluetoothAdapter instances that BREDR part is down and turn off all service and stack * if no LE app needs it */ private void sendBrEdrDownCallback() { mAdapterLock.readLock().lock(); try { if (mAdapter == null) { Log.w(TAG, "sendBrEdrDownCallback: mAdapter is null"); return; } boolean airplaneDoesNotAllowBleOn = Flags.airplaneModeXBleOn() && AirplaneModeListener.isOn(); boolean scanIsAllowed = !Flags.respectBleScanSetting() || BleScanSettingListener.isScanAllowed(); if (!airplaneDoesNotAllowBleOn && isBleAppPresent() && scanIsAllowed) { // Need to stay at BLE ON. Disconnect all Gatt connections Log.i(TAG, "sendBrEdrDownCallback: Staying in BLE_ON"); mAdapter.unregAllGattClient(mContext.getAttributionSource()); } else { Log.i(TAG, "sendBrEdrDownCallback: Stopping ble"); mAdapter.stopBle(mContext.getAttributionSource()); } } catch (RemoteException e) { Log.e(TAG, "sendBrEdrDownCallback: Call to mAdapter failed.", e); } finally { mAdapterLock.readLock().unlock(); } } private Unit enableFromAutoOn() { if (isBluetoothDisallowed()) { Log.d(TAG, "Bluetooth is not allowed, preventing AutoOn"); return Unit.INSTANCE; } Counter.logIncrement("bluetooth.value_auto_on_triggered"); sendToggleNotification("auto_on_bt_enabled_notification"); enable("BluetoothSystemServer/AutoOn"); return Unit.INSTANCE; } boolean enableNoAutoConnect(String packageName) { if (isSatelliteModeOn()) { Log.d(TAG, "enableNoAutoConnect(" + packageName + "): Blocked by satellite mode"); return false; } synchronized (mReceiver) { mQuietEnableExternal = true; mEnableExternal = true; sendEnableMsg(true, ENABLE_DISABLE_REASON_APPLICATION_REQUEST, packageName); } return true; } boolean enable(String packageName) { Log.d( TAG, ("enable(" + packageName + "):") + (" mAdapter=" + mAdapter) + (" isBinding=" + isBinding()) + (" mState=" + mState)); if (isSatelliteModeOn()) { Log.d(TAG, "enable: not enabling - satellite mode is on."); return false; } synchronized (mReceiver) { mQuietEnableExternal = false; mEnableExternal = true; // TODO(b/288450479): Remove clearCallingIdentity when threading is fixed final long callingIdentity = Binder.clearCallingIdentity(); try { AirplaneModeListener.notifyUserToggledBluetooth( mContentResolver, mCurrentUserContext, true); } finally { Binder.restoreCallingIdentity(callingIdentity); } sendEnableMsg(false, ENABLE_DISABLE_REASON_APPLICATION_REQUEST, packageName); } return true; } boolean disable(String packageName, boolean persist) { Log.d( TAG, ("disable(" + packageName + ", " + persist + "):") + (" mAdapter=" + mAdapter) + (" isBinding=" + isBinding()) + (" mState=" + mState)); synchronized (mReceiver) { // TODO(b/288450479): Remove clearCallingIdentity when threading is fixed final long callingIdentity = Binder.clearCallingIdentity(); try { AirplaneModeListener.notifyUserToggledBluetooth( mContentResolver, mCurrentUserContext, false); } finally { Binder.restoreCallingIdentity(callingIdentity); } if (persist) { setBluetoothPersistedState(BLUETOOTH_OFF); } mEnableExternal = false; sendDisableMsg(ENABLE_DISABLE_REASON_APPLICATION_REQUEST, packageName); } return true; } void unbindAndFinish() { Log.d(TAG, "unbindAndFinish(): mAdapter=" + mAdapter + " isBinding=" + isBinding()); mAdapterLock.writeLock().lock(); try { mHandler.removeMessages(MESSAGE_BLUETOOTH_STATE_CHANGE); if (mAdapter == null) { return; } try { mAdapter.unregisterCallback(mBluetoothCallback, mContext.getAttributionSource()); } catch (RemoteException e) { Log.e(TAG, "Unable to unregister BluetoothCallback", e); } if (!Flags.explicitKillFromSystemServer()) { mAdapter = null; mContext.unbindService(mConnection); mHandler.removeMessages(MESSAGE_TIMEOUT_BIND); return; } CompletableFuture binderDead = new CompletableFuture<>(); try { mAdapter.getAdapterBinder() .asBinder() .linkToDeath( () -> { Log.i(TAG, "Successfully received Bluetooth death"); binderDead.complete(null); }, 0); } catch (RemoteException e) { Log.e(TAG, "Failed to linkToDeath", e); binderDead.complete(null); } // Unbind first to avoid receiving unwanted "onServiceDisconnected" mContext.unbindService(mConnection); try { // Force kill Bluetooth to make sure its process is not reused. // Note: In a perfect world, we should be able to re-init the same process. // Unfortunately, this requires an heavy rework of the Bluetooth app // TODO: b/339501753 - Properly stop Bluetooth without killing it mAdapter.killBluetoothProcess(); binderDead.get(1, TimeUnit.SECONDS); } catch (android.os.DeadObjectException e) { // Reduce exception to info and skip waiting (Bluetooth is dead as wanted) Log.i(TAG, "Bluetooth already dead 💀"); } catch (RemoteException e) { Log.e(TAG, "Unexpected RemoteException when calling killBluetoothProcess", e); } catch (TimeoutException | InterruptedException | ExecutionException e) { Log.e(TAG, "Bluetooth death not received correctly", e); } mAdapter = null; mHandler.removeMessages(MESSAGE_TIMEOUT_BIND); } finally { mAdapterLock.writeLock().unlock(); } } /** * Send enable message and set adapter name and address. Called when the boot phase becomes * PHASE_SYSTEM_SERVICES_READY. */ void handleOnBootPhase(UserHandle userHandle) { mHandler.post(() -> internalHandleOnBootPhase(userHandle)); } @VisibleForTesting void initialize(UserHandle userHandle) { mCurrentUserContext = requireNonNull( mContext.createContextAsUser(userHandle, 0), "Current User Context cannot be null"); AirplaneModeListener.initialize( mLooper, mContentResolver, mState, this::onAirplaneModeChanged, this::sendToggleNotification, this::isMediaProfileConnected, this::getCurrentUserContext, TimeSource.Monotonic.INSTANCE); SatelliteModeListener.initialize(mLooper, mContentResolver, this::onSatelliteModeChanged); } private void internalHandleOnBootPhase(UserHandle userHandle) { Log.d(TAG, "internalHandleOnBootPhase(" + userHandle + "): Bluetooth boot completed"); initialize(userHandle); final boolean isBluetoothDisallowed = isBluetoothDisallowed(); if (isBluetoothDisallowed) { return; } final boolean isSafeMode = mContext.getPackageManager().isSafeMode(); if (mEnableExternal && isBluetoothPersistedStateOnBluetooth() && !isSafeMode) { Log.i(TAG, "internalHandleOnBootPhase: Auto-enabling Bluetooth."); sendEnableMsg(mQuietEnableExternal, ENABLE_DISABLE_REASON_SYSTEM_BOOT); } else if (!isNameAndAddressSet()) { Log.i(TAG, "internalHandleOnBootPhase: Getting adapter name and address"); mHandler.sendEmptyMessage(MESSAGE_GET_NAME_AND_ADDRESS); } else { autoOnSetupTimer(); } } /** Called when switching to a different foreground user. */ private void handleSwitchUser(UserHandle userHandle) { Log.d(TAG, "handleSwitchUser(" + userHandle + ")"); mHandler.obtainMessage(MESSAGE_USER_SWITCHED, userHandle).sendToTarget(); } /** Called when user is unlocked. */ void handleOnUnlockUser(UserHandle userHandle) { Log.d(TAG, "handleOnUnlockUser(" + userHandle + ")"); mHandler.obtainMessage(MESSAGE_USER_UNLOCKED, userHandle).sendToTarget(); } private void sendBluetoothOnCallback() { synchronized (mCallbacks) { try { int n = mCallbacks.beginBroadcast(); Log.d(TAG, "Broadcasting onBluetoothOn() to " + n + " receivers."); for (int i = 0; i < n; i++) { try { mCallbacks.getBroadcastItem(i).onBluetoothOn(); } catch (RemoteException e) { Log.e(TAG, "Unable to call onBluetoothOn() on callback #" + i, e); } } } finally { mCallbacks.finishBroadcast(); } } } private void sendBluetoothOffCallback() { synchronized (mCallbacks) { try { int n = mCallbacks.beginBroadcast(); Log.d(TAG, "Broadcasting onBluetoothOff() to " + n + " receivers."); for (int i = 0; i < n; i++) { try { mCallbacks.getBroadcastItem(i).onBluetoothOff(); } catch (RemoteException e) { Log.e(TAG, "Unable to call onBluetoothOff() on callback #" + i, e); } } } finally { mCallbacks.finishBroadcast(); } } } /** Inform BluetoothAdapter instances that Adapter service is up */ private void sendBluetoothServiceUpCallback() { synchronized (mCallbacks) { mAdapterLock.readLock().lock(); try { int n = mCallbacks.beginBroadcast(); Log.d(TAG, "sendBluetoothServiceUpCallback(): to " + n + " receivers"); for (int i = 0; i < n; i++) { try { mCallbacks .getBroadcastItem(i) .onBluetoothServiceUp(mAdapter.getAdapterBinder().asBinder()); } catch (RemoteException e) { Log.e(TAG, "Unable to call onBluetoothServiceUp() on callback #" + i, e); } } } finally { mCallbacks.finishBroadcast(); mAdapterLock.readLock().unlock(); } } } /** Inform BluetoothAdapter instances that Adapter service is down */ private void sendBluetoothServiceDownCallback() { synchronized (mCallbacks) { try { int n = mCallbacks.beginBroadcast(); Log.d(TAG, "sendBluetoothServiceDownCallback(): to " + n + " receivers"); for (int i = 0; i < n; i++) { try { mCallbacks.getBroadcastItem(i).onBluetoothServiceDown(); } catch (RemoteException e) { Log.e(TAG, "Unable to call onBluetoothServiceDown() on callback #" + i, e); } } } finally { mCallbacks.finishBroadcast(); } } } String getAddress() { mAdapterLock.readLock().lock(); try { if (mAdapter != null) { return mAdapter.getAddress(mContext.getAttributionSource()); } } catch (RemoteException e) { Log.e( TAG, "getAddress(): Unable to retrieve address remotely. Returning cached address", e); } finally { mAdapterLock.readLock().unlock(); } // mAddress is accessed from outside. // It is alright without a lock. Here, bluetooth is off, no other thread is // changing mAddress return mAddress; } String getName() { mAdapterLock.readLock().lock(); try { if (mAdapter != null) { return mAdapter.getName(mContext.getAttributionSource()); } } catch (RemoteException e) { Log.e(TAG, "getName(): Unable to retrieve name remotely. Returning cached name", e); } finally { mAdapterLock.readLock().unlock(); } // mName is accessed from outside. // It alright without a lock. Here, bluetooth is off, no other thread is // changing mName return mName; } @VisibleForTesting class BluetoothServiceConnection implements ServiceConnection { public void onServiceConnected(ComponentName componentName, IBinder service) { String name = componentName.getClassName(); Log.d(TAG, "ServiceConnection.onServiceConnected(" + name + ", " + service + ")"); if (!name.equals("com.android.bluetooth.btservice.AdapterService")) { Log.e(TAG, "Unknown service connected: " + name); return; } mHandler.obtainMessage(MESSAGE_BLUETOOTH_SERVICE_CONNECTED, service).sendToTarget(); } public void onServiceDisconnected(ComponentName componentName) { // Called if we unexpectedly disconnect. String name = componentName.getClassName(); Log.d(TAG, "ServiceConnection.onServiceDisconnected(" + name + ")"); if (!name.equals("com.android.bluetooth.btservice.AdapterService")) { Log.e(TAG, "Unknown service disconnected: " + name); return; } mHandler.sendEmptyMessage(MESSAGE_BLUETOOTH_SERVICE_DISCONNECTED); } } private BluetoothServiceConnection mConnection = new BluetoothServiceConnection(); private int mWaitForEnableRetry; private int mWaitForDisableRetry; @VisibleForTesting class BluetoothHandler extends Handler { boolean mGetNameAddressOnly = false; BluetoothHandler(Looper looper) { super(looper); } @Override public void handleMessage(Message msg) { switch (msg.what) { case MESSAGE_GET_NAME_AND_ADDRESS: Log.d(TAG, "MESSAGE_GET_NAME_AND_ADDRESS"); mAdapterLock.writeLock().lock(); try { if (mAdapter == null && !isBinding()) { Log.d(TAG, "Binding to service to get name and address"); mGetNameAddressOnly = true; mHandler.sendEmptyMessageDelayed(MESSAGE_TIMEOUT_BIND, TIMEOUT_BIND_MS); Intent i = new Intent(IBluetooth.class.getName()); if (!doBind( i, mConnection, Context.BIND_AUTO_CREATE | Context.BIND_IMPORTANT, UserHandle.CURRENT)) { mHandler.removeMessages(MESSAGE_TIMEOUT_BIND); } } else if (mAdapter != null) { try { storeNameAndAddress( mAdapter.getName(mContext.getAttributionSource()), mAdapter.getAddress(mContext.getAttributionSource())); } catch (RemoteException e) { Log.e(TAG, "Unable to grab names", e); } if (mGetNameAddressOnly && !mEnable) { unbindAndFinish(); } mGetNameAddressOnly = false; } } finally { mAdapterLock.writeLock().unlock(); } break; case MESSAGE_ENABLE: int quietEnable = msg.arg1; int isBle = msg.arg2; Log.d( TAG, ("MESSAGE_ENABLE(quietEnable=" + quietEnable + ", isBle=" + isBle + ")") + (": mAdapter=" + mAdapter)); handleEnableMessage(quietEnable, isBle); break; case MESSAGE_DISABLE: Log.d(TAG, "MESSAGE_DISABLE: mAdapter=" + mAdapter); handleDisableMessage(); break; case MESSAGE_HANDLE_ENABLE_DELAYED: // The Bluetooth is turning off, wait for STATE_OFF if (!mState.oneOf(STATE_OFF)) { if (mWaitForEnableRetry < MAX_WAIT_FOR_ENABLE_DISABLE_RETRIES) { mWaitForEnableRetry++; mHandler.sendEmptyMessageDelayed( MESSAGE_HANDLE_ENABLE_DELAYED, ENABLE_DISABLE_DELAY_MS); break; } else { Log.e(TAG, "Wait for STATE_OFF timeout"); } } // Either state is changed to STATE_OFF or reaches the maximum retry, we // should move forward to the next step. mWaitForEnableRetry = 0; mHandler.sendEmptyMessageDelayed( MESSAGE_RESTART_BLUETOOTH_SERVICE, getServiceRestartMs()); Log.d(TAG, "Handle enable is finished"); break; case MESSAGE_HANDLE_DISABLE_DELAYED: boolean disabling = (msg.arg1 == 1); Log.d(TAG, "MESSAGE_HANDLE_DISABLE_DELAYED: disabling:" + disabling); if (!disabling) { // The Bluetooth is turning on, wait for STATE_ON if (!mState.oneOf(STATE_ON)) { if (mWaitForDisableRetry < MAX_WAIT_FOR_ENABLE_DISABLE_RETRIES) { mWaitForDisableRetry++; mHandler.sendEmptyMessageDelayed( MESSAGE_HANDLE_DISABLE_DELAYED, ENABLE_DISABLE_DELAY_MS); break; } else { Log.e(TAG, "Wait for STATE_ON timeout"); } } // Either state is changed to STATE_ON or reaches the maximum retry, we // should move forward to the next step. mWaitForDisableRetry = 0; mEnable = false; handleDisable(); // Wait for state exiting STATE_ON Message disableDelayedMsg = mHandler.obtainMessage(MESSAGE_HANDLE_DISABLE_DELAYED, 1, 0); mHandler.sendMessageDelayed(disableDelayedMsg, ENABLE_DISABLE_DELAY_MS); } else { // The Bluetooth is turning off, wait for exiting STATE_ON if (mState.oneOf(STATE_ON)) { if (mWaitForDisableRetry < MAX_WAIT_FOR_ENABLE_DISABLE_RETRIES) { mWaitForDisableRetry++; Message disableDelayedMsg = mHandler.obtainMessage( MESSAGE_HANDLE_DISABLE_DELAYED, 1, 0); mHandler.sendMessageDelayed( disableDelayedMsg, ENABLE_DISABLE_DELAY_MS); break; } else { Log.e(TAG, "Wait for exiting STATE_ON timeout"); } } // Either state is exited from STATE_ON or reaches the maximum retry, we // should move forward to the next step. Log.d(TAG, "Handle disable is finished"); } break; case MESSAGE_RESTORE_USER_SETTING: if ((msg.arg1 == RESTORE_SETTING_TO_OFF) && mEnable) { Log.d(TAG, "MESSAGE_RESTORE_USER_SETTING: set Bluetooth state to disabled"); setBluetoothPersistedState(BLUETOOTH_OFF); mEnableExternal = false; sendDisableMsg(ENABLE_DISABLE_REASON_RESTORE_USER_SETTING); } else if ((msg.arg1 == RESTORE_SETTING_TO_ON) && !mEnable) { Log.d(TAG, "MESSAGE_RESTORE_USER_SETTING: set Bluetooth state to enabled"); mQuietEnableExternal = false; mEnableExternal = true; sendEnableMsg(false, ENABLE_DISABLE_REASON_RESTORE_USER_SETTING); } else { Log.w( TAG, "MESSAGE_RESTORE_USER_SETTING: Unhandled." + (" mEnable=" + mEnable) + (" msg.arg1=" + msg.arg1)); } break; case MESSAGE_BLUETOOTH_SERVICE_CONNECTED: IBinder service = (IBinder) msg.obj; Log.d(TAG, "MESSAGE_BLUETOOTH_SERVICE_CONNECTED: service=" + service); mAdapterLock.writeLock().lock(); try { // Remove timeout mHandler.removeMessages(MESSAGE_TIMEOUT_BIND); mAdapter = BluetoothServerProxy.getInstance().createAdapterBinder(service); int foregroundUserId = ActivityManager.getCurrentUser(); propagateForegroundUserId(foregroundUserId); if (!isNameAndAddressSet()) { mHandler.sendEmptyMessage(MESSAGE_GET_NAME_AND_ADDRESS); if (mGetNameAddressOnly) { return; } } // Register callback object try { mAdapter.registerCallback( mBluetoothCallback, mContext.getAttributionSource()); } catch (RemoteException e) { Log.e(TAG, "Unable to register BluetoothCallback", e); } // Inform BluetoothAdapter instances that service is up if (!Flags.fastBindToApp()) { sendBluetoothServiceUpCallback(); } // Do enable request try { mAdapter.enable(mQuietEnable, mContext.getAttributionSource()); } catch (RemoteException e) { Log.e(TAG, "Unable to call enable()", e); } if (Flags.fastBindToApp()) { sendBluetoothServiceUpCallback(); } } finally { mAdapterLock.writeLock().unlock(); } if (!mEnable) { waitForState(STATE_ON); handleDisable(); waitForState( STATE_OFF, STATE_TURNING_ON, STATE_TURNING_OFF, STATE_BLE_TURNING_ON, STATE_BLE_ON, STATE_BLE_TURNING_OFF); } break; case MESSAGE_BLUETOOTH_STATE_CHANGE: int prevState = msg.arg1; int newState = msg.arg2; Log.d( TAG, "MESSAGE_BLUETOOTH_STATE_CHANGE:" + (" prevState=" + BluetoothAdapter.nameForState(prevState)) + (" newState=" + BluetoothAdapter.nameForState(newState))); mState.set(newState); bluetoothStateChangeHandler(prevState, newState); // handle error state transition case from TURNING_ON to OFF // unbind and rebind bluetooth service and enable bluetooth if ((prevState == STATE_BLE_TURNING_ON) && (newState == STATE_OFF) && (mAdapter != null) && mEnable) { recoverBluetoothServiceFromError(false); } if ((prevState == STATE_TURNING_ON) && (newState == STATE_BLE_ON) && (mAdapter != null) && mEnable) { recoverBluetoothServiceFromError(true); } // If we tried to enable BT while BT was in the process of shutting down, // wait for the BT process to fully tear down and then force a restart // here. This is a bit of a hack (b/29363429). if (prevState == STATE_BLE_TURNING_OFF && newState == STATE_OFF) { if (mEnable) { Log.d(TAG, "Entering STATE_OFF but mEnabled is true; restarting."); waitForState(STATE_OFF); mHandler.sendEmptyMessageDelayed( MESSAGE_RESTART_BLUETOOTH_SERVICE, getServiceRestartMs()); } } if (newState == STATE_ON || newState == STATE_BLE_ON) { // bluetooth is working, reset the counter if (mErrorRecoveryRetryCounter != 0) { Log.w(TAG, "bluetooth is recovered from error"); mErrorRecoveryRetryCounter = 0; } } break; case MESSAGE_BLUETOOTH_SERVICE_DISCONNECTED: Log.e(TAG, "MESSAGE_BLUETOOTH_SERVICE_DISCONNECTED"); mAdapterLock.writeLock().lock(); try { // if service is unbinded already, do nothing and return if (mAdapter == null) { break; } mContext.unbindService(mConnection); mAdapter = null; } finally { mAdapterLock.writeLock().unlock(); } // log the unexpected crash addCrashLog(); addActiveLog(ENABLE_DISABLE_REASON_CRASH, false); if (mEnable) { mEnable = false; mHandler.sendEmptyMessageDelayed( MESSAGE_RESTART_BLUETOOTH_SERVICE, getServiceRestartMs()); } sendBluetoothServiceDownCallback(); // Send BT state broadcast to update // the BT icon correctly if (mState.oneOf(STATE_TURNING_ON, STATE_ON)) { bluetoothStateChangeHandler(STATE_ON, STATE_TURNING_OFF); mState.set(STATE_TURNING_OFF); } if (mState.oneOf(STATE_TURNING_OFF)) { bluetoothStateChangeHandler(STATE_TURNING_OFF, STATE_OFF); } mHandler.removeMessages(MESSAGE_BLUETOOTH_STATE_CHANGE); mState.set(STATE_OFF); break; case MESSAGE_RESTART_BLUETOOTH_SERVICE: mErrorRecoveryRetryCounter++; Log.d( TAG, "MESSAGE_RESTART_BLUETOOTH_SERVICE: retry count=" + mErrorRecoveryRetryCounter); if (mErrorRecoveryRetryCounter < MAX_ERROR_RESTART_RETRIES) { /* Enable without persisting the setting as * it doesn't change when IBluetooth * service restarts */ mEnable = true; addActiveLog(ENABLE_DISABLE_REASON_RESTARTED, true); handleEnable(mQuietEnable); } else { mAdapterLock.writeLock().lock(); mAdapter = null; mAdapterLock.writeLock().unlock(); Log.e(TAG, "Reach maximum retry to restart Bluetooth!"); } break; case MESSAGE_TIMEOUT_BIND: Log.e(TAG, "MESSAGE_TIMEOUT_BIND"); // TODO(b/286082382): Timeout should be more than a log. We should at least call // context.unbindService, eventually log a metric with it break; case MESSAGE_USER_SWITCHED: UserHandle userTo = (UserHandle) msg.obj; Log.d(TAG, "MESSAGE_USER_SWITCHED: userTo=" + userTo); mHandler.removeMessages(MESSAGE_USER_SWITCHED); AutoOnFeature.pause(); mCurrentUserContext = mContext.createContextAsUser(userTo, 0); /* disable and enable BT when detect a user switch */ if (mAdapter != null && mState.oneOf(STATE_ON)) { restartForNewUser(userTo); } else if (isBinding() || mAdapter != null) { Message userMsg = Message.obtain(msg); userMsg.arg1++; // if user is switched when service is binding retry after a delay mHandler.sendMessageDelayed(userMsg, USER_SWITCHED_TIME_MS); Log.d( TAG, "MESSAGE_USER_SWITCHED:" + (" userTo=" + userTo) + (" number of retry attempt=" + userMsg.arg1) + (" isBinding=" + isBinding()) + (" mAdapter=" + mAdapter)); } else { autoOnSetupTimer(); } break; case MESSAGE_USER_UNLOCKED: Log.d(TAG, "MESSAGE_USER_UNLOCKED"); mHandler.removeMessages(MESSAGE_USER_SWITCHED); if (mEnable && !isBinding() && (mAdapter == null)) { // We should be connected, but we gave up for some // reason; maybe the Bluetooth service wasn't encryption // aware, so try binding again. Log.d(TAG, "Enabled but not bound; retrying after unlock"); handleEnable(mQuietEnable); } break; } } private void restartForNewUser(UserHandle unusedNewUser) { mAdapterLock.readLock().lock(); try { if (mAdapter != null) { mAdapter.unregisterCallback( mBluetoothCallback, mContext.getAttributionSource()); } } catch (RemoteException e) { Log.e(TAG, "Unable to unregister", e); } finally { mAdapterLock.readLock().unlock(); } // This method is always called while bluetooth is in STATE_ON assert (mState.oneOf(STATE_ON)); // disable addActiveLog(ENABLE_DISABLE_REASON_USER_SWITCH, false); handleDisable(); // Pbap service need receive STATE_TURNING_OFF intent to close bluetoothStateChangeHandler(STATE_ON, STATE_TURNING_OFF); boolean didDisableTimeout = !waitForState(STATE_OFF); bluetoothStateChangeHandler(STATE_TURNING_OFF, STATE_OFF); // // If disabling Bluetooth times out, wait for an // additional amount of time to ensure the process is // shut down completely before attempting to restart. // if (didDisableTimeout) { SystemClock.sleep(3000); } else { SystemClock.sleep(100); } mHandler.removeMessages(MESSAGE_BLUETOOTH_STATE_CHANGE); mState.set(STATE_OFF); // enable addActiveLog(ENABLE_DISABLE_REASON_USER_SWITCH, true); // mEnable flag could have been reset on stopBle. Reenable it. mEnable = true; handleEnable(mQuietEnable); } } private boolean isBinding() { return mHandler.hasMessages(MESSAGE_TIMEOUT_BIND); } private void handleEnableMessage(int quietEnable, int isBle) { if (mShutdownInProgress) { Log.d(TAG, "Skip Bluetooth Enable in device shutdown process"); return; } if (mHandler.hasMessages(MESSAGE_HANDLE_DISABLE_DELAYED) || mHandler.hasMessages(MESSAGE_HANDLE_ENABLE_DELAYED)) { // We are handling enable or disable right now, wait for it. mHandler.sendMessageDelayed( mHandler.obtainMessage(MESSAGE_ENABLE, quietEnable, isBle), ENABLE_DISABLE_DELAY_MS); return; } mHandler.removeMessages(MESSAGE_RESTART_BLUETOOTH_SERVICE); mEnable = true; if (isBle == 0) { setBluetoothPersistedState(BLUETOOTH_ON_BLUETOOTH); } // Use service interface to get the exact state mAdapterLock.readLock().lock(); try { if (mAdapter != null) { boolean isHandled = true; switch (mState.get()) { case STATE_BLE_ON: if (isBle == 1) { Log.i(TAG, "Already at BLE_ON State"); } else { Log.w(TAG, "BT Enable in BLE_ON State, going to ON"); mAdapter.startBrEdr(mContext.getAttributionSource()); } break; case STATE_BLE_TURNING_ON: case STATE_TURNING_ON: case STATE_ON: Log.i(TAG, "MESSAGE_ENABLE: already enabled"); break; default: isHandled = false; break; } if (isHandled) return; } } catch (RemoteException e) { Log.e(TAG, "", e); } finally { mAdapterLock.readLock().unlock(); } mQuietEnable = (quietEnable == 1); if (mAdapter == null) { handleEnable(mQuietEnable); } else { // // We need to wait until transitioned to STATE_OFF and // the previous Bluetooth process has exited. The // waiting period has three components: // (a) Wait until the local state is STATE_OFF. This // is accomplished by sending delay a message // MESSAGE_HANDLE_ENABLE_DELAYED // (b) Wait until the STATE_OFF state is updated to // all components. // (c) Wait until the Bluetooth process exits, and // ActivityManager detects it. // The waiting for (b) and (c) is accomplished by // delaying the MESSAGE_RESTART_BLUETOOTH_SERVICE // message. The delay time is backed off if Bluetooth // continuously failed to turn on itself. // mWaitForEnableRetry = 0; mHandler.sendEmptyMessageDelayed( MESSAGE_HANDLE_ENABLE_DELAYED, ENABLE_DISABLE_DELAY_MS); } } private void handleDisableMessage() { if (mHandler.hasMessages(MESSAGE_HANDLE_DISABLE_DELAYED) || isBinding() || mHandler.hasMessages(MESSAGE_HANDLE_ENABLE_DELAYED)) { // We are handling enable or disable right now, wait for it. mHandler.sendEmptyMessageDelayed(MESSAGE_DISABLE, ENABLE_DISABLE_DELAY_MS); return; } mHandler.removeMessages(MESSAGE_RESTART_BLUETOOTH_SERVICE); if (mEnable && mAdapter != null) { mWaitForDisableRetry = 0; mHandler.sendEmptyMessageDelayed( MESSAGE_HANDLE_DISABLE_DELAYED, ENABLE_DISABLE_DELAY_MS); } else { mEnable = false; handleDisable(); } } private void handleEnable(boolean quietMode) { mQuietEnable = quietMode; mAdapterLock.writeLock().lock(); try { if (mAdapter == null && !isBinding()) { Log.d(TAG, "binding Bluetooth service"); // Start bind timeout and bind mHandler.sendEmptyMessageDelayed(MESSAGE_TIMEOUT_BIND, TIMEOUT_BIND_MS); Intent i = new Intent(IBluetooth.class.getName()); if (!doBind( i, mConnection, Context.BIND_AUTO_CREATE | Context.BIND_IMPORTANT, UserHandle.CURRENT)) { mHandler.removeMessages(MESSAGE_TIMEOUT_BIND); } } else if (!Flags.fastBindToApp() && mAdapter != null) { // Enable bluetooth try { mAdapter.enable(mQuietEnable, mContext.getAttributionSource()); } catch (RemoteException e) { Log.e(TAG, "Unable to call enable()", e); } } } finally { mAdapterLock.writeLock().unlock(); } } boolean doBind(Intent intent, ServiceConnection conn, int flags, UserHandle user) { ComponentName comp = resolveSystemService(intent); intent.setComponent(comp); if (comp == null || !mContext.bindServiceAsUser(intent, conn, flags, user)) { Log.e(TAG, "Fail to bind to: " + intent); return false; } return true; } private void handleDisable() { mAdapterLock.readLock().lock(); try { if (mAdapter != null) { Log.d(TAG, "handleDisable: Sending off request."); mAdapter.disable(mContext.getAttributionSource()); } } catch (RemoteException e) { Log.e(TAG, "Unable to call disable()", e); } finally { mAdapterLock.readLock().unlock(); } } private void broadcastIntentStateChange(String action, int prevState, int newState) { Log.d( TAG, "broadcastIntentStateChange:" + (" action=" + action.substring(action.lastIndexOf('.') + 1)) + (" prevState=" + BluetoothAdapter.nameForState(prevState)) + (" newState=" + BluetoothAdapter.nameForState(newState))); // Send broadcast message to everyone else Intent intent = new Intent(action); intent.putExtra(BluetoothAdapter.EXTRA_PREVIOUS_STATE, prevState); intent.putExtra(BluetoothAdapter.EXTRA_STATE, newState); intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); mContext.sendBroadcastAsUser( intent, UserHandle.ALL, null, getTempAllowlistBroadcastOptions()); } private boolean isBleState(int state) { switch (state) { case STATE_BLE_ON: case STATE_BLE_TURNING_ON: case STATE_BLE_TURNING_OFF: return true; } return false; } private void bluetoothStateChangeHandler(int prevState, int newState) { if (prevState == newState) { // No change. Nothing to do. return; } if (prevState == STATE_ON) { autoOnSetupTimer(); } // Notify all proxy objects first of adapter state change if (newState == STATE_ON) { if (isAtLeastV() && mDeviceConfigAllowAutoOn) { AutoOnFeature.notifyBluetoothOn(mCurrentUserContext); } sendBluetoothOnCallback(); } else if (newState == STATE_OFF) { // If Bluetooth is off, send service down event to proxy objects, and unbind Log.d(TAG, "bluetoothStateChangeHandler: Bluetooth is OFF send Service Down"); sendBluetoothServiceDownCallback(); unbindAndFinish(); } else if (newState == STATE_BLE_ON && prevState == STATE_BLE_TURNING_ON) { continueFromBleOnState(); } // Nothing specific to do for STATE_TURNING_ broadcastIntentStateChange(BluetoothAdapter.ACTION_BLE_STATE_CHANGED, prevState, newState); // BLE state are shown as STATE_OFF for BrEdr users final int prevBrEdrState = isBleState(prevState) ? STATE_OFF : prevState; final int newBrEdrState = isBleState(newState) ? STATE_OFF : newState; if (prevBrEdrState != newBrEdrState) { // Only broadcast when there is a BrEdr state change. if (newBrEdrState == STATE_OFF) { sendBluetoothOffCallback(); sendBrEdrDownCallback(); } broadcastIntentStateChange( BluetoothAdapter.ACTION_STATE_CHANGED, prevBrEdrState, newBrEdrState); } } boolean waitForManagerState(int state) { return mState.waitForState(STATE_TIMEOUT, state); } private boolean waitForState(int... states) { return mState.waitForState(STATE_TIMEOUT, states); } private void sendDisableMsg(int reason) { sendDisableMsg(reason, mContext.getPackageName()); } private void sendDisableMsg(int reason, String packageName) { mHandler.sendEmptyMessage(MESSAGE_DISABLE); addActiveLog(reason, packageName, false, false); } private void sendEnableMsg(boolean quietMode, int reason) { sendEnableMsg(quietMode, reason, mContext.getPackageName()); } private void sendEnableMsg(boolean quietMode, int reason, String packageName) { sendEnableMsg(quietMode, reason, packageName, false); } private void sendEnableMsg(boolean quietMode, int reason, String packageName, boolean isBle) { mHandler.obtainMessage(MESSAGE_ENABLE, quietMode ? 1 : 0, isBle ? 1 : 0).sendToTarget(); addActiveLog(reason, packageName, true, isBle); mLastEnabledTime = SystemClock.elapsedRealtime(); } private void addActiveLog(int reason, boolean enable) { addActiveLog(reason, mContext.getPackageName(), enable, false); } private void addActiveLog(int reason, String packageName, boolean enable, boolean isBle) { ActiveLog lastActiveLog = mActiveLogs.peekLast(); synchronized (mActiveLogs) { if (mActiveLogs.size() > ACTIVE_LOG_MAX_SIZE) { mActiveLogs.remove(); } mActiveLogs.add( new ActiveLog(reason, packageName, enable, isBle, System.currentTimeMillis())); int state = enable ? BluetoothStatsLog.BLUETOOTH_ENABLED_STATE_CHANGED__STATE__ENABLED : BluetoothStatsLog.BLUETOOTH_ENABLED_STATE_CHANGED__STATE__DISABLED; int lastState; long timeSinceLastChanged; if (lastActiveLog == null) { lastState = BluetoothStatsLog.BLUETOOTH_ENABLED_STATE_CHANGED__STATE__UNKNOWN; timeSinceLastChanged = 0; } else { lastState = lastActiveLog.getEnable() ? BluetoothStatsLog.BLUETOOTH_ENABLED_STATE_CHANGED__STATE__ENABLED : BluetoothStatsLog .BLUETOOTH_ENABLED_STATE_CHANGED__STATE__DISABLED; timeSinceLastChanged = System.currentTimeMillis() - lastActiveLog.getTimestamp(); } BluetoothStatsLog.write_non_chained( BluetoothStatsLog.BLUETOOTH_ENABLED_STATE_CHANGED, Binder.getCallingUid(), null, state, reason, packageName, lastState, timeSinceLastChanged); } } private void addCrashLog() { synchronized (mCrashTimestamps) { if (mCrashTimestamps.size() == CRASH_LOG_MAX_SIZE) { mCrashTimestamps.removeFirst(); } mCrashTimestamps.add(System.currentTimeMillis()); mCrashes++; } } private void recoverBluetoothServiceFromError(boolean clearBle) { Log.e(TAG, "recoverBluetoothServiceFromError"); boolean repeatAirplaneRunnable = false; // 0 means we are matching unset `what` since we are using a token instead if (mHandler.hasMessages(0, ON_AIRPLANE_MODE_CHANGED_TOKEN)) { mHandler.removeCallbacksAndMessages(ON_AIRPLANE_MODE_CHANGED_TOKEN); repeatAirplaneRunnable = true; } mAdapterLock.readLock().lock(); try { if (mAdapter != null) { // Unregister callback object mAdapter.unregisterCallback(mBluetoothCallback, mContext.getAttributionSource()); } } catch (RemoteException e) { Log.e(TAG, "Unable to unregister", e); } finally { mAdapterLock.readLock().unlock(); } SystemClock.sleep(500); // disable addActiveLog(ENABLE_DISABLE_REASON_START_ERROR, false); handleDisable(); waitForState(STATE_OFF); sendBluetoothServiceDownCallback(); mAdapterLock.writeLock().lock(); try { if (mAdapter != null) { mAdapter = null; // Unbind mContext.unbindService(mConnection); } } finally { mAdapterLock.writeLock().unlock(); } mHandler.removeMessages(MESSAGE_BLUETOOTH_STATE_CHANGE); mState.set(STATE_OFF); if (clearBle) { clearBleApps(); } mEnable = false; // Send a Bluetooth Restart message to reenable bluetooth mHandler.sendEmptyMessageDelayed(MESSAGE_RESTART_BLUETOOTH_SERVICE, ERROR_RESTART_TIME_MS); if (repeatAirplaneRunnable) { onAirplaneModeChanged(AirplaneModeListener.isOnOverrode()); } } private boolean isBluetoothDisallowed() { final long callingIdentity = Binder.clearCallingIdentity(); try { return mContext.getSystemService(UserManager.class) .hasUserRestrictionForUser(UserManager.DISALLOW_BLUETOOTH, UserHandle.SYSTEM); } finally { Binder.restoreCallingIdentity(callingIdentity); } } /** * Disables BluetoothOppLauncherActivity component, so the Bluetooth sharing option is not * offered to the user if Bluetooth or sharing is disallowed. Puts the component to its default * state if Bluetooth is not disallowed. * * @param userHandle user to disable bluetooth sharing for * @param bluetoothSharingDisallowed whether bluetooth sharing is disallowed. */ private void updateOppLauncherComponentState( UserHandle userHandle, boolean bluetoothSharingDisallowed) { try { int newState; if (bluetoothSharingDisallowed) { newState = PackageManager.COMPONENT_ENABLED_STATE_DISABLED; } else if (BluetoothProperties.isProfileOppEnabled().orElse(false)) { newState = PackageManager.COMPONENT_ENABLED_STATE_ENABLED; } else { newState = PackageManager.COMPONENT_ENABLED_STATE_DEFAULT; } // Bluetooth OPP activities that should always be enabled, // even when Bluetooth is turned OFF. List baseBluetoothOppActivities = List.of( // Base sharing activity "com.android.bluetooth.opp.BluetoothOppLauncherActivity", // BT enable activities "com.android.bluetooth.opp.BluetoothOppBtEnableActivity", "com.android.bluetooth.opp.BluetoothOppBtEnablingActivity", "com.android.bluetooth.opp.BluetoothOppBtErrorActivity"); PackageManager systemPackageManager = mContext.getPackageManager(); PackageManager userPackageManager = mContext.createContextAsUser(userHandle, 0).getPackageManager(); var allPackages = systemPackageManager.getPackagesForUid(Process.BLUETOOTH_UID); for (String candidatePackage : allPackages) { Log.v(TAG, "Searching package " + candidatePackage); PackageInfo packageInfo; try { packageInfo = systemPackageManager.getPackageInfo( candidatePackage, PackageManager.PackageInfoFlags.of( PackageManager.GET_ACTIVITIES | PackageManager.MATCH_ANY_USER | PackageManager.MATCH_UNINSTALLED_PACKAGES | PackageManager.MATCH_DISABLED_COMPONENTS)); } catch (PackageManager.NameNotFoundException e) { // ignore, try next package Log.e(TAG, "Could not find package " + candidatePackage); continue; } catch (Exception e) { Log.e(TAG, "Error while loading package" + e); continue; } if (packageInfo.activities == null) { continue; } for (var activity : packageInfo.activities) { Log.v(TAG, "Checking activity " + activity.name); if (baseBluetoothOppActivities.contains(activity.name)) { for (String activityName : baseBluetoothOppActivities) { userPackageManager.setComponentEnabledSetting( new ComponentName(candidatePackage, activityName), newState, PackageManager.DONT_KILL_APP); } return; } } } Log.e( TAG, "Cannot toggle Bluetooth OPP activities, could not find them in any package"); } catch (Exception e) { Log.e(TAG, "updateOppLauncherComponentState failed: " + e); } } private int getServiceRestartMs() { return (mErrorRecoveryRetryCounter + 1) * SERVICE_RESTART_TIME_MS; } void dump(FileDescriptor fd, PrintWriter writer, String[] args) { if ((args.length > 0) && args[0].startsWith("--proto")) { dumpProto(fd); return; } String errorMsg = null; writer.println("Bluetooth Status"); writer.println(" enabled: " + isEnabled()); writer.println(" state: " + mState); writer.println(" address: " + logAddress(mAddress)); writer.println(" name: " + mName); if (mEnable) { long onDuration = SystemClock.elapsedRealtime() - mLastEnabledTime; String onDurationString = String.format( Locale.US, "%02d:%02d:%02d.%03d", (int) (onDuration / (1000 * 60 * 60)), (int) ((onDuration / (1000 * 60)) % 60), (int) ((onDuration / 1000) % 60), (int) (onDuration % 1000)); writer.println(" time since enabled: " + onDurationString); } if (mActiveLogs.size() == 0) { writer.println("\nBluetooth never enabled!"); } else { writer.println("\nEnable log:"); for (ActiveLog log : mActiveLogs) { writer.println(" " + log); } } writer.println("\nBluetooth crashed " + mCrashes + " time" + (mCrashes == 1 ? "" : "s")); if (mCrashes == CRASH_LOG_MAX_SIZE) { writer.println("(last " + CRASH_LOG_MAX_SIZE + ")"); } for (Long time : mCrashTimestamps) { writer.println(" " + timeToLog(time)); } writer.println( "\n" + mBleApps.size() + " BLE app" + (mBleApps.size() == 1 ? "" : "s") + " registered"); for (ClientDeathRecipient app : mBleApps.values()) { writer.println(" " + app.getPackageName()); } writer.println("\nBluetoothManagerService:"); writer.println(" mEnable:" + mEnable); writer.println(" mQuietEnable:" + mQuietEnable); writer.println(" mEnableExternal:" + mEnableExternal); writer.println(" mQuietEnableExternal:" + mQuietEnableExternal); writer.println(""); writer.flush(); if (args.length == 0) { // Add arg to produce output args = new String[1]; args[0] = "--print"; } try { dumpBluetoothFlags(writer); } catch (Exception e) { writer.println("Exception while dumping Bluetooth Flags"); } if (mAdapter == null) { errorMsg = "Bluetooth Service not connected"; } else { try { mAdapter.getAdapterBinder().asBinder().dumpAsync(fd, args); } catch (RemoteException re) { errorMsg = "RemoteException while dumping Bluetooth Service"; } } if (errorMsg != null) { writer.println(errorMsg); } } private void dumpBluetoothFlags(PrintWriter writer) throws IllegalAccessException, InvocationTargetException { writer.println("🚩Flag dump:"); // maxLen is used to align the flag output int maxLen = Arrays.stream(Flags.class.getDeclaredMethods()) .map(Method::getName) .map(String::length) .max(Integer::compare) .get(); String fmt = "\t%s: %-" + maxLen + "s %s"; for (Method m : Flags.class.getDeclaredMethods()) { String flagStatus = ((Boolean) m.invoke(null)) ? "[■]" : "[ ]"; String name = m.getName(); String snakeCaseName = name.replaceAll("([A-Z])", "_$1").toLowerCase(Locale.US); writer.println(String.format(fmt, flagStatus, name, snakeCaseName)); } writer.println(""); } private void dumpProto(FileDescriptor fd) { final ProtoOutputStream proto = new ProtoOutputStream(new FileOutputStream(fd)); proto.write(BluetoothManagerServiceDumpProto.ENABLED, isEnabled()); proto.write(BluetoothManagerServiceDumpProto.STATE, mState.get()); proto.write( BluetoothManagerServiceDumpProto.STATE_NAME, BluetoothAdapter.nameForState(mState.get())); proto.write(BluetoothManagerServiceDumpProto.ADDRESS, logAddress(mAddress)); proto.write(BluetoothManagerServiceDumpProto.NAME, mName); if (mEnable) { proto.write(BluetoothManagerServiceDumpProto.LAST_ENABLED_TIME_MS, mLastEnabledTime); } proto.write( BluetoothManagerServiceDumpProto.CURR_TIMESTAMP_MS, SystemClock.elapsedRealtime()); for (ActiveLog log : mActiveLogs) { long token = proto.start(BluetoothManagerServiceDumpProto.ACTIVE_LOGS); log.dump(proto); proto.end(token); } proto.write(BluetoothManagerServiceDumpProto.NUM_CRASHES, mCrashes); proto.write( BluetoothManagerServiceDumpProto.CRASH_LOG_MAXED, mCrashes == CRASH_LOG_MAX_SIZE); for (Long time : mCrashTimestamps) { proto.write(BluetoothManagerServiceDumpProto.CRASH_TIMESTAMPS_MS, time); } proto.write(BluetoothManagerServiceDumpProto.NUM_BLE_APPS, mBleApps.size()); for (ClientDeathRecipient app : mBleApps.values()) { proto.write( BluetoothManagerServiceDumpProto.BLE_APP_PACKAGE_NAMES, app.getPackageName()); } proto.flush(); } private static String getEnableDisableReasonString(int reason) { switch (reason) { case ENABLE_DISABLE_REASON_APPLICATION_REQUEST: return "APPLICATION_REQUEST"; case ENABLE_DISABLE_REASON_AIRPLANE_MODE: return "AIRPLANE_MODE"; case ENABLE_DISABLE_REASON_DISALLOWED: return "DISALLOWED"; case ENABLE_DISABLE_REASON_RESTARTED: return "RESTARTED"; case ENABLE_DISABLE_REASON_START_ERROR: return "START_ERROR"; case ENABLE_DISABLE_REASON_SYSTEM_BOOT: return "SYSTEM_BOOT"; case ENABLE_DISABLE_REASON_CRASH: return "CRASH"; case ENABLE_DISABLE_REASON_USER_SWITCH: return "USER_SWITCH"; case ENABLE_DISABLE_REASON_RESTORE_USER_SETTING: return "RESTORE_USER_SETTING"; case ENABLE_DISABLE_REASON_FACTORY_RESET: return "FACTORY_RESET"; case ENABLE_DISABLE_REASON_SATELLITE_MODE: return "SATELLITE MODE"; default: return "UNKNOWN[" + reason + "]"; } } static @NonNull Bundle getTempAllowlistBroadcastOptions() { final long duration = 10_000; final BroadcastOptions bOptions = BroadcastOptions.makeBasic(); bOptions.setTemporaryAppAllowlist( duration, TEMPORARY_ALLOW_LIST_TYPE_FOREGROUND_SERVICE_ALLOWED, PowerExemptionManager.REASON_BLUETOOTH_BROADCAST, ""); return bOptions.toBundle(); } private ComponentName resolveSystemService(@NonNull Intent intent) { List results = mContext.getPackageManager().queryIntentServices(intent, 0); if (results == null) { return null; } ComponentName comp = null; for (int i = 0; i < results.size(); i++) { ResolveInfo ri = results.get(i); if ((ri.serviceInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) == 0) { continue; } ComponentName foundComp = new ComponentName( ri.serviceInfo.applicationInfo.packageName, ri.serviceInfo.name); if (comp != null) { throw new IllegalStateException( "Multiple system services handle " + intent + ": " + comp + ", " + foundComp); } comp = foundComp; } return comp; } int setBtHciSnoopLogMode(int mode) { final BluetoothProperties.snoop_log_mode_values snoopMode; switch (mode) { case BluetoothAdapter.BT_SNOOP_LOG_MODE_DISABLED: snoopMode = BluetoothProperties.snoop_log_mode_values.DISABLED; break; case BluetoothAdapter.BT_SNOOP_LOG_MODE_FILTERED: snoopMode = BluetoothProperties.snoop_log_mode_values.FILTERED; break; case BluetoothAdapter.BT_SNOOP_LOG_MODE_FULL: snoopMode = BluetoothProperties.snoop_log_mode_values.FULL; break; default: Log.e(TAG, "setBtHciSnoopLogMode: Not a valid mode:" + mode); return BluetoothStatusCodes.ERROR_BAD_PARAMETERS; } try { BluetoothProperties.snoop_log_mode(snoopMode); } catch (RuntimeException e) { Log.e(TAG, "setBtHciSnoopLogMode: Failed to set mode to " + mode + ": " + e); return BluetoothStatusCodes.ERROR_UNKNOWN; } return BluetoothStatusCodes.SUCCESS; } int getBtHciSnoopLogMode() { BluetoothProperties.snoop_log_mode_values mode = BluetoothProperties.snoop_log_mode() .orElse(BluetoothProperties.snoop_log_mode_values.DISABLED); if (mode == BluetoothProperties.snoop_log_mode_values.FILTERED) { return BluetoothAdapter.BT_SNOOP_LOG_MODE_FILTERED; } else if (mode == BluetoothProperties.snoop_log_mode_values.FULL) { return BluetoothAdapter.BT_SNOOP_LOG_MODE_FULL; } return BluetoothAdapter.BT_SNOOP_LOG_MODE_DISABLED; } private final boolean mDeviceConfigAllowAutoOn; private void autoOnSetupTimer() { if (!mDeviceConfigAllowAutoOn) { Log.d(TAG, "No support for AutoOn feature: Not creating a timer"); return; } AutoOnFeature.resetAutoOnTimerForUser( mLooper, mCurrentUserContext, mState, this::enableFromAutoOn); } private T postAndWait(Callable callable) { FutureTask task = new FutureTask(callable); mHandler.post(task); try { return task.get(1, TimeUnit.SECONDS); } catch (TimeoutException | InterruptedException e) { SneakyThrow.sneakyThrow(e); } catch (ExecutionException e) { SneakyThrow.sneakyThrow(e.getCause()); } return null; } boolean isAutoOnSupported() { return mDeviceConfigAllowAutoOn && postAndWait( () -> AutoOnFeature.isUserSupported( mCurrentUserContext.getContentResolver())); } boolean isAutoOnEnabled() { if (!mDeviceConfigAllowAutoOn) { throw new IllegalStateException("AutoOnFeature is not supported in current config"); } return postAndWait(() -> AutoOnFeature.isUserEnabled(mCurrentUserContext)); } @RequiresApi(Build.VERSION_CODES.VANILLA_ICE_CREAM) void setAutoOnEnabled(boolean status) { if (!mDeviceConfigAllowAutoOn) { throw new IllegalStateException("AutoOnFeature is not supported in current config"); } postAndWait( Executors.callable( () -> AutoOnFeature.setUserEnabled( mLooper, mCurrentUserContext, mState, status, this::enableFromAutoOn))); } /** * Check if BLE is supported by this platform * * @param context current device context * @return true if BLE is supported, false otherwise */ private static boolean isBleSupported(Context context) { return context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE); } /** * Check if this is an automotive device * * @param context current device context * @return true if this Android device is an automotive device, false otherwise */ private static boolean isAutomotive(Context context) { return context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE); } /** * Check if this is a watch device * * @param context current device context * @return true if this Android device is a watch device, false otherwise */ private static boolean isWatch(Context context) { return context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH); } /** * Check if this is a TV device * * @param context current device context * @return true if this Android device is a TV device, false otherwise */ private static boolean isTv(Context context) { PackageManager pm = context.getPackageManager(); return pm.hasSystemFeature(PackageManager.FEATURE_TELEVISION) || pm.hasSystemFeature(PackageManager.FEATURE_LEANBACK); } }