summaryrefslogtreecommitdiff
path: root/service/java/com/android/server/bluetooth/BluetoothManagerService.java
diff options
context:
space:
mode:
Diffstat (limited to 'service/java/com/android/server/bluetooth/BluetoothManagerService.java')
-rw-r--r--service/java/com/android/server/bluetooth/BluetoothManagerService.java3410
1 files changed, 3410 insertions, 0 deletions
diff --git a/service/java/com/android/server/bluetooth/BluetoothManagerService.java b/service/java/com/android/server/bluetooth/BluetoothManagerService.java
new file mode 100644
index 0000000000..f5ef190c06
--- /dev/null
+++ b/service/java/com/android/server/bluetooth/BluetoothManagerService.java
@@ -0,0 +1,3410 @@
+/*
+ * 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;
+
+import static android.Manifest.permission.BLUETOOTH_CONNECT;
+import static android.content.pm.PackageManager.MATCH_SYSTEM_ONLY;
+import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+import static android.os.PowerExemptionManager.TEMPORARY_ALLOW_LIST_TYPE_FOREGROUND_SERVICE_ALLOWED;
+import static android.os.UserHandle.USER_SYSTEM;
+import static android.permission.PermissionCheckerManager.PERMISSION_HARD_DENIED;
+
+import android.Manifest;
+import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
+import android.annotation.SuppressLint;
+import android.app.ActivityManager;
+import android.app.AppGlobals;
+import android.app.AppOpsManager;
+import android.app.BroadcastOptions;
+import android.bluetooth.BluetoothA2dp;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothHearingAid;
+import android.bluetooth.BluetoothLeAudio;
+import android.bluetooth.BluetoothProfile;
+import android.bluetooth.BluetoothProtoEnums;
+import android.bluetooth.IBluetooth;
+import android.bluetooth.IBluetoothCallback;
+import android.bluetooth.IBluetoothGatt;
+import android.bluetooth.IBluetoothHeadset;
+import android.bluetooth.IBluetoothManager;
+import android.bluetooth.IBluetoothManagerCallback;
+import android.bluetooth.IBluetoothProfileServiceConnection;
+import android.bluetooth.IBluetoothStateChangeCallback;
+import android.bluetooth.IBluetoothLeCallControl;
+import android.content.ActivityNotFoundException;
+import android.content.AttributionSource;
+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.IPackageManager;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManagerInternal;
+import android.content.pm.UserInfo;
+import android.database.ContentObserver;
+import android.os.Binder;
+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.permission.PermissionManager;
+import android.provider.Settings;
+import android.provider.Settings.SettingNotFoundException;
+import android.text.TextUtils;
+import android.util.FeatureFlagUtils;
+import android.util.Log;
+import android.util.Slog;
+import android.util.proto.ProtoOutputStream;
+
+import com.android.internal.R;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.DumpUtils;
+import com.android.internal.util.FrameworkStatsLog;
+import com.android.server.pm.UserManagerInternal;
+import com.android.server.pm.UserManagerInternal.UserRestrictionsListener;
+import com.android.server.pm.UserRestrictionsUtils;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.Locale;
+import java.util.Map;
+import java.util.NoSuchElementException;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
+
+class BluetoothManagerService extends IBluetoothManager.Stub {
+ private static final String TAG = "BluetoothManagerService";
+ private static final boolean DBG = true;
+
+ private static final String BLUETOOTH_PRIVILEGED =
+ android.Manifest.permission.BLUETOOTH_PRIVILEGED;
+
+ private static final int ACTIVE_LOG_MAX_SIZE = 20;
+ private static final int CRASH_LOG_MAX_SIZE = 100;
+
+ private static final int TIMEOUT_BIND_MS = 3000; //Maximum msec to wait for a bind
+ //Maximum msec to wait for service restart
+ private static final int SERVICE_RESTART_TIME_MS = 400;
+ //Maximum msec to wait for restart due to error
+ private static final int ERROR_RESTART_TIME_MS = 3000;
+ //Maximum msec to delay MESSAGE_USER_SWITCHED
+ private static final int USER_SWITCHED_TIME_MS = 200;
+ // Delay for the addProxy function in msec
+ private static final int ADD_PROXY_DELAY_MS = 100;
+ // Delay for retrying enable and disable in msec
+ private static final int ENABLE_DISABLE_DELAY_MS = 300;
+ private static final int DELAY_BEFORE_RESTART_DUE_TO_INIT_FLAGS_CHANGED_MS = 300;
+ private static final int DELAY_FOR_RETRY_INIT_FLAG_CHECK_MS = 86400;
+
+ private static final int MESSAGE_ENABLE = 1;
+ private static final int MESSAGE_DISABLE = 2;
+ private static final int MESSAGE_HANDLE_ENABLE_DELAYED = 3;
+ private static final int MESSAGE_HANDLE_DISABLE_DELAYED = 4;
+ private static final int MESSAGE_INFORM_ADAPTER_SERVICE_UP = 22;
+ private static final int MESSAGE_REGISTER_STATE_CHANGE_CALLBACK = 30;
+ private static final int MESSAGE_UNREGISTER_STATE_CHANGE_CALLBACK = 31;
+ private static final int MESSAGE_BLUETOOTH_SERVICE_CONNECTED = 40;
+ private static final int MESSAGE_BLUETOOTH_SERVICE_DISCONNECTED = 41;
+ private static final int MESSAGE_RESTART_BLUETOOTH_SERVICE = 42;
+ private static final int MESSAGE_BLUETOOTH_STATE_CHANGE = 60;
+ private static final int MESSAGE_TIMEOUT_BIND = 100;
+ private static final int MESSAGE_TIMEOUT_UNBIND = 101;
+ private static final int MESSAGE_GET_NAME_AND_ADDRESS = 200;
+ private static final int MESSAGE_USER_SWITCHED = 300;
+ private static final int MESSAGE_USER_UNLOCKED = 301;
+ private static final int MESSAGE_ADD_PROXY_DELAYED = 400;
+ private static final int MESSAGE_BIND_PROFILE_SERVICE = 401;
+ private static final int MESSAGE_RESTORE_USER_SETTING = 500;
+ private static final int MESSAGE_INIT_FLAGS_CHANGED = 600;
+
+ 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
+ private static final int BLUETOOTH_OFF = 0;
+ // Bluetooth persisted setting is on
+ // and Airplane mode won't affect Bluetooth state at start up
+ private 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 static final int SERVICE_IBLUETOOTH = 1;
+ private static final int SERVICE_IBLUETOOTHGATT = 2;
+
+ private final Context mContext;
+
+ // Locks are not provided for mName and mAddress.
+ // They are accessed in handler or broadcast receiver, same thread context.
+ private String mAddress;
+ private String mName;
+ private final ContentResolver mContentResolver;
+ private final int mUserId;
+ private final RemoteCallbackList<IBluetoothManagerCallback> mCallbacks;
+ private final RemoteCallbackList<IBluetoothStateChangeCallback> mStateChangeCallbacks;
+ private IBinder mBluetoothBinder;
+ private IBluetooth mBluetooth;
+ private IBluetoothGatt mBluetoothGatt;
+ private final ReentrantReadWriteLock mBluetoothLock = new ReentrantReadWriteLock();
+ private boolean mBinding;
+ private int mBindingUserID;
+ private boolean mUnbinding;
+ private boolean mTryBindOnBindTimeout = false;
+
+ private BluetoothModeChangeHelper mBluetoothModeChangeHelper;
+
+ private BluetoothAirplaneModeListener mBluetoothAirplaneModeListener;
+
+ private BluetoothDeviceConfigListener mBluetoothDeviceConfigListener;
+
+ // used inside handler thread
+ private boolean mQuietEnable = false;
+ private boolean mEnable;
+
+ private static CharSequence timeToLog(long timestamp) {
+ return android.text.format.DateFormat.format("MM-dd HH:mm:ss", timestamp);
+ }
+
+ /**
+ * Used for tracking apps that enabled / disabled Bluetooth.
+ */
+ private class ActiveLog {
+ private int mReason;
+ private String mPackageName;
+ private boolean mEnable;
+ private long mTimestamp;
+
+ ActiveLog(int reason, String packageName, boolean enable, long timestamp) {
+ mReason = reason;
+ mPackageName = packageName;
+ mEnable = enable;
+ mTimestamp = timestamp;
+ }
+
+ public String toString() {
+ return timeToLog(mTimestamp) + (mEnable ? " Enabled " : " Disabled ")
+ + " due to " + getEnableDisableReasonString(mReason) + " by " + mPackageName;
+ }
+
+ 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<ActiveLog> mActiveLogs = new LinkedList<>();
+ private final LinkedList<Long> mCrashTimestamps = new LinkedList<>();
+ private int mCrashes;
+ private long mLastEnabledTime;
+
+ // configuration from external IBinder call which is used to
+ // synchronize with broadcast receiver.
+ private boolean mQuietEnableExternal;
+ private boolean mEnableExternal;
+
+ // Map of apps registered to keep BLE scanning on.
+ private Map<IBinder, ClientDeathRecipient> mBleApps =
+ new ConcurrentHashMap<IBinder, ClientDeathRecipient>();
+
+ private int mState;
+ private final BluetoothHandler mHandler;
+ private int mErrorRecoveryRetryCounter;
+ private final int mSystemUiUid;
+
+ private boolean mIsHearingAidProfileSupported;
+
+ private AppOpsManager mAppOps;
+
+ // Save a ProfileServiceConnections object for each of the bound
+ // bluetooth profile services
+ private final Map<Integer, ProfileServiceConnections> mProfileServices = new HashMap<>();
+
+ private final boolean mWirelessConsentRequired;
+
+ private final IBluetoothCallback mBluetoothCallback = new IBluetoothCallback.Stub() {
+ @Override
+ public void onBluetoothStateChange(int prevState, int newState) throws RemoteException {
+ Message msg =
+ mHandler.obtainMessage(MESSAGE_BLUETOOTH_STATE_CHANGE, prevState, newState);
+ mHandler.sendMessage(msg);
+ }
+ };
+
+ private final UserRestrictionsListener mUserRestrictionsListener =
+ new UserRestrictionsListener() {
+ @Override
+ public void onUserRestrictionsChanged(int userId, Bundle newRestrictions,
+ Bundle prevRestrictions) {
+
+ if (UserRestrictionsUtils.restrictionsChanged(prevRestrictions, newRestrictions,
+ UserManager.DISALLOW_BLUETOOTH_SHARING)) {
+ updateOppLauncherComponentState(userId,
+ newRestrictions.getBoolean(UserManager.DISALLOW_BLUETOOTH_SHARING));
+ }
+
+ // DISALLOW_BLUETOOTH can only be set by DO or PO on the system user.
+ if (userId == USER_SYSTEM
+ && UserRestrictionsUtils.restrictionsChanged(prevRestrictions,
+ newRestrictions, UserManager.DISALLOW_BLUETOOTH)) {
+ if (userId == USER_SYSTEM && newRestrictions.getBoolean(
+ UserManager.DISALLOW_BLUETOOTH)) {
+ updateOppLauncherComponentState(userId, true); // Sharing disallowed
+ sendDisableMsg(BluetoothProtoEnums.ENABLE_DISABLE_REASON_DISALLOWED,
+ mContext.getPackageName());
+ } else {
+ updateOppLauncherComponentState(userId, newRestrictions.getBoolean(
+ UserManager.DISALLOW_BLUETOOTH_SHARING));
+ }
+ }
+ }
+ };
+
+ @VisibleForTesting
+ public void onInitFlagsChanged() {
+ mHandler.removeMessages(MESSAGE_INIT_FLAGS_CHANGED);
+ mHandler.sendEmptyMessageDelayed(
+ MESSAGE_INIT_FLAGS_CHANGED,
+ DELAY_BEFORE_RESTART_DUE_TO_INIT_FLAGS_CHANGED_MS);
+ }
+
+ public boolean onFactoryReset(AttributionSource attributionSource) {
+ mContext.enforceCallingOrSelfPermission(BLUETOOTH_PRIVILEGED,
+ "Need BLUETOOTH_PRIVILEGED permission");
+
+ // Wait for stable state if bluetooth is temporary state.
+ int state = getState();
+ if (state == BluetoothAdapter.STATE_BLE_TURNING_ON
+ || state == BluetoothAdapter.STATE_TURNING_ON
+ || state == BluetoothAdapter.STATE_TURNING_OFF) {
+ if (!waitForState(Set.of(BluetoothAdapter.STATE_BLE_ON, BluetoothAdapter.STATE_ON))) {
+ return false;
+ }
+ }
+
+ // Clear registered LE apps to force shut-off Bluetooth
+ clearBleApps();
+ state = getState();
+ try {
+ mBluetoothLock.readLock().lock();
+ if (mBluetooth == null) {
+ return false;
+ }
+ if (state == BluetoothAdapter.STATE_BLE_ON) {
+ addActiveLog(
+ BluetoothProtoEnums.ENABLE_DISABLE_REASON_FACTORY_RESET,
+ mContext.getPackageName(), false);
+ mBluetooth.onBrEdrDown(attributionSource);
+ return true;
+ } else if (state == BluetoothAdapter.STATE_ON) {
+ addActiveLog(
+ BluetoothProtoEnums.ENABLE_DISABLE_REASON_FACTORY_RESET,
+ mContext.getPackageName(), false);
+ mBluetooth.disable(attributionSource);
+ return true;
+ }
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Unable to shutdown Bluetooth", e);
+ } finally {
+ mBluetoothLock.readLock().unlock();
+ }
+ return false;
+ }
+
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED)
+ public void onAirplaneModeChanged() {
+ synchronized (this) {
+ if (isBluetoothPersistedStateOn()) {
+ if (isAirplaneModeOn()) {
+ persistBluetoothSetting(BLUETOOTH_ON_AIRPLANE);
+ } else {
+ persistBluetoothSetting(BLUETOOTH_ON_BLUETOOTH);
+ }
+ }
+
+ int st = BluetoothAdapter.STATE_OFF;
+ try {
+ mBluetoothLock.readLock().lock();
+ if (mBluetooth != null) {
+ st = mBluetooth.getState();
+ }
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Unable to call getState", e);
+ return;
+ } finally {
+ mBluetoothLock.readLock().unlock();
+ }
+
+ Slog.d(TAG,
+ "Airplane Mode change - current state: " + BluetoothAdapter.nameForState(
+ st) + ", isAirplaneModeOn()=" + isAirplaneModeOn());
+
+ if (isAirplaneModeOn()) {
+ // Clear registered LE apps to force shut-off
+ clearBleApps();
+
+ // If state is BLE_ON make sure we trigger disableBLE
+ if (st == BluetoothAdapter.STATE_BLE_ON) {
+ try {
+ mBluetoothLock.readLock().lock();
+ if (mBluetooth != null) {
+ addActiveLog(
+ BluetoothProtoEnums.ENABLE_DISABLE_REASON_AIRPLANE_MODE,
+ mContext.getPackageName(), false);
+ mBluetooth.onBrEdrDown(mContext.getAttributionSource());
+ mEnable = false;
+ }
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Unable to call onBrEdrDown", e);
+ } finally {
+ mBluetoothLock.readLock().unlock();
+ }
+ } else {
+ Slog.d(TAG, "Airplane ON: sendDisableMsg");
+ sendDisableMsg(BluetoothProtoEnums.ENABLE_DISABLE_REASON_AIRPLANE_MODE,
+ mContext.getPackageName());
+ }
+ } else if (mEnableExternal) {
+ if (isBluetoothPersistedStateOn()) {
+ Slog.d(TAG, "Airplane OFF: sendEnableMsg");
+ sendEnableMsg(mQuietEnableExternal,
+ BluetoothProtoEnums.ENABLE_DISABLE_REASON_AIRPLANE_MODE,
+ mContext.getPackageName());
+ }
+ }
+ }
+ }
+
+ 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 (DBG) {
+ Slog.d(TAG, "Bluetooth Adapter name changed to " + newName + " by "
+ + mContext.getPackageName());
+ }
+ if (newName != null) {
+ storeNameAndAddress(newName, null);
+ }
+ } else if (BluetoothAdapter.ACTION_BLUETOOTH_ADDRESS_CHANGED.equals(action)) {
+ String newAddress = intent.getStringExtra(BluetoothAdapter.EXTRA_BLUETOOTH_ADDRESS);
+ if (newAddress != null) {
+ if (DBG) {
+ Slog.d(TAG, "Bluetooth Adapter address changed to " + newAddress);
+ }
+ storeNameAndAddress(null, newAddress);
+ } else {
+ if (DBG) {
+ Slog.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);
+
+ if (DBG) {
+ Slog.d(TAG,
+ "ACTION_SETTING_RESTORED with BLUETOOTH_ON, prevValue=" + prevValue
+ + ", newValue=" + newValue);
+ }
+
+ if ((newValue != null) && (prevValue != null) && !prevValue.equals(newValue)) {
+ Message msg = mHandler.obtainMessage(MESSAGE_RESTORE_USER_SETTING,
+ newValue.equals("0") ? RESTORE_SETTING_TO_OFF
+ : RESTORE_SETTING_TO_ON, 0);
+ mHandler.sendMessage(msg);
+ }
+ }
+ } else if (BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED.equals(action)
+ || BluetoothHearingAid.ACTION_CONNECTION_STATE_CHANGED.equals(action)
+ || BluetoothLeAudio.ACTION_LE_AUDIO_CONNECTION_STATE_CHANGED.equals(action)) {
+ final int state = intent.getIntExtra(BluetoothProfile.EXTRA_STATE,
+ BluetoothProfile.STATE_CONNECTED);
+ if (mHandler.hasMessages(MESSAGE_INIT_FLAGS_CHANGED)
+ && state == BluetoothProfile.STATE_DISCONNECTED
+ && !mBluetoothModeChangeHelper.isMediaProfileConnected()) {
+ Slog.i(TAG, "Device disconnected, reactivating pending flag changes");
+ onInitFlagsChanged();
+ }
+ }
+ }
+ };
+
+ BluetoothManagerService(Context context) {
+ mHandler = new BluetoothHandler(IoThread.get().getLooper());
+
+ mContext = context;
+
+ mWirelessConsentRequired = context.getResources()
+ .getBoolean(com.android.internal.R.bool.config_wirelessConsentRequired);
+
+ mCrashes = 0;
+ mBluetooth = null;
+ mBluetoothBinder = null;
+ mBluetoothGatt = null;
+ mBinding = false;
+ mTryBindOnBindTimeout = false;
+ mUnbinding = false;
+ mEnable = false;
+ mState = BluetoothAdapter.STATE_OFF;
+ mQuietEnableExternal = false;
+ mEnableExternal = false;
+ mAddress = null;
+ mName = null;
+ mErrorRecoveryRetryCounter = 0;
+ mContentResolver = context.getContentResolver();
+ mUserId = mContentResolver.getUserId();
+ // Observe BLE scan only mode settings change.
+ registerForBleScanModeChange();
+ mCallbacks = new RemoteCallbackList<IBluetoothManagerCallback>();
+ mStateChangeCallbacks = new RemoteCallbackList<IBluetoothStateChangeCallback>();
+
+ mIsHearingAidProfileSupported = context.getResources()
+ .getBoolean(com.android.internal.R.bool.config_hearing_aid_profile_supported);
+
+ // TODO: We need a more generic way to initialize the persist keys of FeatureFlagUtils
+ String value = SystemProperties.get(FeatureFlagUtils.PERSIST_PREFIX + FeatureFlagUtils.HEARING_AID_SETTINGS);
+ if (!TextUtils.isEmpty(value)) {
+ boolean isHearingAidEnabled = Boolean.parseBoolean(value);
+ Log.v(TAG, "set feature flag HEARING_AID_SETTINGS to " + isHearingAidEnabled);
+ FeatureFlagUtils.setEnabled(context, FeatureFlagUtils.HEARING_AID_SETTINGS, isHearingAidEnabled);
+ if (isHearingAidEnabled && !mIsHearingAidProfileSupported) {
+ // Overwrite to enable support by FeatureFlag
+ mIsHearingAidProfileSupported = true;
+ }
+ }
+
+ 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(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED);
+ filter.addAction(BluetoothHearingAid.ACTION_CONNECTION_STATE_CHANGED);
+ filter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
+ mContext.registerReceiver(mReceiver, filter);
+
+ loadStoredNameAndAddress();
+ if (isBluetoothPersistedStateOn()) {
+ if (DBG) {
+ Slog.d(TAG, "Startup: Bluetooth persisted state is ON.");
+ }
+ mEnableExternal = true;
+ }
+
+ String airplaneModeRadios =
+ Settings.Global.getString(mContentResolver, Settings.Global.AIRPLANE_MODE_RADIOS);
+ if (airplaneModeRadios == null || airplaneModeRadios.contains(
+ Settings.Global.RADIO_BLUETOOTH)) {
+ mBluetoothAirplaneModeListener = new BluetoothAirplaneModeListener(
+ this, IoThread.get().getLooper(), context);
+ }
+
+ int systemUiUid = -1;
+ // Check if device is configured with no home screen, which implies no SystemUI.
+ boolean noHome = mContext.getResources().getBoolean(R.bool.config_noHomeScreen);
+ if (!noHome) {
+ PackageManagerInternal pm = LocalServices.getService(PackageManagerInternal.class);
+ systemUiUid = pm.getPackageUid(pm.getSystemUiServiceComponent().getPackageName(),
+ MATCH_SYSTEM_ONLY, USER_SYSTEM);
+ }
+ if (systemUiUid >= 0) {
+ Slog.d(TAG, "Detected SystemUiUid: " + Integer.toString(systemUiUid));
+ } else {
+ // Some platforms, such as wearables do not have a system ui.
+ Slog.w(TAG, "Unable to resolve SystemUI's UID.");
+ }
+ mSystemUiUid = systemUiUid;
+ }
+
+ /**
+ * Returns true if airplane mode is currently on
+ */
+ private boolean isAirplaneModeOn() {
+ return Settings.Global.getInt(mContext.getContentResolver(),
+ Settings.Global.AIRPLANE_MODE_ON, 0) == 1;
+ }
+
+ private boolean supportBluetoothPersistedState() {
+ return mContext.getResources().getBoolean(R.bool.config_supportBluetoothPersistedState);
+ }
+
+ /**
+ * Returns true if the Bluetooth saved state is "on"
+ */
+ private boolean isBluetoothPersistedStateOn() {
+ if (!supportBluetoothPersistedState()) {
+ return false;
+ }
+ int state = Settings.Global.getInt(mContentResolver, Settings.Global.BLUETOOTH_ON, -1);
+ if (DBG) {
+ Slog.d(TAG, "Bluetooth persisted state: " + state);
+ }
+ return state != BLUETOOTH_OFF;
+ }
+
+ private boolean isBluetoothPersistedStateOnAirplane() {
+ if (!supportBluetoothPersistedState()) {
+ return false;
+ }
+ int state = Settings.Global.getInt(mContentResolver, Settings.Global.BLUETOOTH_ON, -1);
+ if (DBG) {
+ Slog.d(TAG, "Bluetooth persisted state: " + state);
+ }
+ return state == BLUETOOTH_ON_AIRPLANE;
+ }
+
+ /**
+ * Returns true if the Bluetooth saved state is BLUETOOTH_ON_BLUETOOTH
+ */
+ private boolean isBluetoothPersistedStateOnBluetooth() {
+ if (!supportBluetoothPersistedState()) {
+ return false;
+ }
+ return Settings.Global.getInt(mContentResolver, Settings.Global.BLUETOOTH_ON,
+ BLUETOOTH_ON_BLUETOOTH) == BLUETOOTH_ON_BLUETOOTH;
+ }
+
+ /**
+ * Save the Bluetooth on/off state
+ */
+ private void persistBluetoothSetting(int value) {
+ if (DBG) {
+ Slog.d(TAG, "Persisting Bluetooth Setting: " + value);
+ }
+ // waive WRITE_SECURE_SETTINGS permission check
+ final long callingIdentity = Binder.clearCallingIdentity();
+ try {
+ Settings.Global.putInt(mContext.getContentResolver(),
+ Settings.Global.BLUETOOTH_ON, value);
+ } finally {
+ Binder.restoreCallingIdentity(callingIdentity);
+ }
+ }
+
+ /**
+ * Returns true if the Bluetooth Adapter's name and address is
+ * locally cached
+ * @return
+ */
+ 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
+ * in the local cache
+ */
+ private void loadStoredNameAndAddress() {
+ if (DBG) {
+ Slog.d(TAG, "Loading stored name and address");
+ }
+ if (mContext.getResources()
+ .getBoolean(com.android.internal.R.bool.config_bluetooth_address_validation)
+ && Settings.Secure.getIntForUser(mContentResolver,
+ Settings.Secure.BLUETOOTH_NAME, 0, mUserId)
+ == 0) {
+ // if the valid flag is not set, don't load the address and name
+ if (DBG) {
+ Slog.d(TAG, "invalid bluetooth name and address stored");
+ }
+ return;
+ }
+ mName = Settings.Secure.getStringForUser(
+ mContentResolver, Settings.Secure.BLUETOOTH_NAME, mUserId);
+ mAddress = Settings.Secure.getStringForUser(
+ mContentResolver, Settings.Secure.BLUETOOTH_ADDRESS, mUserId);
+ if (DBG) {
+ Slog.d(TAG, "Stored bluetooth Name=" + mName + ",Address=" + mAddress);
+ }
+ }
+
+ /**
+ * Save the Bluetooth name and address in the persistent store.
+ * Only non-null values will be saved.
+ * @param name
+ * @param address
+ */
+ private void storeNameAndAddress(String name, String address) {
+ if (name != null) {
+ Settings.Secure.putStringForUser(mContentResolver, Settings.Secure.BLUETOOTH_NAME, name,
+ mUserId);
+ mName = name;
+ if (DBG) {
+ Slog.d(TAG, "Stored Bluetooth name: " + Settings.Secure.getStringForUser(
+ mContentResolver, Settings.Secure.BLUETOOTH_NAME,
+ mUserId));
+ }
+ }
+
+ if (address != null) {
+ Settings.Secure.putStringForUser(mContentResolver, Settings.Secure.BLUETOOTH_ADDRESS,
+ address, mUserId);
+ mAddress = address;
+ if (DBG) {
+ Slog.d(TAG,
+ "Stored Bluetoothaddress: " + Settings.Secure.getStringForUser(
+ mContentResolver, Settings.Secure.BLUETOOTH_ADDRESS,
+ mUserId));
+ }
+ }
+
+ if ((name != null) && (address != null)) {
+ Settings.Secure.putIntForUser(mContentResolver, Settings.Secure.BLUETOOTH_ADDR_VALID, 1,
+ mUserId);
+ }
+ }
+
+ public IBluetooth registerAdapter(IBluetoothManagerCallback callback) {
+ if (callback == null) {
+ Slog.w(TAG, "Callback is null in registerAdapter");
+ return null;
+ }
+ synchronized (mCallbacks) {
+ mCallbacks.register(callback);
+ }
+ return mBluetooth;
+ }
+
+ public void unregisterAdapter(IBluetoothManagerCallback callback) {
+ if (callback == null) {
+ Slog.w(TAG, "Callback is null in unregisterAdapter");
+ return;
+ }
+ synchronized (mCallbacks) {
+ mCallbacks.unregister(callback);
+ }
+ }
+
+ public void registerStateChangeCallback(IBluetoothStateChangeCallback callback) {
+ if (callback == null) {
+ Slog.w(TAG, "registerStateChangeCallback: Callback is null!");
+ return;
+ }
+ Message msg = mHandler.obtainMessage(MESSAGE_REGISTER_STATE_CHANGE_CALLBACK);
+ msg.obj = callback;
+ mHandler.sendMessage(msg);
+ }
+
+ public void unregisterStateChangeCallback(IBluetoothStateChangeCallback callback) {
+ if (callback == null) {
+ Slog.w(TAG, "unregisterStateChangeCallback: Callback is null!");
+ return;
+ }
+ Message msg = mHandler.obtainMessage(MESSAGE_UNREGISTER_STATE_CHANGE_CALLBACK);
+ msg.obj = callback;
+ mHandler.sendMessage(msg);
+ }
+
+ public boolean isEnabled() {
+ return getState() == BluetoothAdapter.STATE_ON;
+ }
+
+ public int getState() {
+ if ((Binder.getCallingUid() != Process.SYSTEM_UID) && (!checkIfCallerIsForegroundUser())) {
+ Slog.w(TAG, "getState(): report OFF for non-active and non system user");
+ return BluetoothAdapter.STATE_OFF;
+ }
+
+ try {
+ mBluetoothLock.readLock().lock();
+ if (mBluetooth != null) {
+ return mBluetooth.getState();
+ }
+ } catch (RemoteException e) {
+ Slog.e(TAG, "getState()", e);
+ } finally {
+ mBluetoothLock.readLock().unlock();
+ }
+ return BluetoothAdapter.STATE_OFF;
+ }
+
+ class ClientDeathRecipient implements IBinder.DeathRecipient {
+ private String mPackageName;
+
+ ClientDeathRecipient(String packageName) {
+ mPackageName = packageName;
+ }
+
+ public void binderDied() {
+ if (DBG) {
+ Slog.d(TAG, "Binder is dead - unregister " + mPackageName);
+ }
+
+ for (Map.Entry<IBinder, ClientDeathRecipient> entry : mBleApps.entrySet()) {
+ IBinder token = entry.getKey();
+ ClientDeathRecipient deathRec = entry.getValue();
+ if (deathRec.equals(this)) {
+ updateBleAppCount(token, false, mPackageName);
+ break;
+ }
+ }
+
+ int appCount = mBleApps.size();
+ if (DBG) {
+ Slog.d(TAG, appCount + "Binder is dead,registered Ble Apps");
+ }
+
+ if (appCount == 0 && mEnable) {
+ disableBleScanMode();
+ }
+
+ if (appCount == 0) {
+ int st = BluetoothAdapter.STATE_OFF;
+ try {
+ mBluetoothLock.readLock().lock();
+ if (mBluetooth != null) {
+ st = mBluetooth.getState();
+ }
+ if (!mEnableExternal || (st == BluetoothAdapter.STATE_BLE_ON)) {
+ if (DBG) {
+ Slog.d(TAG, "Move to BT state OFF");
+ }
+ sendBrEdrDownCallback(mContext.getAttributionSource());
+ }
+ } catch (RemoteException e) {
+ Slog.e(TAG, "", e);
+ } finally {
+ mBluetoothLock.readLock().unlock();
+ }
+ }
+ }
+
+ public String getPackageName() {
+ return mPackageName;
+ }
+ }
+
+ @Override
+ public boolean isBleScanAlwaysAvailable() {
+ if (isAirplaneModeOn() && !mEnable) {
+ return false;
+ }
+ try {
+ return Settings.Global.getInt(mContentResolver,
+ Settings.Global.BLE_SCAN_ALWAYS_AVAILABLE) != 0;
+ } catch (SettingNotFoundException e) {
+ }
+ return false;
+ }
+
+ @Override
+ public boolean isHearingAidProfileSupported() {
+ return mIsHearingAidProfileSupported;
+ }
+
+ private boolean isDeviceProvisioned() {
+ return Settings.Global.getInt(mContentResolver, Settings.Global.DEVICE_PROVISIONED,
+ 0) != 0;
+ }
+
+ // Monitor change of BLE scan only mode settings.
+ private void registerForProvisioningStateChange() {
+ ContentObserver contentObserver = new ContentObserver(null) {
+ @Override
+ public void onChange(boolean selfChange) {
+ if (!isDeviceProvisioned()) {
+ if (DBG) {
+ Slog.d(TAG, "DEVICE_PROVISIONED setting changed, but device is not "
+ + "provisioned");
+ }
+ return;
+ }
+ if (mHandler.hasMessages(MESSAGE_INIT_FLAGS_CHANGED)) {
+ Slog.i(TAG, "Device provisioned, reactivating pending flag changes");
+ onInitFlagsChanged();
+ }
+ }
+ };
+
+ mContentResolver.registerContentObserver(
+ Settings.Global.getUriFor(Settings.Global.DEVICE_PROVISIONED), false,
+ contentObserver);
+ }
+
+ // Monitor change of BLE scan only mode settings.
+ private void registerForBleScanModeChange() {
+ ContentObserver contentObserver = new ContentObserver(null) {
+ @Override
+ public void onChange(boolean selfChange) {
+ if (isBleScanAlwaysAvailable()) {
+ // Nothing to do
+ return;
+ }
+ // BLE scan is not available.
+ disableBleScanMode();
+ clearBleApps();
+ try {
+ mBluetoothLock.readLock().lock();
+ if (mBluetooth != null) {
+ addActiveLog(BluetoothProtoEnums.ENABLE_DISABLE_REASON_APPLICATION_REQUEST,
+ mContext.getPackageName(), false);
+ mBluetooth.onBrEdrDown(mContext.getAttributionSource());
+ }
+ } catch (RemoteException e) {
+ Slog.e(TAG, "error when disabling bluetooth", e);
+ } finally {
+ mBluetoothLock.readLock().unlock();
+ }
+ }
+ };
+
+ mContentResolver.registerContentObserver(
+ Settings.Global.getUriFor(Settings.Global.BLE_SCAN_ALWAYS_AVAILABLE), false,
+ contentObserver);
+ }
+
+ // Disable ble scan only mode.
+ private void disableBleScanMode() {
+ try {
+ mBluetoothLock.readLock().lock();
+ if (mBluetooth != null && (mBluetooth.getState() != BluetoothAdapter.STATE_ON) && (!isBluetoothPersistedStateOnBluetooth())) {
+ if (DBG) {
+ Slog.d(TAG, "Reseting the mEnable flag for clean disable");
+ }
+ if (!mEnableExternal) {
+ mEnable = false;
+ }
+ }
+ } catch (RemoteException e) {
+ Slog.e(TAG, "getState()", e);
+ } finally {
+ mBluetoothLock.readLock().unlock();
+ }
+ }
+
+ private int updateBleAppCount(IBinder token, boolean enable, String packageName) {
+ ClientDeathRecipient r = mBleApps.get(token);
+ int st = BluetoothAdapter.STATE_OFF;
+ 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);
+ if (DBG) {
+ Slog.d(TAG, "Registered for death of " + packageName);
+ }
+ } else if (!enable && r != null) {
+ // Unregister death recipient as the app goes away.
+ token.unlinkToDeath(r, 0);
+ mBleApps.remove(token);
+ if (DBG) {
+ Slog.d(TAG, "Unregistered for death of " + packageName);
+ }
+ }
+
+ int appCount = mBleApps.size();
+ if (DBG) {
+ Slog.d(TAG, appCount + " registered Ble Apps");
+ }
+ return appCount;
+ }
+
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
+ private boolean checkBluetoothPermissions(AttributionSource attributionSource, String message,
+ boolean requireForeground) {
+ if (isBluetoothDisallowed()) {
+ if (DBG) {
+ Slog.d(TAG, "checkBluetoothPermissions: bluetooth disallowed");
+ }
+ return false;
+ }
+ // Check if packageName belongs to callingUid
+ final int callingUid = Binder.getCallingUid();
+ final boolean isCallerSystem = UserHandle.getAppId(callingUid) == Process.SYSTEM_UID;
+ if (!isCallerSystem) {
+ checkPackage(callingUid, attributionSource.getPackageName());
+
+ if (requireForeground && !checkIfCallerIsForegroundUser()) {
+ Slog.w(TAG, "Not allowed for non-active and non system user");
+ return false;
+ }
+
+ if (!checkConnectPermissionForDataDelivery(mContext, attributionSource, message)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ public boolean enableBle(AttributionSource attributionSource, IBinder token)
+ throws RemoteException {
+ final String packageName = attributionSource.getPackageName();
+ if (!checkBluetoothPermissions(attributionSource, "enableBle", false)) {
+ if (DBG) {
+ Slog.d(TAG, "enableBle(): bluetooth disallowed");
+ }
+ return false;
+ }
+
+ if (DBG) {
+ Slog.d(TAG, "enableBle(" + packageName + "): mBluetooth =" + mBluetooth
+ + " mBinding = " + mBinding + " mState = "
+ + BluetoothAdapter.nameForState(mState));
+ }
+ updateBleAppCount(token, true, packageName);
+
+ if (mState == BluetoothAdapter.STATE_ON
+ || mState == BluetoothAdapter.STATE_BLE_ON
+ || mState == BluetoothAdapter.STATE_TURNING_ON
+ || mState == BluetoothAdapter.STATE_TURNING_OFF
+ || mState == BluetoothAdapter.STATE_BLE_TURNING_ON) {
+ Log.d(TAG, "enableBLE(): Bluetooth is already enabled or is turning on");
+ return true;
+ }
+ synchronized (mReceiver) {
+ // waive WRITE_SECURE_SETTINGS permission check
+ sendEnableMsg(false, BluetoothProtoEnums.ENABLE_DISABLE_REASON_APPLICATION_REQUEST,
+ packageName, true);
+ }
+ return true;
+ }
+
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED)
+ public boolean disableBle(AttributionSource attributionSource, IBinder token)
+ throws RemoteException {
+ final String packageName = attributionSource.getPackageName();
+ if (!checkBluetoothPermissions(attributionSource, "disableBle", false)) {
+ if (DBG) {
+ Slog.d(TAG, "disableBLE(): bluetooth disallowed");
+ }
+ return false;
+ }
+
+ if (DBG) {
+ Slog.d(TAG, "disableBle(" + packageName + "): mBluetooth =" + mBluetooth
+ + " mBinding = " + mBinding + " mState = "
+ + BluetoothAdapter.nameForState(mState));
+ }
+ /* update app count even in bt off state, if quick enableBle and
+ * disableBle during BT turning off may leave app count non zero
+ */
+ updateBleAppCount(token, false, packageName);
+
+ if (mState == BluetoothAdapter.STATE_OFF) {
+ Slog.d(TAG, "disableBLE(): Already disabled");
+ return false;
+ }
+
+ if (mState == BluetoothAdapter.STATE_BLE_ON && !isBleAppPresent()) {
+ if (mEnable) {
+ disableBleScanMode();
+ }
+ if (!mEnableExternal) {
+ addActiveLog(BluetoothProtoEnums.ENABLE_DISABLE_REASON_APPLICATION_REQUEST,
+ packageName, false);
+ sendBrEdrDownCallback(attributionSource);
+ }
+ }
+ return true;
+ }
+
+ // Clear all apps using BLE scan only mode.
+ private void clearBleApps() {
+ mBleApps.clear();
+ }
+
+ /** @hide */
+ public boolean isBleAppPresent() {
+ if (DBG) {
+ Slog.d(TAG, "isBleAppPresent() count: " + mBleApps.size());
+ }
+ return mBleApps.size() > 0;
+ }
+
+ /**
+ * Call IBluetooth.onLeServiceUp() to continue if Bluetooth should be on,
+ * call IBluetooth.onBrEdrDown() to disable if Bluetooth should be off.
+ */
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED)
+ private void continueFromBleOnState() {
+ if (DBG) {
+ Slog.d(TAG, "continueFromBleOnState()");
+ }
+ try {
+ mBluetoothLock.readLock().lock();
+ if (mBluetooth == null) {
+ Slog.e(TAG, "onBluetoothServiceUp: mBluetooth is null!");
+ return;
+ }
+ int st = mBluetooth.getState();
+ if (st != BluetoothAdapter.STATE_BLE_ON) {
+ if (DBG) Slog.v(TAG, "onBluetoothServiceUp: state isn't BLE_ON: " +
+ BluetoothAdapter.nameForState(st));
+ return;
+ }
+ if (!mEnableExternal && !isBleAppPresent() &&
+ !isBluetoothPersistedStateOnBluetooth()) {
+ Slog.i(TAG, "Bluetooth was disabled while enabling BLE, disable BLE now");
+ mEnable = false;
+ mBluetooth.onBrEdrDown(mContext.getAttributionSource());
+ return;
+ }
+ if (isBluetoothPersistedStateOnBluetooth() ||
+ mEnableExternal) {
+ // This triggers transition to STATE_ON
+ mBluetooth.updateQuietModeStatus(mQuietEnable,
+ mContext.getAttributionSource());
+ mBluetooth.onLeServiceUp(mContext.getAttributionSource());
+ persistBluetoothSetting(BLUETOOTH_ON_BLUETOOTH);
+ }
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Unable to call onServiceUp", e);
+ } finally {
+ mBluetoothLock.readLock().unlock();
+ }
+ }
+
+ /**
+ * Inform BluetoothAdapter instances that BREDR part is down
+ * and turn off all service and stack if no LE app needs it
+ */
+ @RequiresPermission(allOf = {
+ android.Manifest.permission.BLUETOOTH_CONNECT,
+ android.Manifest.permission.BLUETOOTH_PRIVILEGED,
+ })
+ private void sendBrEdrDownCallback(AttributionSource attributionSource) {
+ if (DBG) {
+ Slog.d(TAG, "Calling sendBrEdrDownCallback callbacks");
+ }
+
+ if (mBluetooth == null) {
+ Slog.w(TAG, "Bluetooth handle is null");
+ return;
+ }
+
+ if (isBleAppPresent()) {
+ // Need to stay at BLE ON. Disconnect all Gatt connections
+ try {
+ if (mBluetoothGatt != null) {
+ mBluetoothGatt.unregAll(attributionSource);
+ }
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Unable to disconnect all apps.", e);
+ }
+ } else {
+ try {
+ mBluetoothLock.readLock().lock();
+ if (mBluetooth != null) {
+ mBluetooth.onBrEdrDown(attributionSource);
+ }
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Call to onBrEdrDown() failed.", e);
+ } finally {
+ mBluetoothLock.readLock().unlock();
+ }
+ }
+
+ }
+
+ public boolean enableNoAutoConnect(AttributionSource attributionSource) {
+ final String packageName = attributionSource.getPackageName();
+ if (!checkBluetoothPermissions(attributionSource, "enableNoAutoConnect", false)) {
+ if (DBG) {
+ Slog.d(TAG, "enableNoAutoConnect(): not enabling - bluetooth disallowed");
+ }
+ return false;
+ }
+
+ if (DBG) {
+ Slog.d(TAG, "enableNoAutoConnect(): mBluetooth =" + mBluetooth + " mBinding = "
+ + mBinding);
+ }
+
+ int callingAppId = UserHandle.getAppId(Binder.getCallingUid());
+ if (callingAppId != Process.NFC_UID) {
+ throw new SecurityException("no permission to enable Bluetooth quietly");
+ }
+
+ synchronized (mReceiver) {
+ mQuietEnableExternal = true;
+ mEnableExternal = true;
+ sendEnableMsg(true,
+ BluetoothProtoEnums.ENABLE_DISABLE_REASON_APPLICATION_REQUEST, packageName);
+ }
+ return true;
+ }
+
+ public boolean enable(AttributionSource attributionSource) throws RemoteException {
+ final String packageName = attributionSource.getPackageName();
+ if (!checkBluetoothPermissions(attributionSource, "enable", true)) {
+ if (DBG) {
+ Slog.d(TAG, "enable(): not enabling - bluetooth disallowed");
+ }
+ return false;
+ }
+
+ final int callingUid = Binder.getCallingUid();
+ final boolean callerSystem = UserHandle.getAppId(callingUid) == Process.SYSTEM_UID;
+ if (!callerSystem && !isEnabled() && mWirelessConsentRequired
+ && startConsentUiIfNeeded(packageName,
+ callingUid, BluetoothAdapter.ACTION_REQUEST_ENABLE)) {
+ return false;
+ }
+
+ if (DBG) {
+ Slog.d(TAG, "enable(" + packageName + "): mBluetooth =" + mBluetooth + " mBinding = "
+ + mBinding + " mState = " + BluetoothAdapter.nameForState(mState));
+ }
+
+ synchronized (mReceiver) {
+ mQuietEnableExternal = false;
+ mEnableExternal = true;
+
+ // waive WRITE_SECURE_SETTINGS permission check
+ sendEnableMsg(false,
+ BluetoothProtoEnums.ENABLE_DISABLE_REASON_APPLICATION_REQUEST, packageName);
+ }
+ if (DBG) {
+ Slog.d(TAG, "enable returning");
+ }
+ return true;
+ }
+
+ public boolean disable(AttributionSource attributionSource, boolean persist)
+ throws RemoteException {
+ if (!persist) {
+ mContext.enforceCallingOrSelfPermission(BLUETOOTH_PRIVILEGED,
+ "Need BLUETOOTH_PRIVILEGED permission");
+ }
+
+ final String packageName = attributionSource.getPackageName();
+ if (!checkBluetoothPermissions(attributionSource, "disable", true)) {
+ if (DBG) {
+ Slog.d(TAG, "disable(): not disabling - bluetooth disallowed");
+ }
+ return false;
+ }
+
+ final int callingUid = Binder.getCallingUid();
+ final boolean callerSystem = UserHandle.getAppId(callingUid) == Process.SYSTEM_UID;
+ if (!callerSystem && isEnabled() && mWirelessConsentRequired
+ && startConsentUiIfNeeded(packageName,
+ callingUid, BluetoothAdapter.ACTION_REQUEST_DISABLE)) {
+ return false;
+ }
+
+ if (DBG) {
+ Slog.d(TAG, "disable(" + packageName + "): mBluetooth = "
+ + mBluetooth + " mBinding = " + mBinding);
+ }
+
+ synchronized (mReceiver) {
+ if (!isBluetoothPersistedStateOnAirplane()) {
+ if (persist) {
+ persistBluetoothSetting(BLUETOOTH_OFF);
+ mEnableExternal = false;
+ }
+ }
+ if (persist) {
+ sendDisableMsg(BluetoothProtoEnums.ENABLE_DISABLE_REASON_APPLICATION_REQUEST,
+ packageName);
+ } else {
+ /* It means disable is called by shutdown thread */
+ synchronized (this) {
+ clearBleApps();
+ }
+
+ try {
+ mBluetoothLock.readLock().lock();
+ mEnableExternal = false;
+ if (mBluetooth != null) {
+ if(mBluetooth.getState() == BluetoothAdapter.STATE_BLE_ON) {
+ mEnable = false;
+ mBluetooth.onBrEdrDown(attributionSource);
+ } else {
+ sendDisableMsg(BluetoothProtoEnums.ENABLE_DISABLE_REASON_SYSTEM_BOOT,
+ packageName);
+ }
+ }
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Unable to initiate disable", e);
+ } finally {
+ mBluetoothLock.readLock().unlock();
+ }
+ }
+ }
+ return true;
+ }
+
+ private boolean startConsentUiIfNeeded(String packageName,
+ int callingUid, String intentAction) throws RemoteException {
+ if (checkBluetoothPermissionWhenWirelessConsentRequired()) {
+ return false;
+ }
+ try {
+ // Validate the package only if we are going to use it
+ ApplicationInfo applicationInfo = mContext.getPackageManager()
+ .getApplicationInfoAsUser(packageName,
+ PackageManager.MATCH_DEBUG_TRIAGED_MISSING,
+ UserHandle.getUserId(callingUid));
+ if (applicationInfo.uid != callingUid) {
+ throw new SecurityException("Package " + packageName
+ + " not in uid " + callingUid);
+ }
+
+ Intent intent = new Intent(intentAction);
+ intent.putExtra(Intent.EXTRA_PACKAGE_NAME, packageName);
+ intent.setFlags(
+ Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
+ try {
+ mContext.startActivity(intent);
+ } catch (ActivityNotFoundException e) {
+ // Shouldn't happen
+ Slog.e(TAG, "Intent to handle action " + intentAction + " missing");
+ return false;
+ }
+ return true;
+ } catch (PackageManager.NameNotFoundException e) {
+ throw new RemoteException(e.getMessage());
+ }
+ }
+
+ /**
+ * Check if AppOpsManager is available and the packageName belongs to uid
+ *
+ * A null package belongs to any uid
+ */
+ private void checkPackage(int uid, String packageName) {
+ if (mAppOps == null) {
+ Slog.w(TAG, "checkPackage(): called before system boot up, uid "
+ + uid + ", packageName " + packageName);
+ throw new IllegalStateException("System has not boot yet");
+ }
+ if (packageName == null) {
+ Slog.w(TAG, "checkPackage(): called with null packageName from " + uid);
+ return;
+ }
+ try {
+ mAppOps.checkPackage(uid, packageName);
+ } catch (SecurityException e) {
+ Slog.w(TAG, "checkPackage(): " + packageName + " does not belong to uid " + uid);
+ throw new SecurityException(e.getMessage());
+ }
+ }
+
+ /**
+ * Check if the caller must still pass permission check or if the caller is exempted
+ * from the consent UI via the MANAGE_BLUETOOTH_WHEN_WIRELESS_CONSENT_REQUIRED check.
+ *
+ * Commands from some callers may be exempted from triggering the consent UI when
+ * enabling bluetooth. This exemption is checked via the
+ * MANAGE_BLUETOOTH_WHEN_WIRELESS_CONSENT_REQUIRED and allows calls to skip
+ * the consent UI where it may otherwise be required.
+ *
+ * @hide
+ */
+ @SuppressLint("AndroidFrameworkRequiresPermission")
+ private boolean checkBluetoothPermissionWhenWirelessConsentRequired() {
+ int result = mContext.checkCallingPermission(
+ android.Manifest.permission.MANAGE_BLUETOOTH_WHEN_WIRELESS_CONSENT_REQUIRED);
+ return result == PackageManager.PERMISSION_GRANTED;
+ }
+
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED)
+ public void unbindAndFinish() {
+ if (DBG) {
+ Slog.d(TAG, "unbindAndFinish(): " + mBluetooth + " mBinding = " + mBinding
+ + " mUnbinding = " + mUnbinding);
+ }
+
+ try {
+ mBluetoothLock.writeLock().lock();
+ if (mUnbinding) {
+ return;
+ }
+ mUnbinding = true;
+ mHandler.removeMessages(MESSAGE_BLUETOOTH_STATE_CHANGE);
+ mHandler.removeMessages(MESSAGE_BIND_PROFILE_SERVICE);
+ if (mBluetooth != null) {
+ //Unregister callback object
+ try {
+ mBluetooth.unregisterCallback(mBluetoothCallback,
+ mContext.getAttributionSource());
+ } catch (RemoteException re) {
+ Slog.e(TAG, "Unable to unregister BluetoothCallback", re);
+ }
+ mBluetoothBinder = null;
+ mBluetooth = null;
+ mContext.unbindService(mConnection);
+ mUnbinding = false;
+ mBinding = false;
+ mTryBindOnBindTimeout = false;
+ } else {
+ mUnbinding = false;
+ }
+ mBluetoothGatt = null;
+ } finally {
+ mBluetoothLock.writeLock().unlock();
+ }
+ }
+
+ public IBluetoothGatt getBluetoothGatt() {
+ // sync protection
+ return mBluetoothGatt;
+ }
+
+ public boolean isBluetoothAvailableForBinding() {
+ try {
+ mBluetoothLock.readLock().lock();
+ if (mBluetooth != null && ((mBluetooth.getState() == BluetoothAdapter.STATE_ON) ||
+ (mBluetooth.getState() == BluetoothAdapter.STATE_TURNING_ON))) {
+ return true;
+ } else {
+ return false;
+ }
+ } catch (RemoteException e) {
+ Slog.e(TAG, "getState()", e);
+ } finally {
+ mBluetoothLock.readLock().unlock();
+ }
+ return false;
+ }
+
+ @Override
+ public boolean bindBluetoothProfileService(int bluetoothProfile,
+ IBluetoothProfileServiceConnection proxy) {
+ if (isBluetoothAvailableForBinding() == false) {
+ Slog.w(TAG, "bindBluetoothProfileService:Trying to bind to profile: "
+ + bluetoothProfile + ", while Bluetooth is disabled");
+ return false;
+ }
+ synchronized (mProfileServices) {
+ ProfileServiceConnections psc = mProfileServices.get(new Integer(bluetoothProfile));
+ if (psc == null) {
+ if (DBG) {
+ Slog.d(TAG, "Creating new ProfileServiceConnections object for" + " profile: "
+ + bluetoothProfile);
+ }
+
+ Intent intent;
+ if (bluetoothProfile == BluetoothProfile.HEADSET) {
+ intent = new Intent(IBluetoothHeadset.class.getName());
+ } else if (bluetoothProfile== BluetoothProfile.LE_CALL_CONTROL) {
+ intent = new Intent(IBluetoothLeCallControl.class.getName());
+ } else {
+ return false;
+ }
+
+ psc = new ProfileServiceConnections(intent);
+ if (!psc.bindService()) {
+ return false;
+ }
+
+ mProfileServices.put(new Integer(bluetoothProfile), psc);
+ }
+ else
+ Slog.w(TAG, "psc is not null in bindBluetoothProfileService");
+ }
+
+ // Introducing a delay to give the client app time to prepare
+ Message addProxyMsg = mHandler.obtainMessage(MESSAGE_ADD_PROXY_DELAYED);
+ addProxyMsg.arg1 = bluetoothProfile;
+ addProxyMsg.obj = proxy;
+ mHandler.sendMessageDelayed(addProxyMsg, ADD_PROXY_DELAY_MS);
+ return true;
+ }
+
+ @Override
+ public void unbindBluetoothProfileService(int bluetoothProfile,
+ IBluetoothProfileServiceConnection proxy) {
+ synchronized (mProfileServices) {
+ Integer profile = new Integer(bluetoothProfile);
+ ProfileServiceConnections psc = mProfileServices.get(profile);
+ if (psc == null) {
+ Slog.e(TAG, "unbindBluetoothProfileService: psc is null, returning");
+ return;
+ }
+ Slog.w(TAG, "unbindBluetoothProfileService: calling psc.removeProxy");
+ psc.removeProxy(proxy);
+ if (psc.isEmpty()) {
+ // All proxies are disconnected, unbind with the service.
+ try {
+ mContext.unbindService(psc);
+ } catch (IllegalArgumentException e) {
+ Slog.e(TAG, "Unable to unbind service with intent: " + psc.mIntent, e);
+ }
+ Slog.w(TAG, "psc.isEmpty is true, removing psc entry for profile "
+ + profile);
+ mProfileServices.remove(profile);
+ }
+ }
+ }
+
+ private void unbindAllBluetoothProfileServices() {
+ synchronized (mProfileServices) {
+ for (Integer i : mProfileServices.keySet()) {
+ ProfileServiceConnections psc = mProfileServices.get(i);
+ try {
+ mContext.unbindService(psc);
+ } catch (IllegalArgumentException e) {
+ Slog.e(TAG, "Unable to unbind service with intent: " + psc.mIntent, e);
+ }
+ psc.removeAllProxies();
+ }
+ mProfileServices.clear();
+ }
+ }
+
+ /**
+ * Send enable message and set adapter name and address. Called when the boot phase becomes
+ * PHASE_SYSTEM_SERVICES_READY.
+ */
+ public void handleOnBootPhase() {
+ if (DBG) {
+ Slog.d(TAG, "Bluetooth boot completed");
+ }
+ mAppOps = mContext.getSystemService(AppOpsManager.class);
+ UserManagerInternal userManagerInternal =
+ LocalServices.getService(UserManagerInternal.class);
+ userManagerInternal.addUserRestrictionsListener(mUserRestrictionsListener);
+ final boolean isBluetoothDisallowed = isBluetoothDisallowed();
+ if (isBluetoothDisallowed) {
+ return;
+ }
+ final boolean isSafeMode = mContext.getPackageManager().isSafeMode();
+ if (mEnableExternal && isBluetoothPersistedStateOnBluetooth() && !isSafeMode) {
+ if (DBG) {
+ Slog.d(TAG, "Auto-enabling Bluetooth.");
+ }
+ sendEnableMsg(mQuietEnableExternal,
+ BluetoothProtoEnums.ENABLE_DISABLE_REASON_SYSTEM_BOOT,
+ mContext.getPackageName());
+ } else if (!isNameAndAddressSet()) {
+ if (DBG) {
+ Slog.d(TAG, "Getting adapter name and address");
+ }
+ Message getMsg = mHandler.obtainMessage(MESSAGE_GET_NAME_AND_ADDRESS);
+ mHandler.sendMessage(getMsg);
+ }
+
+ mBluetoothModeChangeHelper = new BluetoothModeChangeHelper(mContext);
+ if (mBluetoothAirplaneModeListener != null) {
+ mBluetoothAirplaneModeListener.start(mBluetoothModeChangeHelper);
+ }
+ registerForProvisioningStateChange();
+ mBluetoothDeviceConfigListener = new BluetoothDeviceConfigListener(this, DBG);
+ }
+
+ /**
+ * Called when switching to a different foreground user.
+ */
+ public void handleOnSwitchUser(int userHandle) {
+ if (DBG) {
+ Slog.d(TAG, "User " + userHandle + " switched");
+ }
+ mHandler.obtainMessage(MESSAGE_USER_SWITCHED, userHandle, 0).sendToTarget();
+ }
+
+ /**
+ * Called when user is unlocked.
+ */
+ public void handleOnUnlockUser(int userHandle) {
+ if (DBG) {
+ Slog.d(TAG, "User " + userHandle + " unlocked");
+ }
+ mHandler.obtainMessage(MESSAGE_USER_UNLOCKED, userHandle, 0).sendToTarget();
+ }
+
+ /**
+ * This class manages the clients connected to a given ProfileService
+ * and maintains the connection with that service.
+ */
+ private final class ProfileServiceConnections
+ implements ServiceConnection, IBinder.DeathRecipient {
+ final RemoteCallbackList<IBluetoothProfileServiceConnection> mProxies =
+ new RemoteCallbackList<IBluetoothProfileServiceConnection>();
+ IBinder mService;
+ ComponentName mClassName;
+ Intent mIntent;
+ boolean mInvokingProxyCallbacks = false;
+
+ ProfileServiceConnections(Intent intent) {
+ mService = null;
+ mClassName = null;
+ mIntent = intent;
+ }
+
+ private boolean bindService() {
+ int state = BluetoothAdapter.STATE_OFF;
+ try {
+ mBluetoothLock.readLock().lock();
+ if (mBluetooth != null) {
+ state = mBluetooth.getState();
+ }
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Unable to call getState", e);
+ return false;
+ } finally {
+ mBluetoothLock.readLock().unlock();
+ }
+
+ if (state != BluetoothAdapter.STATE_ON) {
+ if (DBG) {
+ Slog.d(TAG, "Unable to bindService while Bluetooth is disabled");
+ }
+ return false;
+ }
+
+ if (mIntent != null && mService == null && doBind(mIntent, this, 0,
+ UserHandle.CURRENT_OR_SELF)) {
+ Message msg = mHandler.obtainMessage(MESSAGE_BIND_PROFILE_SERVICE);
+ msg.obj = this;
+ mHandler.sendMessageDelayed(msg, TIMEOUT_BIND_MS);
+ return true;
+ }
+ Slog.w(TAG, "Unable to bind with intent: " + mIntent);
+ return false;
+ }
+
+ private void addProxy(IBluetoothProfileServiceConnection proxy) {
+ mProxies.register(proxy);
+ if (mService != null) {
+ try {
+ proxy.onServiceConnected(mClassName, mService);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Unable to connect to proxy", e);
+ }
+ } else {
+ if (isBluetoothAvailableForBinding() == false) {
+ Slog.w(TAG, "addProxy: Trying to bind to profile: " + mClassName +
+ ", while Bluetooth is disabled");
+ mProxies.unregister(proxy);
+ return;
+ }
+
+ if (!mHandler.hasMessages(MESSAGE_BIND_PROFILE_SERVICE, this)) {
+ Message msg = mHandler.obtainMessage(MESSAGE_BIND_PROFILE_SERVICE);
+ msg.obj = this;
+ mHandler.sendMessage(msg);
+ }
+ }
+ }
+
+ private void removeProxy(IBluetoothProfileServiceConnection proxy) {
+ if (proxy != null) {
+ if (mProxies.unregister(proxy)) {
+ try {
+ proxy.onServiceDisconnected(mClassName);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Unable to disconnect proxy", e);
+ }
+ }
+
+ Slog.w(TAG, "removing the proxy, count is "
+ + mProxies.getRegisteredCallbackCount());
+ } else {
+ Slog.w(TAG, "Trying to remove a null proxy");
+ }
+ }
+
+ private void removeAllProxies() {
+ onServiceDisconnected(mClassName);
+ mProxies.kill();
+ }
+
+ private boolean isEmpty() {
+ return (mProxies != null && mProxies.getRegisteredCallbackCount() == 0);
+ }
+
+ @Override
+ public void onServiceConnected(ComponentName className, IBinder service) {
+ // remove timeout message
+ mHandler.removeMessages(MESSAGE_BIND_PROFILE_SERVICE, this);
+ mService = service;
+ mClassName = className;
+ try {
+ mService.linkToDeath(this, 0);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Unable to linkToDeath", e);
+ }
+
+ if (mInvokingProxyCallbacks) {
+ Slog.e(TAG, "Proxy callbacks already in progress.");
+ return;
+ }
+ mInvokingProxyCallbacks = true;
+
+ final int n = mProxies.beginBroadcast();
+ try {
+ for (int i = 0; i < n; i++) {
+ try {
+ mProxies.getBroadcastItem(i).onServiceConnected(className, service);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Unable to connect to proxy", e);
+ }
+ }
+ } finally {
+ mProxies.finishBroadcast();
+ mInvokingProxyCallbacks = false;
+ }
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName className) {
+ if (mService == null) return;
+ try {
+ mService.unlinkToDeath(this, 0);
+ } catch (NoSuchElementException e) {
+ Slog.e(TAG, "Unable to unlinkToDeath", e);
+ }
+ mService = null;
+ mClassName = null;
+
+ if (mInvokingProxyCallbacks) {
+ Slog.e(TAG, "Proxy callbacks already in progress.");
+ return;
+ }
+ mInvokingProxyCallbacks = true;
+
+ final int n = mProxies.beginBroadcast();
+ try {
+ for (int i = 0; i < n; i++) {
+ try {
+ mProxies.getBroadcastItem(i).onServiceDisconnected(className);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Unable to disconnect from proxy", e);
+ }
+ }
+ } finally {
+ mProxies.finishBroadcast();
+ mInvokingProxyCallbacks = false;
+ }
+ }
+
+ @Override
+ public void binderDied() {
+ if (DBG) {
+ Slog.w(TAG, "Profile service for profile: " + mClassName + " died.");
+ }
+ onServiceDisconnected(mClassName);
+
+ if (isBluetoothAvailableForBinding() == false) {
+ Slog.w(TAG, "binderDied: Trying to bind to profile: " + mClassName +
+ ", while Bluetooth is disabled");
+ return;
+ }
+ // Trigger rebind
+ Message msg = mHandler.obtainMessage(MESSAGE_BIND_PROFILE_SERVICE);
+ msg.obj = this;
+ mHandler.sendMessageDelayed(msg, TIMEOUT_BIND_MS);
+ }
+ }
+
+ private void sendBluetoothStateCallback(boolean isUp) {
+ try {
+ int n = mStateChangeCallbacks.beginBroadcast();
+ if (DBG) {
+ Slog.d(TAG, "Broadcasting onBluetoothStateChange(" + isUp + ") to " + n
+ + " receivers.");
+ }
+ for (int i = 0; i < n; i++) {
+ try {
+ mStateChangeCallbacks.getBroadcastItem(i).onBluetoothStateChange(isUp);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Unable to call onBluetoothStateChange() on callback #" + i, e);
+ }
+ }
+ } finally {
+ mStateChangeCallbacks.finishBroadcast();
+ }
+ }
+
+ /**
+ * Inform BluetoothAdapter instances that Adapter service is up
+ */
+ private void sendBluetoothServiceUpCallback() {
+ synchronized (mCallbacks) {
+ try {
+ mBluetoothLock.writeLock().lock();
+ int n = mCallbacks.beginBroadcast();
+ Slog.d(TAG, "Broadcasting onBluetoothServiceUp() to " + n + " receivers.");
+ for (int i = 0; i < n; i++) {
+ try {
+ mCallbacks.getBroadcastItem(i).onBluetoothServiceUp(mBluetooth);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Unable to call onBluetoothServiceUp() on callback #" + i, e);
+ }
+ }
+ } finally {
+ mCallbacks.finishBroadcast();
+ mBluetoothLock.writeLock().unlock();
+ }
+ }
+ }
+
+ /**
+ * Inform BluetoothAdapter instances that Adapter service is down
+ */
+ private void sendBluetoothServiceDownCallback() {
+ synchronized (mCallbacks) {
+ try {
+ int n = mCallbacks.beginBroadcast();
+ Slog.d(TAG, "Broadcasting onBluetoothServiceDown() to " + n + " receivers.");
+ for (int i = 0; i < n; i++) {
+ try {
+ mCallbacks.getBroadcastItem(i).onBluetoothServiceDown();
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Unable to call onBluetoothServiceDown() on callback #" + i, e);
+ }
+ }
+ } finally {
+ mCallbacks.finishBroadcast();
+ }
+ }
+ }
+
+ public String getAddress(AttributionSource attributionSource) {
+ if (!checkConnectPermissionForDataDelivery(mContext, attributionSource, "getAddress")) {
+ return null;
+ }
+
+ if ((Binder.getCallingUid() != Process.SYSTEM_UID) && (!checkIfCallerIsForegroundUser())) {
+ Slog.w(TAG, "getAddress(): not allowed for non-active and non system user");
+ return null;
+ }
+
+ if (mContext.checkCallingOrSelfPermission(Manifest.permission.LOCAL_MAC_ADDRESS)
+ != PackageManager.PERMISSION_GRANTED) {
+ return BluetoothAdapter.DEFAULT_MAC_ADDRESS;
+ }
+
+ try {
+ mBluetoothLock.readLock().lock();
+ if (mBluetooth != null) {
+ return mBluetooth.getAddressWithAttribution(attributionSource);
+ }
+ } catch (RemoteException e) {
+ Slog.e(TAG,
+ "getAddress(): Unable to retrieve address remotely. Returning cached address",
+ e);
+ } finally {
+ mBluetoothLock.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;
+ }
+
+ public String getName(AttributionSource attributionSource) {
+ if (!checkConnectPermissionForDataDelivery(mContext, attributionSource, "getName")) {
+ return null;
+ }
+
+ if ((Binder.getCallingUid() != Process.SYSTEM_UID) && (!checkIfCallerIsForegroundUser())) {
+ Slog.w(TAG, "getName(): not allowed for non-active and non system user");
+ return null;
+ }
+
+ try {
+ mBluetoothLock.readLock().lock();
+ if (mBluetooth != null) {
+ return mBluetooth.getName(attributionSource);
+ }
+ } catch (RemoteException e) {
+ Slog.e(TAG, "getName(): Unable to retrieve name remotely. Returning cached name", e);
+ } finally {
+ mBluetoothLock.readLock().unlock();
+ }
+
+ // mName is accessed from outside.
+ // It alright without a lock. Here, bluetooth is off, no other thread is
+ // changing mName
+ return mName;
+ }
+
+ public boolean factoryReset() {
+ final int callingUid = Binder.getCallingUid();
+ final boolean callerSystem = UserHandle.getAppId(callingUid) == Process.SYSTEM_UID;
+
+ if (!callerSystem) {
+ if (!checkIfCallerIsForegroundUser()) {
+ Slog.w(TAG, "factoryReset(): not allowed for non-active and non system user");
+ return false;
+ }
+
+ mContext.enforceCallingOrSelfPermission(
+ BLUETOOTH_PRIVILEGED, "Need BLUETOOTH PRIVILEGED permission");
+ }
+ persistBluetoothSetting(BLUETOOTH_ON_BLUETOOTH);
+
+ /* Wait for stable state if bluetooth is temporary state. */
+ int state = getState();
+ if (state == BluetoothAdapter.STATE_BLE_TURNING_ON
+ || state == BluetoothAdapter.STATE_TURNING_ON
+ || state == BluetoothAdapter.STATE_TURNING_OFF) {
+ if (!waitForState(Set.of(BluetoothAdapter.STATE_BLE_ON, BluetoothAdapter.STATE_ON))) {
+ return false;
+ }
+ }
+
+ // Clear registered LE apps to force shut-off
+ clearBleApps();
+ try {
+ mBluetoothLock.writeLock().lock();
+ if (mBluetooth == null) {
+ mEnable = true;
+ handleEnable(mQuietEnable);
+ } else if (state == BluetoothAdapter.STATE_OFF) {
+ mEnable = true;
+ mBluetooth.factoryReset(mContext.getAttributionSource());
+ handleEnable(mQuietEnable);
+ } else if (state == BluetoothAdapter.STATE_BLE_ON) {
+ addActiveLog(
+ BluetoothProtoEnums.ENABLE_DISABLE_REASON_FACTORY_RESET,
+ mContext.getPackageName(), false);
+ mBluetooth.onBrEdrDown(mContext.getAttributionSource());
+ return mBluetooth.factoryReset(mContext.getAttributionSource());
+ } else if (state == BluetoothAdapter.STATE_ON) {
+ addActiveLog(
+ BluetoothProtoEnums.ENABLE_DISABLE_REASON_FACTORY_RESET,
+ mContext.getPackageName(), false);
+ mBluetooth.disable(mContext.getAttributionSource());
+ return mBluetooth.factoryReset(mContext.getAttributionSource());
+ }
+ } catch (RemoteException e) {
+ Slog.e(TAG, "factoryReset(): Unable to do factoryReset.", e);
+ return false;
+ } finally {
+ mBluetoothLock.writeLock().unlock();
+ }
+ return true;
+ }
+
+ private class BluetoothServiceConnection implements ServiceConnection {
+ public void onServiceConnected(ComponentName componentName, IBinder service) {
+ String name = componentName.getClassName();
+ if (DBG) {
+ Slog.d(TAG, "BluetoothServiceConnection: " + name);
+ }
+ Message msg = mHandler.obtainMessage(MESSAGE_BLUETOOTH_SERVICE_CONNECTED);
+ if (name.equals("com.android.bluetooth.btservice.AdapterService")) {
+ msg.arg1 = SERVICE_IBLUETOOTH;
+ mHandler.removeMessages(MESSAGE_TIMEOUT_BIND);
+ } else if (name.equals("com.android.bluetooth.gatt.GattService")) {
+ msg.arg1 = SERVICE_IBLUETOOTHGATT;
+ } else {
+ Slog.e(TAG, "Unknown service connected: " + name);
+ return;
+ }
+ msg.obj = service;
+ mHandler.sendMessage(msg);
+ }
+
+ public void onServiceDisconnected(ComponentName componentName) {
+ // Called if we unexpectedly disconnect.
+ String name = componentName.getClassName();
+ if (DBG) {
+ Slog.d(TAG, "BluetoothServiceConnection, disconnected: " + name);
+ }
+ Message msg = mHandler.obtainMessage(MESSAGE_BLUETOOTH_SERVICE_DISCONNECTED);
+ if (name.equals("com.android.bluetooth.btservice.AdapterService")) {
+ msg.arg1 = SERVICE_IBLUETOOTH;
+ } else if (name.equals("com.android.bluetooth.gatt.GattService")) {
+ msg.arg1 = SERVICE_IBLUETOOTHGATT;
+ } else {
+ Slog.e(TAG, "Unknown service disconnected: " + name);
+ return;
+ }
+ mHandler.sendMessage(msg);
+ }
+ }
+
+ private BluetoothServiceConnection mConnection = new BluetoothServiceConnection();
+
+ private class BluetoothHandler extends Handler {
+ boolean mGetNameAddressOnly = false;
+ private int mWaitForEnableRetry;
+ private int mWaitForDisableRetry;
+
+ BluetoothHandler(Looper looper) {
+ super(looper);
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MESSAGE_GET_NAME_AND_ADDRESS:
+ if (DBG) {
+ Slog.d(TAG, "MESSAGE_GET_NAME_AND_ADDRESS");
+ }
+ try {
+ mBluetoothLock.writeLock().lock();
+ if ((mBluetooth == null) && (!mBinding)) {
+ if (DBG) {
+ Slog.d(TAG, "Binding to service to get name and address");
+ }
+ mGetNameAddressOnly = true;
+ Message timeoutMsg = mHandler.obtainMessage(MESSAGE_TIMEOUT_BIND);
+ mHandler.sendMessageDelayed(timeoutMsg, 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 {
+ mBinding = true;
+ mBindingUserID = ActivityManager.getCurrentUser();
+ Slog.d(TAG, "Binding BT service. Current user: " + mBindingUserID);
+ }
+ } else if (mBluetooth != null) {
+ try {
+ storeNameAndAddress(
+ mBluetooth.getName(mContext.getAttributionSource()),
+ mBluetooth.getAddressWithAttribution(
+ mContext.getAttributionSource()));
+ } catch (RemoteException re) {
+ Slog.e(TAG, "Unable to grab names", re);
+ }
+ if (mGetNameAddressOnly && !mEnable) {
+ unbindAndFinish();
+ }
+ mGetNameAddressOnly = false;
+ }
+ } finally {
+ mBluetoothLock.writeLock().unlock();
+ }
+ break;
+
+ case MESSAGE_ENABLE:
+ int quietEnable = msg.arg1;
+ int isBle = msg.arg2;
+ if (mHandler.hasMessages(MESSAGE_HANDLE_DISABLE_DELAYED)
+ || mHandler.hasMessages(MESSAGE_HANDLE_ENABLE_DELAYED)) {
+ if (msg.arg2 == 0) {
+ int delay = ENABLE_DISABLE_DELAY_MS;
+
+ if (mHandler.hasMessages(MESSAGE_DISABLE)) {
+ delay = ENABLE_DISABLE_DELAY_MS * 2;
+ }
+ // Keep only one MESSAGE_ENABLE and ensure it is the last one
+ // to be taken out of the queue
+ mHandler.removeMessages(MESSAGE_ENABLE);
+ // We are handling enable or disable right now, wait for it.
+ mHandler.sendMessageDelayed(mHandler.obtainMessage(
+ MESSAGE_ENABLE, quietEnable, 1), delay);
+ Slog.d(TAG, "Queue new MESSAGE_ENABLE");
+ } else {
+ mHandler.sendMessageDelayed(mHandler.obtainMessage(
+ MESSAGE_ENABLE, quietEnable, isBle), ENABLE_DISABLE_DELAY_MS);
+ Slog.d(TAG, "Re-Queue previous MESSAGE_ENABLE");
+ if (mHandler.hasMessages(MESSAGE_DISABLE)) {
+ // Ensure the original order of just entering the queue
+ // if MESSAGE_DISABLE present
+ mHandler.removeMessages(MESSAGE_DISABLE);
+ mHandler.sendMessageDelayed(mHandler.obtainMessage(
+ MESSAGE_DISABLE, 0, isBle), ENABLE_DISABLE_DELAY_MS * 2);
+ Slog.d(TAG, "Re-Queue previous MESSAGE_DISABLE");
+ }
+ }
+ break;
+ } else if(msg.arg2 == 0 && mHandler.hasMessages(MESSAGE_DISABLE)) {
+ mHandler.removeMessages(MESSAGE_ENABLE);
+ mHandler.sendMessageDelayed(mHandler.obtainMessage(
+ MESSAGE_ENABLE, quietEnable, isBle), ENABLE_DISABLE_DELAY_MS * 2);
+ Slog.d(TAG, "MESSAGE_DISABLE exist. Queue new MESSAGE_ENABLE");
+ break;
+ }
+
+ if (DBG) {
+ Slog.d(TAG, "MESSAGE_ENABLE(" + quietEnable + "): mBluetooth = "
+ + mBluetooth);
+ }
+ mHandler.removeMessages(MESSAGE_RESTART_BLUETOOTH_SERVICE);
+ mEnable = true;
+
+ if (isBle == 0) {
+ persistBluetoothSetting(BLUETOOTH_ON_BLUETOOTH);
+ }
+
+ mQuietEnable = (quietEnable == 1);
+ // Use service interface to get the exact state
+ try {
+ mBluetoothLock.readLock().lock();
+ if (mBluetooth != null) {
+ boolean isHandled = true;
+ int state = mBluetooth.getState();
+ switch (state) {
+ case BluetoothAdapter.STATE_BLE_ON:
+ if (isBle == 1) {
+ Slog.i(TAG, "Already at BLE_ON State");
+ } else if (isBluetoothPersistedStateOnBluetooth() ||
+ mEnableExternal) {
+ Slog.w(TAG, "BLE_ON State:Enable from Settings or" +
+ "BT on persisted, going to ON");
+ mBluetooth.updateQuietModeStatus(mQuietEnable,
+ mContext.getAttributionSource());
+ mBluetooth.onLeServiceUp(mContext.getAttributionSource());
+ persistBluetoothSetting(BLUETOOTH_ON_BLUETOOTH);
+
+ // waive WRITE_SECURE_SETTINGS permission check
+ long callingIdentity = Binder.clearCallingIdentity();
+ Binder.restoreCallingIdentity(callingIdentity);
+ } else {
+ Slog.w(TAG, "BLE_ON State:Queued enable from ble app," +
+ " stay in ble on");
+ }
+ break;
+ case BluetoothAdapter.STATE_BLE_TURNING_ON:
+ case BluetoothAdapter.STATE_TURNING_ON:
+ case BluetoothAdapter.STATE_ON:
+ Slog.i(TAG, "MESSAGE_ENABLE: already enabled");
+ break;
+ default:
+ isHandled = false;
+ break;
+ }
+ if (isHandled) break;
+ }
+ } catch (RemoteException e) {
+ Slog.e(TAG, "", e);
+ } finally {
+ mBluetoothLock.readLock().unlock();
+ }
+
+ if (mBluetooth == null) {
+ Slog.d(TAG, "MESSAGE_ENABLE: handleEnable");
+ 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;
+ Slog.d(TAG, "Re-Queue MESSAGE_HANDLE_ENABLE_DELAYED");
+ Message enableDelayedMsg =
+ mHandler.obtainMessage(MESSAGE_HANDLE_ENABLE_DELAYED);
+ mHandler.sendMessageDelayed(enableDelayedMsg, ENABLE_DISABLE_DELAY_MS);
+ }
+ break;
+
+ case MESSAGE_DISABLE:
+ if (mHandler.hasMessages(MESSAGE_HANDLE_DISABLE_DELAYED) || mBinding
+ || mHandler.hasMessages(MESSAGE_HANDLE_ENABLE_DELAYED)) {
+ if (msg.arg2 == 0) {
+ int delay = ENABLE_DISABLE_DELAY_MS;
+
+ if (mHandler.hasMessages(MESSAGE_ENABLE)) {
+ delay = ENABLE_DISABLE_DELAY_MS * 2;
+ }
+ // Keep only one MESSAGE_DISABLE and ensure it is the last one
+ // to be taken out of the queue
+ mHandler.removeMessages(MESSAGE_DISABLE);
+ // We are handling enable or disable right now, wait for it.
+ mHandler.sendMessageDelayed(mHandler.obtainMessage(
+ MESSAGE_DISABLE, 0, 1), delay);
+ Slog.d(TAG, "Queue new MESSAGE_DISABLE");
+ } else {
+ mHandler.sendMessageDelayed(mHandler.obtainMessage(
+ MESSAGE_DISABLE, 0, 1), ENABLE_DISABLE_DELAY_MS);
+ Slog.d(TAG, "Re-Queue previous MESSAGE_DISABLE");
+ if (mHandler.hasMessages(MESSAGE_ENABLE)) {
+ // Ensure the original order of just entering the queue
+ // if MESSAGE_DISABLE present
+ mHandler.removeMessages(MESSAGE_ENABLE);
+ mHandler.sendMessageDelayed(mHandler.obtainMessage(
+ MESSAGE_ENABLE, mQuietEnableExternal ? 1: 0, 1),
+ ENABLE_DISABLE_DELAY_MS * 2);
+ Slog.d(TAG, "Re-Queue previous MESSAGE_ENABLE");
+ }
+ }
+ break;
+ } else if(msg.arg2 == 0 && mHandler.hasMessages(MESSAGE_ENABLE)) {
+ mHandler.removeMessages(MESSAGE_DISABLE);
+ mHandler.sendMessageDelayed(mHandler.obtainMessage(
+ MESSAGE_DISABLE, 0, 1), ENABLE_DISABLE_DELAY_MS * 2);
+ Slog.d(TAG, "MESSAGE_ENABLE exist. Queue new MESSAGE_DISABLE");
+ break;
+ }
+
+ if (DBG) {
+ Slog.d(TAG, "MESSAGE_DISABLE: mBluetooth = " + mBluetooth
+ + ", mBinding = " + mBinding + " mEnable = " + mEnable);
+ }
+ mHandler.removeMessages(MESSAGE_RESTART_BLUETOOTH_SERVICE);
+
+ if (mEnable && mBluetooth != null) {
+ mWaitForDisableRetry = 0;
+ Message disableDelayedMsg =
+ mHandler.obtainMessage(MESSAGE_HANDLE_DISABLE_DELAYED, 0, 0);
+ mHandler.sendMessageDelayed(disableDelayedMsg, ENABLE_DISABLE_DELAY_MS);
+ Slog.d(TAG, "Re-Queue MESSAGE_HANDLE_DISABLE_DELAYED(0)");
+ } else {
+ mEnable = false;
+ handleDisable();
+ Slog.d(TAG, "MESSAGE_DISABLE: handleDisable");
+ }
+ break;
+
+ case MESSAGE_HANDLE_ENABLE_DELAYED: {
+ /* The Bluetooth is turning off, wait for STATE_OFF then restart bluetooth
+ * if ble app running, then wait for BLE ON and continue bt turn on
+ */
+ Slog.d(TAG, "MESSAGE_HANDLE_ENABLE_DELAYED, mState=" +
+ BluetoothAdapter.nameForState(mState) + " mEnableExternal = "
+ + mEnableExternal + " getServiceRestartMs()="
+ + getServiceRestartMs());
+ if ((mState == BluetoothAdapter.STATE_BLE_ON) && (isBleAppPresent() ||
+ mWaitForEnableRetry > 0)) {
+ Slog.d(TAG, "isBleAppPresent(): " + isBleAppPresent() +
+ " mWaitForEnableRetry=" + mWaitForEnableRetry);
+ mWaitForEnableRetry = 0;
+ if (mEnableExternal || isBluetoothPersistedStateOnBluetooth()) {
+ try {
+ mBluetoothLock.readLock().lock();
+ if (mBluetooth != null) {
+ mBluetooth.updateQuietModeStatus(mQuietEnable,
+ mContext.getAttributionSource());
+ mBluetooth.onLeServiceUp(mContext.getAttributionSource());
+ }
+ } catch (RemoteException e) {
+ Slog.e(TAG, "", e);
+ } finally {
+ mBluetoothLock.readLock().unlock();
+ }
+ } else {
+ Slog.e(TAG, "BLE app running stay in BLE ON state");
+ }
+ break;
+ } else if (mState != BluetoothAdapter.STATE_OFF) {
+ if (mWaitForEnableRetry < MAX_WAIT_FOR_ENABLE_DISABLE_RETRIES) {
+ mWaitForEnableRetry++;
+ Message enableDelayedMsg =
+ mHandler.obtainMessage(MESSAGE_HANDLE_ENABLE_DELAYED);
+ mHandler.sendMessageDelayed(enableDelayedMsg, ENABLE_DISABLE_DELAY_MS);
+ Slog.d(TAG, "Re-Queue MESSAGE_HANDLE_ENABLE_DELAYED");
+ break;
+ } else {
+ Slog.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;
+ Message restartMsg =
+ mHandler.obtainMessage(MESSAGE_RESTART_BLUETOOTH_SERVICE);
+ mHandler.sendMessageDelayed(restartMsg, getServiceRestartMs());
+ Slog.d(TAG, "Queue MESSAGE_RESTART_BLUETOOTH_SERVICE");
+ Slog.d(TAG, "Handle enable is finished");
+ break;
+ }
+
+ case MESSAGE_HANDLE_DISABLE_DELAYED: {
+ boolean disabling = (msg.arg1 == 1);
+ Slog.d(TAG, "MESSAGE_HANDLE_DISABLE_DELAYED: disabling:" + disabling);
+ if (!disabling) {
+ /* if bluetooth is in BLE ON state and enable is from ble app
+ * then skip disable, else wait for complete ON or timeout.
+ */
+ if ((mState == BluetoothAdapter.STATE_BLE_ON) &&
+ !mEnableExternal &&
+ !isBluetoothPersistedStateOnBluetooth() &&
+ isBleAppPresent()) {
+ Slog.w(TAG, "Enable from BLE APP, stay in BLE ON");
+ mWaitForDisableRetry = 0;
+ mEnable = false;
+ break;
+ } else if (mState != BluetoothAdapter.STATE_ON) {
+ if (mWaitForDisableRetry < MAX_WAIT_FOR_ENABLE_DISABLE_RETRIES) {
+ mWaitForDisableRetry++;
+ Message disableDelayedMsg = mHandler.obtainMessage(
+ MESSAGE_HANDLE_DISABLE_DELAYED, 0, 0);
+ mHandler.sendMessageDelayed(disableDelayedMsg,
+ ENABLE_DISABLE_DELAY_MS);
+ Slog.d(TAG, "Re-Queue MESSAGE_HANDLE_DISABLE_DELAYED(0)");
+ break;
+ } else {
+ Slog.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;
+ Slog.d(TAG, "MESSAGE_HANDLE_DISABLE_DELAYED: handleDisable");
+ handleDisable();
+ // Wait for state exiting STATE_ON
+ Message disableDelayedMsg =
+ mHandler.obtainMessage(MESSAGE_HANDLE_DISABLE_DELAYED, 1, 0);
+ mHandler.sendMessageDelayed(disableDelayedMsg, ENABLE_DISABLE_DELAY_MS);
+ Slog.d(TAG, "Re-Queue MESSAGE_HANDLE_DISABLE_DELAYED(1)");
+ } else {
+ // The Bluetooth is turning off, wait for exiting STATE_ON
+ if (mState == BluetoothAdapter.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);
+ Slog.d(TAG, "Re-Queue MESSAGE_HANDLE_DISABLE_DELAYED(1)");
+ break;
+ } else {
+ Slog.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.
+ Slog.d(TAG, "Handle disable is finished");
+ }
+ break;
+ }
+
+ case MESSAGE_RESTORE_USER_SETTING:
+ if (msg.arg1 == RESTORE_SETTING_TO_OFF) {
+ if (DBG) {
+ Slog.d(TAG, "Restore Bluetooth state to disabled");
+ }
+ persistBluetoothSetting(BLUETOOTH_OFF);
+ mEnableExternal = false;
+ clearBleApps();
+ try {
+ mBluetoothLock.readLock().lock();
+ mEnableExternal = false;
+ if (mBluetooth != null) {
+ if (mBluetooth.getState() == BluetoothAdapter.STATE_BLE_ON) {
+ mEnable = false;
+ mBluetooth.onBrEdrDown(mContext.getAttributionSource());
+ } else {
+ sendDisableMsg(
+ BluetoothProtoEnums.ENABLE_DISABLE_REASON_RESTORE_USER_SETTING,
+ mContext.getPackageName());
+ }
+ }
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Unable to initiate disable", e);
+ } finally {
+ mBluetoothLock.readLock().unlock();
+ }
+ } else if (msg.arg1 == RESTORE_SETTING_TO_ON) {
+ if (DBG) {
+ Slog.d(TAG, "Restore Bluetooth state to enabled");
+ }
+ mQuietEnableExternal = false;
+ mEnableExternal = true;
+ // waive WRITE_SECURE_SETTINGS permission check
+ sendEnableMsg(false,
+ BluetoothProtoEnums.ENABLE_DISABLE_REASON_RESTORE_USER_SETTING,
+ mContext.getPackageName());
+ }
+ break;
+ case MESSAGE_INFORM_ADAPTER_SERVICE_UP: {
+ if (DBG) Slog.d(TAG,"MESSAGE_INFORM_ADAPTER_SERVICE_UP");
+ sendBluetoothServiceUpCallback();
+ break;
+ }
+ case MESSAGE_REGISTER_STATE_CHANGE_CALLBACK: {
+ IBluetoothStateChangeCallback callback =
+ (IBluetoothStateChangeCallback) msg.obj;
+ mStateChangeCallbacks.register(callback);
+ break;
+ }
+ case MESSAGE_UNREGISTER_STATE_CHANGE_CALLBACK: {
+ IBluetoothStateChangeCallback callback =
+ (IBluetoothStateChangeCallback) msg.obj;
+ mStateChangeCallbacks.unregister(callback);
+ break;
+ }
+ case MESSAGE_ADD_PROXY_DELAYED: {
+ ProfileServiceConnections psc = mProfileServices.get(msg.arg1);
+ if (psc == null) {
+ break;
+ }
+ IBluetoothProfileServiceConnection proxy =
+ (IBluetoothProfileServiceConnection) msg.obj;
+ psc.addProxy(proxy);
+ break;
+ }
+ case MESSAGE_BIND_PROFILE_SERVICE: {
+ Slog.w(TAG, "MESSAGE_BIND_PROFILE_SERVICE");
+ ProfileServiceConnections psc = (ProfileServiceConnections) msg.obj;
+ removeMessages(MESSAGE_BIND_PROFILE_SERVICE, msg.obj);
+ if (psc == null) {
+ Slog.w(TAG, "psc is null, breaking");
+ break;
+ }
+ Slog.w(TAG, "Calling psc.bindService from MESSAGE_BIND_PROFILE_SERVICE");
+ psc.bindService();
+ break;
+ }
+ case MESSAGE_BLUETOOTH_SERVICE_CONNECTED: {
+ if (DBG) {
+ Slog.d(TAG, "MESSAGE_BLUETOOTH_SERVICE_CONNECTED: " + msg.arg1);
+ }
+
+ IBinder service = (IBinder) msg.obj;
+ try {
+ mBluetoothLock.writeLock().lock();
+ if (msg.arg1 == SERVICE_IBLUETOOTHGATT) {
+ mBluetoothGatt =
+ IBluetoothGatt.Stub.asInterface(Binder.allowBlocking(service));
+ continueFromBleOnState();
+ break;
+ } // else must be SERVICE_IBLUETOOTH
+
+ mBinding = false;
+ mTryBindOnBindTimeout = false;
+ mBluetoothBinder = service;
+ mBluetooth = IBluetooth.Stub.asInterface(Binder.allowBlocking(service));
+
+ if (!isNameAndAddressSet()) {
+ Message getMsg = mHandler.obtainMessage(MESSAGE_GET_NAME_AND_ADDRESS);
+ mHandler.sendMessage(getMsg);
+ if (mGetNameAddressOnly) {
+ return;
+ }
+ }
+
+ //Register callback object
+ try {
+ mBluetooth.registerCallback(mBluetoothCallback,
+ mContext.getAttributionSource());
+ } catch (RemoteException re) {
+ Slog.e(TAG, "Unable to register BluetoothCallback", re);
+ }
+ //Inform BluetoothAdapter instances that service is up
+ Message informMsg =
+ mHandler.obtainMessage(MESSAGE_INFORM_ADAPTER_SERVICE_UP);
+ mHandler.sendMessage(informMsg);
+
+ //Do enable request
+ try {
+ if (!mBluetooth.enable(mQuietEnable, mContext.getAttributionSource())) {
+ Slog.e(TAG, "IBluetooth.enable() returned false");
+ }
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Unable to call enable()", e);
+ }
+ } finally {
+ mBluetoothLock.writeLock().unlock();
+ }
+
+ if (!mEnable) {
+ /* Wait for BLE ON or ON state ,if enable is from BLE app
+ * skip disable, else wait for on state and handle disable
+ */
+ waitForState(Set.of(BluetoothAdapter.STATE_BLE_ON,
+ BluetoothAdapter.STATE_ON));
+
+ int st = getState();
+ if ((st == BluetoothAdapter.STATE_TURNING_ON) ||
+ ((st == BluetoothAdapter.STATE_BLE_ON) &&
+ (mEnableExternal || isBluetoothPersistedStateOnBluetooth()))) {
+ waitForState(Set.of(BluetoothAdapter.STATE_ON));
+ } else if ((st == BluetoothAdapter.STATE_BLE_ON) && isBleAppPresent()) {
+ Slog.e(TAG, "MESSAGE_BLUETOOTH_SERVICE_CONNECTED: ble app present");
+ break;
+ }
+ handleDisable();
+ waitForState(Set.of(BluetoothAdapter.STATE_OFF,
+ BluetoothAdapter.STATE_TURNING_ON,
+ BluetoothAdapter.STATE_TURNING_OFF,
+ BluetoothAdapter.STATE_BLE_TURNING_ON,
+ BluetoothAdapter.STATE_BLE_ON,
+ BluetoothAdapter.STATE_BLE_TURNING_OFF));
+ }
+ break;
+ }
+ case MESSAGE_BLUETOOTH_STATE_CHANGE: {
+ int prevState = msg.arg1;
+ int newState = msg.arg2;
+ if (DBG) {
+ Slog.d(TAG,
+ "MESSAGE_BLUETOOTH_STATE_CHANGE: " + BluetoothAdapter.nameForState(
+ prevState) + " > " + BluetoothAdapter.nameForState(
+ newState));
+ }
+ mState = newState;
+ bluetoothStateChangeHandler(prevState, newState);
+ // handle error state transition case from TURNING_ON to OFF
+ // unbind and rebind bluetooth service and enable bluetooth
+ if ((prevState == BluetoothAdapter.STATE_BLE_TURNING_ON) && (newState
+ == BluetoothAdapter.STATE_OFF) && (mBluetooth != null) && mEnable) {
+ recoverBluetoothServiceFromError(false);
+ }
+ if ((prevState == BluetoothAdapter.STATE_TURNING_ON) &&
+ (newState == BluetoothAdapter.STATE_OFF) &&
+ (mBluetooth != null) && mEnable) {
+ persistBluetoothSetting(BLUETOOTH_OFF);
+ }
+ if ((prevState == BluetoothAdapter.STATE_TURNING_ON) &&
+ (newState == BluetoothAdapter.STATE_BLE_ON) &&
+ (mBluetooth != 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 == BluetoothAdapter.STATE_BLE_TURNING_OFF) && (newState
+ == BluetoothAdapter.STATE_OFF)) {
+ if (mEnable) {
+ Slog.d(TAG, "Entering STATE_OFF but mEnabled is true; restarting.");
+ mHandler.removeMessages(MESSAGE_RESTART_BLUETOOTH_SERVICE);
+ waitForState(Set.of(BluetoothAdapter.STATE_OFF));
+ Message restartMsg =
+ mHandler.obtainMessage(MESSAGE_RESTART_BLUETOOTH_SERVICE);
+ mHandler.sendMessageDelayed(restartMsg, getServiceRestartMs());
+ }
+ }
+ if (newState == BluetoothAdapter.STATE_ON
+ || newState == BluetoothAdapter.STATE_BLE_ON) {
+ // bluetooth is working, reset the counter
+ if (mErrorRecoveryRetryCounter != 0) {
+ Slog.w(TAG, "bluetooth is recovered from error");
+ mErrorRecoveryRetryCounter = 0;
+ }
+ }
+ break;
+ }
+ case MESSAGE_BLUETOOTH_SERVICE_DISCONNECTED: {
+ Slog.e(TAG, "MESSAGE_BLUETOOTH_SERVICE_DISCONNECTED(" + msg.arg1 + ")");
+ try {
+ mBluetoothLock.writeLock().lock();
+ if (msg.arg1 == SERVICE_IBLUETOOTH) {
+ // if service is unbinded already, do nothing and return
+ if (mBluetooth == null) {
+ break;
+ }
+ mBluetooth = null;
+ } else if (msg.arg1 == SERVICE_IBLUETOOTHGATT) {
+ mBluetoothGatt = null;
+ break;
+ } else {
+ Slog.e(TAG, "Unknown argument for service disconnect!");
+ break;
+ }
+ } finally {
+ mBluetoothLock.writeLock().unlock();
+ }
+
+ // Make sure BT process exit completely
+ int[] pids = Process.getPidsForCommands(
+ new String[]{ "com.android.bluetooth" });
+ if (pids != null && pids.length > 0) {
+ for(int pid : pids) {
+ Slog.e(TAG, "Killing BT process with PID = " + pid);
+ Process.killProcess(pid);
+ }
+ }
+
+ // log the unexpected crash
+ addCrashLog();
+ addActiveLog(BluetoothProtoEnums.ENABLE_DISABLE_REASON_CRASH,
+ mContext.getPackageName(), false);
+ if (mEnable) {
+ mEnable = false;
+ // Send a Bluetooth Restart message
+ Message restartMsg =
+ mHandler.obtainMessage(MESSAGE_RESTART_BLUETOOTH_SERVICE);
+ mHandler.sendMessageDelayed(restartMsg, getServiceRestartMs());
+ }
+
+ sendBluetoothServiceDownCallback();
+
+ // Send BT state broadcast to update
+ // the BT icon correctly
+ if ((mState == BluetoothAdapter.STATE_TURNING_ON) || (mState
+ == BluetoothAdapter.STATE_ON)) {
+ bluetoothStateChangeHandler(BluetoothAdapter.STATE_ON,
+ BluetoothAdapter.STATE_TURNING_OFF);
+ mState = BluetoothAdapter.STATE_TURNING_OFF;
+ }
+ if (mState == BluetoothAdapter.STATE_TURNING_OFF) {
+ bluetoothStateChangeHandler(BluetoothAdapter.STATE_TURNING_OFF,
+ BluetoothAdapter.STATE_OFF);
+ }
+
+ mHandler.removeMessages(MESSAGE_BLUETOOTH_STATE_CHANGE);
+ mState = BluetoothAdapter.STATE_OFF;
+ break;
+ }
+ case MESSAGE_RESTART_BLUETOOTH_SERVICE: {
+ mErrorRecoveryRetryCounter++;
+ Slog.d(TAG, "MESSAGE_RESTART_BLUETOOTH_SERVICE: retry count="
+ + mErrorRecoveryRetryCounter);
+ if (mErrorRecoveryRetryCounter < MAX_ERROR_RESTART_RETRIES) {
+ /* Enable without persisting the setting as
+ it doesnt change when IBluetooth
+ service restarts */
+ mEnable = true;
+ addActiveLog(BluetoothProtoEnums.ENABLE_DISABLE_REASON_RESTARTED,
+ mContext.getPackageName(), true);
+ handleEnable(mQuietEnable);
+ } else {
+ Slog.e(TAG, "Reach maximum retry to restart Bluetooth!");
+ }
+ break;
+ }
+ case MESSAGE_TIMEOUT_BIND: {
+ Slog.e(TAG, "MESSAGE_TIMEOUT_BIND");
+ mBluetoothLock.writeLock().lock();
+ mBinding = false;
+ mBluetoothLock.writeLock().unlock();
+ // Ensure try BIND for one more time
+ if(!mTryBindOnBindTimeout) {
+ int userID = ActivityManager.getCurrentUser();
+
+ Slog.d(TAG, "Current user: " + userID);
+ if (mBindingUserID == userID) {
+ Slog.e(TAG, " Trying to Bind again");
+ mTryBindOnBindTimeout = true;
+ handleEnable(mQuietEnable);
+ }
+ } else {
+ Slog.e(TAG, "Bind trails excedded");
+ mTryBindOnBindTimeout = false;
+ }
+ break;
+ }
+ case MESSAGE_TIMEOUT_UNBIND: {
+ Slog.e(TAG, "MESSAGE_TIMEOUT_UNBIND");
+ mBluetoothLock.writeLock().lock();
+ mUnbinding = false;
+ mBluetoothLock.writeLock().unlock();
+ break;
+ }
+
+ case MESSAGE_USER_SWITCHED: {
+ if (DBG) {
+ Slog.d(TAG, "MESSAGE_USER_SWITCHED");
+ }
+ mHandler.removeMessages(MESSAGE_USER_SWITCHED);
+
+ try {
+ mBluetoothLock.writeLock().lock();
+ int state = getState();
+
+ if (mBluetooth != null && ((state == BluetoothAdapter.STATE_ON) ||
+ (state == BluetoothAdapter.STATE_BLE_ON && isBleAppPresent()))) {
+
+ /* disable and enable BT when detect a user switch */
+ if (state == BluetoothAdapter.STATE_ON) {
+ restartForReason(
+ BluetoothProtoEnums.ENABLE_DISABLE_REASON_USER_SWITCH);
+ } else {
+ if (DBG) {
+ Slog.d(TAG, "Turn off from BLE state");
+ }
+ clearBleApps();
+ addActiveLog(BluetoothProtoEnums.ENABLE_DISABLE_REASON_USER_SWITCH,
+ mContext.getPackageName(), false);
+ mEnable = false;
+ mBluetooth.onBrEdrDown(mContext.getAttributionSource());
+ }
+ } else if (mBinding || mBluetooth != null) {
+ Message userMsg = mHandler.obtainMessage(MESSAGE_USER_SWITCHED);
+ userMsg.arg2 = 1 + msg.arg2;
+ // if user is switched when service is binding retry after a delay
+ mHandler.sendMessageDelayed(userMsg, USER_SWITCHED_TIME_MS);
+ if (DBG) {
+ Slog.d(TAG, "Retry MESSAGE_USER_SWITCHED " + userMsg.arg2);
+ }
+ }
+ } catch (RemoteException e) {
+ Slog.e(TAG, "MESSAGE_USER_SWITCHED: Remote exception", e);
+ } finally {
+ mBluetoothLock.writeLock().unlock();
+ }
+ break;
+ }
+ case MESSAGE_USER_UNLOCKED: {
+ if (DBG) {
+ Slog.d(TAG, "MESSAGE_USER_UNLOCKED");
+ }
+ mHandler.removeMessages(MESSAGE_USER_SWITCHED);
+
+ if (mEnable && !mBinding && (mBluetooth == null)) {
+ // We should be connected, but we gave up for some
+ // reason; maybe the Bluetooth service wasn't encryption
+ // aware, so try binding again.
+ if (DBG) {
+ Slog.d(TAG, "Enabled but not bound; retrying after unlock");
+ }
+ handleEnable(mQuietEnable);
+ }
+ break;
+ }
+ case MESSAGE_INIT_FLAGS_CHANGED: {
+ if (DBG) {
+ Slog.d(TAG, "MESSAGE_INIT_FLAGS_CHANGED");
+ }
+ mHandler.removeMessages(MESSAGE_INIT_FLAGS_CHANGED);
+ if (mBluetoothModeChangeHelper.isMediaProfileConnected()) {
+ Slog.i(TAG, "Delaying MESSAGE_INIT_FLAGS_CHANGED by "
+ + DELAY_FOR_RETRY_INIT_FLAG_CHECK_MS
+ + " ms due to existing connections");
+ mHandler.sendEmptyMessageDelayed(
+ MESSAGE_INIT_FLAGS_CHANGED,
+ DELAY_FOR_RETRY_INIT_FLAG_CHECK_MS);
+ break;
+ }
+ if (!isDeviceProvisioned()) {
+ Slog.i(TAG, "Delaying MESSAGE_INIT_FLAGS_CHANGED by "
+ + DELAY_FOR_RETRY_INIT_FLAG_CHECK_MS
+ + "ms because device is not provisioned");
+ mHandler.sendEmptyMessageDelayed(
+ MESSAGE_INIT_FLAGS_CHANGED,
+ DELAY_FOR_RETRY_INIT_FLAG_CHECK_MS);
+ break;
+ }
+ if (mBluetooth != null && isEnabled()) {
+ Slog.i(TAG, "Restarting Bluetooth due to init flag change");
+ restartForReason(
+ BluetoothProtoEnums.ENABLE_DISABLE_REASON_INIT_FLAGS_CHANGED);
+ }
+ break;
+ }
+ }
+ }
+
+ @RequiresPermission(allOf = {
+ android.Manifest.permission.BLUETOOTH_CONNECT,
+ android.Manifest.permission.BLUETOOTH_PRIVILEGED
+ })
+ private void restartForReason(int reason) {
+ try {
+ mBluetoothLock.readLock().lock();
+ if (mBluetooth != null) {
+ mBluetooth.unregisterCallback(mBluetoothCallback,
+ mContext.getAttributionSource());
+ }
+ } catch (RemoteException re) {
+ Slog.e(TAG, "Unable to unregister", re);
+ } finally {
+ mBluetoothLock.readLock().unlock();
+ }
+
+ if (mState == BluetoothAdapter.STATE_TURNING_OFF) {
+ // MESSAGE_USER_SWITCHED happened right after MESSAGE_ENABLE
+ bluetoothStateChangeHandler(mState, BluetoothAdapter.STATE_OFF);
+ mState = BluetoothAdapter.STATE_OFF;
+ }
+ if (mState == BluetoothAdapter.STATE_OFF) {
+ bluetoothStateChangeHandler(mState, BluetoothAdapter.STATE_TURNING_ON);
+ mState = BluetoothAdapter.STATE_TURNING_ON;
+ }
+
+ /* wait for stable state BLE_ON or ON */
+ waitForState(Set.of(BluetoothAdapter.STATE_BLE_ON,
+ BluetoothAdapter.STATE_ON));
+
+ if (mState == BluetoothAdapter.STATE_TURNING_ON) {
+ bluetoothStateChangeHandler(mState, BluetoothAdapter.STATE_ON);
+ }
+
+ unbindAllBluetoothProfileServices();
+ // disable
+ addActiveLog(reason, mContext.getPackageName(), false);
+
+ clearBleApps();
+
+ handleDisable();
+ // Pbap service need receive STATE_TURNING_OFF intent to close
+ bluetoothStateChangeHandler(BluetoothAdapter.STATE_ON,
+ BluetoothAdapter.STATE_TURNING_OFF);
+
+ /* wait for BLE_ON or OFF state. If its BLE ON state
+ * post BLE ON state to bluetoothStateChangeHandler
+ * to continue off and wait for off state
+ */
+ boolean didDisableTimeout =
+ !waitForState(Set.of(BluetoothAdapter.STATE_BLE_ON,
+ BluetoothAdapter.STATE_OFF));
+
+ if(!didDisableTimeout) {
+ int state = getState();
+
+ if (state == BluetoothAdapter.STATE_BLE_ON) {
+ bluetoothStateChangeHandler(BluetoothAdapter.STATE_TURNING_OFF,
+ BluetoothAdapter.STATE_BLE_ON);
+ }
+
+ didDisableTimeout =
+ !waitForState(Set.of(BluetoothAdapter.STATE_OFF));
+ }
+ bluetoothStateChangeHandler(BluetoothAdapter.STATE_TURNING_OFF,
+ BluetoothAdapter.STATE_OFF);
+ sendBluetoothServiceDownCallback();
+
+ if(!didDisableTimeout) {
+ try {
+ mBluetoothLock.writeLock().lock();
+ if (mBluetooth != null) {
+ mBluetooth = null;
+ // Unbind
+ mContext.unbindService(mConnection);
+ }
+ mBluetoothGatt = null;
+ } finally {
+ mBluetoothLock.writeLock().unlock();
+ }
+ }
+
+ //
+ // 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);
+ mHandler.removeMessages(MESSAGE_BLUETOOTH_SERVICE_DISCONNECTED);
+ } else {
+ SystemClock.sleep(100);
+ }
+
+ mHandler.removeMessages(MESSAGE_BLUETOOTH_STATE_CHANGE);
+ mState = BluetoothAdapter.STATE_OFF;
+ // enable
+ addActiveLog(reason, mContext.getPackageName(), true);
+ // mEnable flag could have been reset on disableBLE. Reenable it.
+ mEnable = true;
+ handleEnable(mQuietEnable);
+ }
+ }
+
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
+ private void handleEnable(boolean quietMode) {
+ mQuietEnable = quietMode;
+
+ try {
+ mBluetoothLock.writeLock().lock();
+ if ((mBluetooth == null) && (!mBinding)) {
+ Slog.d(TAG, "binding Bluetooth service");
+ //Start bind timeout and bind
+ Message timeoutMsg = mHandler.obtainMessage(MESSAGE_TIMEOUT_BIND);
+ mHandler.sendMessageDelayed(timeoutMsg, 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 {
+ mBinding = true;
+ mBindingUserID = ActivityManager.getCurrentUser();
+ Slog.d(TAG, "Binding BT service. Current user: " + mBindingUserID);
+ }
+ } else if (mBluetooth != null) {
+ //Enable bluetooth
+ try {
+ if (!mBluetooth.enable(mQuietEnable, mContext.getAttributionSource())) {
+ Slog.e(TAG, "IBluetooth.enable() returned false");
+ }
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Unable to call enable()", e);
+ }
+ }
+ } finally {
+ mBluetoothLock.writeLock().unlock();
+ }
+ }
+
+ boolean doBind(Intent intent, ServiceConnection conn, int flags, UserHandle user) {
+ ComponentName comp = intent.resolveSystemService(mContext.getPackageManager(), 0);
+ intent.setComponent(comp);
+ if (comp == null || !mContext.bindServiceAsUser(intent, conn, flags, user)) {
+ Slog.e(TAG, "Fail to bind to: " + intent);
+ return false;
+ }
+ return true;
+ }
+
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
+ private void handleDisable() {
+ try {
+ mBluetoothLock.readLock().lock();
+ if (mBluetooth != null) {
+ if (DBG) {
+ Slog.d(TAG, "Sending off request.");
+ }
+ if (!mBluetooth.disable(mContext.getAttributionSource())) {
+ Slog.e(TAG, "IBluetooth.disable() returned false");
+ }
+ }
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Unable to call disable()", e);
+ } finally {
+ mBluetoothLock.readLock().unlock();
+ }
+ }
+
+ private boolean checkIfCallerIsForegroundUser() {
+ int foregroundUser;
+ int callingUser = UserHandle.getCallingUserId();
+ int callingUid = Binder.getCallingUid();
+ final long callingIdentity = Binder.clearCallingIdentity();
+ UserManager um = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
+ UserInfo ui = um.getProfileParent(callingUser);
+ int parentUser = (ui != null) ? ui.id : UserHandle.USER_NULL;
+ int callingAppId = UserHandle.getAppId(callingUid);
+ boolean valid = false;
+ try {
+ foregroundUser = ActivityManager.getCurrentUser();
+ valid = (callingUser == foregroundUser) || parentUser == foregroundUser
+ || callingAppId == Process.NFC_UID || callingAppId == mSystemUiUid
+ || callingAppId == Process.SHELL_UID;
+ if (DBG && !valid) {
+ Slog.d(TAG, "checkIfCallerIsForegroundUser: valid=" + valid + " callingUser="
+ + callingUser + " parentUser=" + parentUser + " foregroundUser="
+ + foregroundUser);
+ }
+ } finally {
+ Binder.restoreCallingIdentity(callingIdentity);
+ }
+ return valid;
+ }
+
+ private void sendBleStateChanged(int prevState, int newState) {
+ if (DBG) {
+ Slog.d(TAG,
+ "Sending BLE State Change: " + BluetoothAdapter.nameForState(prevState) + " > "
+ + BluetoothAdapter.nameForState(newState));
+ }
+ // Send broadcast message to everyone else
+ Intent intent = new Intent(BluetoothAdapter.ACTION_BLE_STATE_CHANGED);
+ intent.putExtra(BluetoothAdapter.EXTRA_PREVIOUS_STATE, prevState);
+ intent.putExtra(BluetoothAdapter.EXTRA_STATE, newState);
+ intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
+ intent.setFlags(Intent.FLAG_RECEIVER_FOREGROUND);
+ mContext.sendBroadcastAsUser(intent, UserHandle.ALL, null, getTempAllowlistBroadcastOptions());
+ }
+
+ private boolean isBleState(int state) {
+ switch (state) {
+ case BluetoothAdapter.STATE_BLE_ON:
+ case BluetoothAdapter.STATE_BLE_TURNING_ON:
+ case BluetoothAdapter.STATE_BLE_TURNING_OFF:
+ return true;
+ }
+ return false;
+ }
+
+ @RequiresPermission(allOf = {
+ android.Manifest.permission.BLUETOOTH_CONNECT,
+ android.Manifest.permission.BLUETOOTH_PRIVILEGED,
+ })
+ private void bluetoothStateChangeHandler(int prevState, int newState) {
+ boolean isStandardBroadcast = true;
+ if (prevState == newState) { // No change. Nothing to do.
+ return;
+ }
+ // Notify all proxy objects first of adapter state change
+ if (newState == BluetoothAdapter.STATE_BLE_ON || newState == BluetoothAdapter.STATE_OFF) {
+ boolean intermediate_off = (prevState == BluetoothAdapter.STATE_TURNING_OFF
+ && newState == BluetoothAdapter.STATE_BLE_ON);
+
+ if (newState == BluetoothAdapter.STATE_OFF) {
+ // If Bluetooth is off, send service down event to proxy objects, and unbind
+ if (DBG) {
+ Slog.d(TAG, "Bluetooth is complete off, send Service Down");
+ }
+ sendBluetoothServiceDownCallback();
+ sendBluetoothStateCallback(false);
+ unbindAndFinish();
+ sendBleStateChanged(prevState, newState);
+
+ /* Currently, the OFF intent is broadcasted externally only when we transition
+ * from TURNING_OFF to BLE_ON state. So if the previous state is a BLE state,
+ * we are guaranteed that the OFF intent has been broadcasted earlier and we
+ * can safely skip it.
+ * Conversely, if the previous state is not a BLE state, it indicates that some
+ * sort of crash has occurred, moving us directly to STATE_OFF without ever
+ * passing through BLE_ON. We should broadcast the OFF intent in this case. */
+ isStandardBroadcast = !isBleState(prevState);
+
+ } else if (!intermediate_off) {
+ // connect to GattService
+ if (DBG) {
+ Slog.d(TAG, "Bluetooth is in LE only mode");
+ }
+ if (mBluetoothGatt != null || !mContext.getPackageManager()
+ .hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) {
+ continueFromBleOnState();
+ } else {
+ if (DBG) {
+ Slog.d(TAG, "Binding Bluetooth GATT service");
+ }
+ Intent i = new Intent(IBluetoothGatt.class.getName());
+ doBind(i, mConnection, Context.BIND_AUTO_CREATE | Context.BIND_IMPORTANT,
+ UserHandle.CURRENT);
+ }
+ sendBleStateChanged(prevState, newState);
+ //Don't broadcase this as std intent
+ isStandardBroadcast = false;
+
+ } else if (intermediate_off) {
+ if (DBG) {
+ Slog.d(TAG, "Intermediate off, back to LE only mode");
+ }
+ // For LE only mode, broadcast as is
+ sendBleStateChanged(prevState, newState);
+ sendBluetoothStateCallback(false); // BT is OFF for general users
+ // Broadcast as STATE_OFF
+ newState = BluetoothAdapter.STATE_OFF;
+ sendBrEdrDownCallback(mContext.getAttributionSource());
+ }
+ } else if (newState == BluetoothAdapter.STATE_ON) {
+ boolean isUp = (newState == BluetoothAdapter.STATE_ON);
+ sendBluetoothStateCallback(isUp);
+ sendBleStateChanged(prevState, newState);
+
+ } else if (newState == BluetoothAdapter.STATE_BLE_TURNING_ON
+ || newState == BluetoothAdapter.STATE_BLE_TURNING_OFF) {
+ sendBleStateChanged(prevState, newState);
+ isStandardBroadcast = false;
+
+ } else if (newState == BluetoothAdapter.STATE_TURNING_ON
+ || newState == BluetoothAdapter.STATE_TURNING_OFF) {
+ sendBleStateChanged(prevState, newState);
+ }
+
+ if (isStandardBroadcast) {
+ if (prevState == BluetoothAdapter.STATE_BLE_ON) {
+ // Show prevState of BLE_ON as OFF to standard users
+ prevState = BluetoothAdapter.STATE_OFF;
+ }
+ if (DBG) {
+ Slog.d(TAG,
+ "Sending State Change: " + BluetoothAdapter.nameForState(prevState) + " > "
+ + BluetoothAdapter.nameForState(newState));
+ }
+ Intent intent = new Intent(BluetoothAdapter.ACTION_STATE_CHANGED);
+ intent.putExtra(BluetoothAdapter.EXTRA_PREVIOUS_STATE, prevState);
+ intent.putExtra(BluetoothAdapter.EXTRA_STATE, newState);
+ intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
+ intent.addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
+ mContext.sendBroadcastAsUser(intent, UserHandle.ALL, null,
+ getTempAllowlistBroadcastOptions());
+ }
+ }
+
+ private boolean waitForState(Set<Integer> states) {
+ int i = 0;
+ while (i < 16) {
+ try {
+ mBluetoothLock.readLock().lock();
+ if (mBluetooth == null) {
+ break;
+ }
+ if (states.contains(mBluetooth.getState())) {
+ return true;
+ }
+ } catch (RemoteException e) {
+ Slog.e(TAG, "getState()", e);
+ break;
+ } finally {
+ mBluetoothLock.readLock().unlock();
+ }
+ SystemClock.sleep(300);
+ i++;
+ }
+ Slog.e(TAG, "waitForState " + states + " time out");
+ return false;
+ }
+
+ /* TODO(b/151672214) - Update for change from waitForOnOff -> waitForState
+ private boolean waitForMonitoredState(Set<Integer> states) {
+ int i = 0;
+ while (i < 10) {
+ synchronized(mConnection) {
+ try {
+ if (mBluetooth == null) break;
+ if (on) {
+ if (mBluetooth.getState() == BluetoothAdapter.STATE_ON) return true;
+ if (mBluetooth.getState() == BluetoothAdapter.STATE_BLE_ON) {
+ bluetoothStateChangeHandler(BluetoothAdapter.STATE_BLE_TURNING_ON,
+ BluetoothAdapter.STATE_BLE_ON);
+ if (mBluetoothGatt != null) {
+ Slog.d(TAG,"GattService is connected, execute waitForState");
+ boolean ret = waitForState(states);
+ return ret;
+ } else {
+ Slog.d(TAG,
+ "GattService connect in progress, return to avoid timeout");
+ return true;
+ }
+ }
+ } else if (off) {
+ if (mBluetooth.getState() == BluetoothAdapter.STATE_OFF) return true;
+ if (mBluetooth.getState() == BluetoothAdapter.STATE_BLE_ON) {
+ bluetoothStateChangeHandler(BluetoothAdapter.STATE_TURNING_OFF,
+ BluetoothAdapter.STATE_BLE_ON);
+ boolean ret = waitForState(states);
+ return ret;
+ }
+ } else {
+ if (mBluetooth.getState() != BluetoothAdapter.STATE_ON) return true;
+ }
+ } catch (RemoteException e) {
+ Slog.e(TAG, "getState()", e);
+ break;
+ }
+ }
+ SystemClock.sleep(300);
+ i++;
+ }
+ Slog.e(TAG,"waitForMonitoredOnOff time out");
+ return false;
+ }
+ */
+
+ private void sendDisableMsg(int reason, String packageName) {
+ mHandler.sendMessage(mHandler.obtainMessage(MESSAGE_DISABLE));
+ addActiveLog(reason, packageName, false);
+ }
+
+ 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.sendMessage(mHandler.obtainMessage(MESSAGE_ENABLE, quietMode ? 1 : 0,
+ isBle ? 1 : 0));
+ addActiveLog(reason, packageName, true);
+ mLastEnabledTime = SystemClock.elapsedRealtime();
+ }
+
+ private void addActiveLog(int reason, String packageName, boolean enable) {
+ synchronized (mActiveLogs) {
+ if (mActiveLogs.size() > ACTIVE_LOG_MAX_SIZE) {
+ mActiveLogs.remove();
+ }
+ mActiveLogs.add(
+ new ActiveLog(reason, packageName, enable, System.currentTimeMillis()));
+ }
+
+ int state = enable ? FrameworkStatsLog.BLUETOOTH_ENABLED_STATE_CHANGED__STATE__ENABLED :
+ FrameworkStatsLog.BLUETOOTH_ENABLED_STATE_CHANGED__STATE__DISABLED;
+ FrameworkStatsLog.write_non_chained(FrameworkStatsLog.BLUETOOTH_ENABLED_STATE_CHANGED,
+ Binder.getCallingUid(), null, state, reason, packageName);
+ }
+
+ private void addCrashLog() {
+ synchronized (mCrashTimestamps) {
+ if (mCrashTimestamps.size() == CRASH_LOG_MAX_SIZE) {
+ mCrashTimestamps.removeFirst();
+ }
+ mCrashTimestamps.add(System.currentTimeMillis());
+ mCrashes++;
+ }
+ }
+
+ @RequiresPermission(allOf = {
+ android.Manifest.permission.BLUETOOTH_CONNECT,
+ android.Manifest.permission.BLUETOOTH_PRIVILEGED,
+ })
+ private void recoverBluetoothServiceFromError(boolean clearBle) {
+ Slog.e(TAG, "recoverBluetoothServiceFromError");
+ try {
+ mBluetoothLock.readLock().lock();
+ if (mBluetooth != null) {
+ //Unregister callback object
+ mBluetooth.unregisterCallback(mBluetoothCallback, mContext.getAttributionSource());
+ }
+ } catch (RemoteException re) {
+ Slog.e(TAG, "Unable to unregister", re);
+ } finally {
+ mBluetoothLock.readLock().unlock();
+ }
+
+ waitForState(Set.of(BluetoothAdapter.STATE_OFF));
+
+ sendBluetoothServiceDownCallback();
+
+ mHandler.removeMessages(MESSAGE_BLUETOOTH_STATE_CHANGE);
+ mState = BluetoothAdapter.STATE_OFF;
+
+ if (clearBle) {
+ clearBleApps();
+ }
+
+ mEnable = false;
+
+ // Send a Bluetooth Restart message to reenable bluetooth
+ Message restartMsg = mHandler.obtainMessage(MESSAGE_RESTART_BLUETOOTH_SERVICE);
+ mHandler.sendMessageDelayed(restartMsg, ERROR_RESTART_TIME_MS);
+ }
+
+ private boolean isBluetoothDisallowed() {
+ final long callingIdentity = Binder.clearCallingIdentity();
+ try {
+ return mContext.getSystemService(UserManager.class)
+ .hasUserRestriction(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 userId user to disable bluetooth sharing for.
+ * @param bluetoothSharingDisallowed whether bluetooth sharing is disallowed.
+ */
+ private void updateOppLauncherComponentState(int userId, boolean bluetoothSharingDisallowed) {
+ final ComponentName oppLauncherComponent = new ComponentName("com.android.bluetooth",
+ "com.android.bluetooth.opp.BluetoothOppLauncherActivity");
+ final int newState =
+ bluetoothSharingDisallowed ? PackageManager.COMPONENT_ENABLED_STATE_DISABLED
+ : PackageManager.COMPONENT_ENABLED_STATE_DEFAULT;
+ try {
+ final IPackageManager imp = AppGlobals.getPackageManager();
+ imp.setComponentEnabledSetting(oppLauncherComponent, newState,
+ PackageManager.DONT_KILL_APP, userId);
+ } catch (Exception e) {
+ // The component was not found, do nothing.
+ }
+ }
+
+ private int getServiceRestartMs() {
+ return (mErrorRecoveryRetryCounter + 1) * SERVICE_RESTART_TIME_MS;
+ }
+
+ @Override
+ public void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
+ if (!DumpUtils.checkDumpPermission(mContext, TAG, writer)) {
+ return;
+ }
+ 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: " + BluetoothAdapter.nameForState(mState));
+ writer.println(" address: " + 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";
+ }
+
+ if (mBluetoothBinder == null) {
+ errorMsg = "Bluetooth Service not connected";
+ } else {
+ try {
+ mBluetoothBinder.dump(fd, args);
+ } catch (RemoteException re) {
+ errorMsg = "RemoteException while dumping Bluetooth Service";
+ }
+ }
+ if (errorMsg != null) {
+ writer.println(errorMsg);
+ }
+ }
+
+ private void dumpProto(FileDescriptor fd) {
+ final ProtoOutputStream proto = new ProtoOutputStream(fd);
+ proto.write(BluetoothManagerServiceDumpProto.ENABLED, isEnabled());
+ proto.write(BluetoothManagerServiceDumpProto.STATE, mState);
+ proto.write(BluetoothManagerServiceDumpProto.STATE_NAME,
+ BluetoothAdapter.nameForState(mState));
+ proto.write(BluetoothManagerServiceDumpProto.ADDRESS, 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 BluetoothProtoEnums.ENABLE_DISABLE_REASON_APPLICATION_REQUEST:
+ return "APPLICATION_REQUEST";
+ case BluetoothProtoEnums.ENABLE_DISABLE_REASON_AIRPLANE_MODE:
+ return "AIRPLANE_MODE";
+ case BluetoothProtoEnums.ENABLE_DISABLE_REASON_DISALLOWED:
+ return "DISALLOWED";
+ case BluetoothProtoEnums.ENABLE_DISABLE_REASON_RESTARTED:
+ return "RESTARTED";
+ case BluetoothProtoEnums.ENABLE_DISABLE_REASON_START_ERROR:
+ return "START_ERROR";
+ case BluetoothProtoEnums.ENABLE_DISABLE_REASON_SYSTEM_BOOT:
+ return "SYSTEM_BOOT";
+ case BluetoothProtoEnums.ENABLE_DISABLE_REASON_CRASH:
+ return "CRASH";
+ case BluetoothProtoEnums.ENABLE_DISABLE_REASON_USER_SWITCH:
+ return "USER_SWITCH";
+ case BluetoothProtoEnums.ENABLE_DISABLE_REASON_RESTORE_USER_SETTING:
+ return "RESTORE_USER_SETTING";
+ case BluetoothProtoEnums.ENABLE_DISABLE_REASON_FACTORY_RESET:
+ return "FACTORY_RESET";
+ case BluetoothProtoEnums.ENABLE_DISABLE_REASON_INIT_FLAGS_CHANGED:
+ return "INIT_FLAGS_CHANGED";
+ case BluetoothProtoEnums.ENABLE_DISABLE_REASON_UNSPECIFIED:
+ default: return "UNKNOWN[" + reason + "]";
+ }
+ }
+
+ @SuppressLint("AndroidFrameworkRequiresPermission")
+ private static boolean checkPermissionForDataDelivery(Context context, String permission,
+ AttributionSource attributionSource, String message) {
+ PermissionManager pm = context.getSystemService(PermissionManager.class);
+ if (pm == null) {
+ return false;
+ }
+ AttributionSource currentAttribution = new AttributionSource
+ .Builder(context.getAttributionSource())
+ .setNext(attributionSource)
+ .build();
+ final int result = pm.checkPermissionForDataDeliveryFromDataSource(permission,
+ currentAttribution, message);
+ if (result == PERMISSION_GRANTED) {
+ return true;
+ }
+
+ final String msg = "Need " + permission + " permission for " + attributionSource + ": "
+ + message;
+ if (result == PERMISSION_HARD_DENIED) {
+ throw new SecurityException(msg);
+ } else {
+ Log.w(TAG, msg);
+ return false;
+ }
+ }
+
+ /**
+ * Returns true if the BLUETOOTH_CONNECT permission is granted for the calling app. Returns
+ * false if the result is a soft denial. Throws SecurityException if the result is a hard
+ * denial.
+ *
+ * <p>Should be used in situations where the app op should not be noted.
+ */
+ @SuppressLint("AndroidFrameworkRequiresPermission")
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
+ public static boolean checkConnectPermissionForDataDelivery(
+ Context context, AttributionSource attributionSource, String message) {
+ return checkPermissionForDataDelivery(context, BLUETOOTH_CONNECT,
+ attributionSource, message);
+ }
+
+ 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();
+ }
+}