Loading android/app/res/values/strings.xml +5 −0 Original line number Diff line number Diff line Loading @@ -248,4 +248,9 @@ <string name="a2dp_sink_mbs_label">Bluetooth Audio</string> <string name="bluetooth_opp_file_limit_exceeded">Files bigger than 4GB cannot be transferred</string> <string name="bluetooth_connect_action">Connect to Bluetooth</string> <string name="bluetooth_enabled_apm_title">You turned on Bluetooth</string> <string name="bluetooth_enabled_apm_message">Your phone will keep Bluetooth on in airplane mode, unless you turn it off while in this mode</string> <string name="bluetooth_stays_on_title">Bluetooth stays on</string> <string name="bluetooth_and_wifi_stays_on_title">Bluetooth and Wi-Fi stays on</string> <string name="bluetooth_and_wifi_stays_on_message">Your phone will keep both on in airplane mode, unless you turn it off while in this mode</string> </resources> service/java/com/android/server/bluetooth/BluetoothAirplaneModeListener.java +114 −7 Original line number Diff line number Diff line Loading @@ -18,6 +18,8 @@ package com.android.server.bluetooth; import android.annotation.RequiresPermission; import android.content.Context; import android.content.pm.PackageManager; import android.content.res.Resources; import android.database.ContentObserver; import android.os.Handler; import android.os.Looper; Loading @@ -41,18 +43,44 @@ public class BluetoothAirplaneModeListener { private static final String TAG = "BluetoothAirplaneModeListener"; @VisibleForTesting static final String TOAST_COUNT = "bluetooth_airplane_toast_count"; // keeps track of whether wifi should remain on in airplane mode public static final String WIFI_APM_STATE = "wifi_apm_state"; // keeps track of whether wifi and bt remains on notification was shown public static final String APM_WIFI_BT_NOTIFICATION = "apm_wifi_bt_notification"; // keeps track of whether bt remains on notification was shown public static final String APM_BT_NOTIFICATION = "apm_bt_notification"; // keeps track of whether airplane mode enhancement feature is enabled public static final String APM_ENHANCEMENT = "apm_enhancement_enabled"; // keeps track of whether user changed bt state in airplane mode public static final String APM_USER_TOGGLED_BLUETOOTH = "apm_user_toggled_bluetooth"; // keeps track of whether bt should remain on in airplane mode public static final String BLUETOOTH_APM_STATE = "bluetooth_apm_state"; // keeps track of what the default value for bt should be in airplane mode public static final String BT_DEFAULT_APM_STATE = "bt_default_apm_state"; // keeps track of whether user enabling bt notification was shown public static final String APM_BT_ENABLED_NOTIFICATION = "apm_bt_enabled_notification"; private static final int MSG_AIRPLANE_MODE_CHANGED = 0; public static final int NOTIFICATION_NOT_SHOWN = 0; public static final int NOTIFICATION_SHOWN = 1; public static final int UNUSED = 0; public static final int USED = 1; @VisibleForTesting static final int MAX_TOAST_COUNT = 10; // 10 times private final BluetoothManagerService mBluetoothManager; private final BluetoothAirplaneModeHandler mHandler; private final Context mContext; private BluetoothModeChangeHelper mAirplaneHelper; private BluetoothNotificationManager mNotificationManager; @VisibleForTesting int mToastCount = 0; BluetoothAirplaneModeListener(BluetoothManagerService service, Looper looper, Context context) { BluetoothAirplaneModeListener(BluetoothManagerService service, Looper looper, Context context, BluetoothNotificationManager notificationManager) { mBluetoothManager = service; mNotificationManager = notificationManager; mContext = context; mHandler = new BluetoothAirplaneModeHandler(looper); context.getContentResolver().registerContentObserver( Loading Loading @@ -117,9 +145,29 @@ public class BluetoothAirplaneModeListener { // BLUETOOTH_ON_AIRPLANE mode. mAirplaneHelper.setSettingsInt(Settings.Global.BLUETOOTH_ON, BluetoothManagerService.BLUETOOTH_ON_AIRPLANE); if (!isApmEnhancementEnabled() || !isBluetoothToggledOnApm()) { if (shouldPopToast()) { mAirplaneHelper.showToastMessage(); } } else { if (isWifiEnabledOnApm() && isFirstTimeNotification(APM_WIFI_BT_NOTIFICATION)) { try { sendApmNotification("bluetooth_and_wifi_stays_on_title", "bluetooth_and_wifi_stays_on_message", APM_WIFI_BT_NOTIFICATION); } catch (Exception e) { Log.e(TAG, "APM enhancement BT and Wi-Fi stays on notification not shown"); } } else if (!isWifiEnabledOnApm() && isFirstTimeNotification(APM_BT_NOTIFICATION)) { try { sendApmNotification("bluetooth_stays_on_title", "bluetooth_enabled_apm_message", APM_BT_NOTIFICATION); } catch (Exception e) { Log.e(TAG, "APM enhancement BT stays on notification not shown"); } } } return; } if (mAirplaneHelper != null) { Loading @@ -132,10 +180,69 @@ public class BluetoothAirplaneModeListener { if (mAirplaneHelper == null) { return false; } if (!mAirplaneHelper.isBluetoothOn() || !mAirplaneHelper.isAirplaneModeOn() || !mAirplaneHelper.isMediaProfileConnected()) { return false; boolean apmEnhancementUsed = isApmEnhancementEnabled() && isBluetoothToggledOnApm(); // APM feature disabled or user has not used the feature yet by changing BT state in APM // BT will only remain on in APM when media profile is connected if (!apmEnhancementUsed && mAirplaneHelper.isBluetoothOn() && mAirplaneHelper.isAirplaneModeOn() && mAirplaneHelper.isMediaProfileConnected()) { return true; } // APM feature enabled and user has used the feature by changing BT state in APM // BT will only remain on in APM based on user's last action in APM if (apmEnhancementUsed && mAirplaneHelper.isBluetoothOn() && mAirplaneHelper.isAirplaneModeOn() && mAirplaneHelper.isBluetoothOnAPM()) { return true; } // APM feature enabled and user has not used the feature yet by changing BT state in APM // BT will only remain on in APM if the default value is set to on if (isApmEnhancementEnabled() && !isBluetoothToggledOnApm() && mAirplaneHelper.isBluetoothOn() && mAirplaneHelper.isAirplaneModeOn() && mAirplaneHelper.isBluetoothOnAPM()) { return true; } return false; } private boolean isApmEnhancementEnabled() { return mAirplaneHelper.getSettingsInt(APM_ENHANCEMENT) == 1; } private boolean isBluetoothToggledOnApm() { return mAirplaneHelper.getSettingsSecureInt(APM_USER_TOGGLED_BLUETOOTH, UNUSED) == USED; } private boolean isWifiEnabledOnApm() { return mAirplaneHelper.getSettingsInt(Settings.Global.WIFI_ON) != 0 && mAirplaneHelper.getSettingsSecureInt(WIFI_APM_STATE, 0) == 1; } private boolean isFirstTimeNotification(String name) { return mAirplaneHelper.getSettingsSecureInt( name, NOTIFICATION_NOT_SHOWN) == NOTIFICATION_NOT_SHOWN; } /** * Helper method to send APM notification */ public void sendApmNotification(String titleId, String messageId, String notificationState) throws PackageManager.NameNotFoundException { String btPackageName = mAirplaneHelper.getBluetoothPackageName(); if (btPackageName == null) { Log.e(TAG, "Unable to find Bluetooth package name with " + "APM notification resources"); return; } Resources resources = mContext.getPackageManager() .getResourcesForApplication(btPackageName); int title = resources.getIdentifier(titleId, "string", btPackageName); int message = resources.getIdentifier(messageId, "string", btPackageName); mNotificationManager.sendApmNotification( resources.getString(title), resources.getString(message)); mAirplaneHelper.setSettingsSecureInt(notificationState, NOTIFICATION_SHOWN); } } service/java/com/android/server/bluetooth/BluetoothDeviceConfigListener.java +34 −2 Original line number Diff line number Diff line Loading @@ -16,7 +16,12 @@ package com.android.server.bluetooth; import static com.android.server.bluetooth.BluetoothAirplaneModeListener.APM_ENHANCEMENT; import static com.android.server.bluetooth.BluetoothAirplaneModeListener.BT_DEFAULT_APM_STATE; import android.content.Context; import android.provider.DeviceConfig; import android.provider.Settings; import android.util.Log; import java.util.ArrayList; Loading @@ -35,10 +40,22 @@ public class BluetoothDeviceConfigListener { private final BluetoothManagerService mService; private final boolean mLogDebug; private final Context mContext; private static final int DEFAULT_APM_ENHANCEMENT = 0; private static final int DEFAULT_BT_APM_STATE = 0; private boolean mPrevApmEnhancement; private boolean mPrevBtApmState; BluetoothDeviceConfigListener(BluetoothManagerService service, boolean logDebug) { BluetoothDeviceConfigListener(BluetoothManagerService service, boolean logDebug, Context context) { mService = service; mLogDebug = logDebug; mContext = context; mPrevApmEnhancement = Settings.Global.getInt(mContext.getContentResolver(), APM_ENHANCEMENT, DEFAULT_APM_ENHANCEMENT) == 1; mPrevBtApmState = Settings.Global.getInt(mContext.getContentResolver(), BT_DEFAULT_APM_STATE, DEFAULT_BT_APM_STATE) == 1; DeviceConfig.addOnPropertiesChangedListener( DeviceConfig.NAMESPACE_BLUETOOTH, (Runnable r) -> r.run(), Loading @@ -59,6 +76,22 @@ public class BluetoothDeviceConfigListener { } Log.d(TAG, "onPropertiesChanged: " + String.join(",", flags)); } boolean apmEnhancement = properties.getBoolean( APM_ENHANCEMENT, mPrevApmEnhancement); if (apmEnhancement != mPrevApmEnhancement) { mPrevApmEnhancement = apmEnhancement; Settings.Global.putInt(mContext.getContentResolver(), APM_ENHANCEMENT, apmEnhancement ? 1 : 0); } boolean btApmState = properties.getBoolean( BT_DEFAULT_APM_STATE, mPrevBtApmState); if (btApmState != mPrevBtApmState) { mPrevBtApmState = btApmState; Settings.Global.putInt(mContext.getContentResolver(), BT_DEFAULT_APM_STATE, btApmState ? 1 : 0); } boolean foundInit = false; for (String name : properties.getKeyset()) { if (name.startsWith("INIT_")) { Loading @@ -72,5 +105,4 @@ public class BluetoothDeviceConfigListener { mService.onInitFlagsChanged(); } }; } service/java/com/android/server/bluetooth/BluetoothManagerService.java +87 −7 Original line number Diff line number Diff line Loading @@ -21,6 +21,13 @@ import static android.os.PowerExemptionManager.TEMPORARY_ALLOW_LIST_TYPE_FOREGRO import static android.permission.PermissionManager.PERMISSION_GRANTED; import static android.permission.PermissionManager.PERMISSION_HARD_DENIED; import static com.android.server.bluetooth.BluetoothAirplaneModeListener.APM_BT_ENABLED_NOTIFICATION; import static com.android.server.bluetooth.BluetoothAirplaneModeListener.APM_ENHANCEMENT; import static com.android.server.bluetooth.BluetoothAirplaneModeListener.APM_USER_TOGGLED_BLUETOOTH; import static com.android.server.bluetooth.BluetoothAirplaneModeListener.BLUETOOTH_APM_STATE; import static com.android.server.bluetooth.BluetoothAirplaneModeListener.NOTIFICATION_NOT_SHOWN; import static com.android.server.bluetooth.BluetoothAirplaneModeListener.USED; import android.Manifest; import android.annotation.NonNull; import android.annotation.RequiresPermission; Loading Loading @@ -182,6 +189,9 @@ public class BluetoothManagerService extends IBluetoothManager.Stub { @VisibleForTesting static final int BLUETOOTH_ON_AIRPLANE = 2; private static final int BLUETOOTH_OFF_APM = 0; private static final int BLUETOOTH_ON_APM = 1; private static final int SERVICE_IBLUETOOTH = 1; private static final int SERVICE_IBLUETOOTHGATT = 2; Loading Loading @@ -227,6 +237,8 @@ public class BluetoothManagerService extends IBluetoothManager.Stub { private BluetoothDeviceConfigListener mBluetoothDeviceConfigListener; private BluetoothNotificationManager mBluetoothNotificationManager; // used inside handler thread private boolean mQuietEnable = false; private boolean mEnable; Loading Loading @@ -542,6 +554,8 @@ public class BluetoothManagerService extends IBluetoothManager.Stub { mUserManager = mContext.getSystemService(UserManager.class); mBluetoothNotificationManager = new BluetoothNotificationManager(mContext); mIsHearingAidProfileSupported = BluetoothProperties.isProfileAshaCentralEnabled().orElse(false); Loading Loading @@ -602,7 +616,8 @@ public class BluetoothManagerService extends IBluetoothManager.Stub { if (airplaneModeRadios == null || airplaneModeRadios.contains( Settings.Global.RADIO_BLUETOOTH)) { mBluetoothAirplaneModeListener = new BluetoothAirplaneModeListener( this, mBluetoothHandlerThread.getLooper(), context); this, mBluetoothHandlerThread.getLooper(), context, mBluetoothNotificationManager); } int systemUiUid = -1; Loading @@ -627,6 +642,13 @@ public class BluetoothManagerService extends IBluetoothManager.Stub { Settings.Global.AIRPLANE_MODE_ON, 0) == 1; } /** * Returns true if airplane mode enhancement feature is enabled */ private boolean isApmEnhancementOn() { return Settings.Global.getInt(mContext.getContentResolver(), APM_ENHANCEMENT, 0) == 1; } private boolean supportBluetoothPersistedState() { // Set default support to true to copy config default. return BluetoothProperties.isSupportPersistedStateEnabled().orElse(true); Loading Loading @@ -685,6 +707,43 @@ public class BluetoothManagerService extends IBluetoothManager.Stub { } } /** * Set the Settings Secure Int value for foreground user */ private void setSettingsSecureInt(String name, int value) { if (DBG) { Log.d(TAG, "Persisting Settings Secure Int: " + name + "=" + value); } // waive WRITE_SECURE_SETTINGS permission check final long callingIdentity = Binder.clearCallingIdentity(); try { Context userContext = mContext.createContextAsUser( UserHandle.of(ActivityManager.getCurrentUser()), 0); Settings.Secure.putInt(userContext.getContentResolver(), name, value); } finally { Binder.restoreCallingIdentity(callingIdentity); } } /** * Return whether APM notification has been shown */ private boolean isFirstTimeNotification(String name) { boolean firstTime = false; // waive WRITE_SECURE_SETTINGS permission check final long callingIdentity = Binder.clearCallingIdentity(); try { Context userContext = mContext.createContextAsUser( UserHandle.of(ActivityManager.getCurrentUser()), 0); firstTime = Settings.Secure.getInt(userContext.getContentResolver(), name, NOTIFICATION_NOT_SHOWN) == NOTIFICATION_NOT_SHOWN; } finally { Binder.restoreCallingIdentity(callingIdentity); } return firstTime; } /** * Returns true if the Bluetooth Adapter's name and address is * locally cached Loading Loading @@ -1321,6 +1380,23 @@ public class BluetoothManagerService extends IBluetoothManager.Stub { synchronized (mReceiver) { mQuietEnableExternal = false; mEnableExternal = true; if (isAirplaneModeOn() && isApmEnhancementOn()) { setSettingsSecureInt(BLUETOOTH_APM_STATE, BLUETOOTH_ON_APM); setSettingsSecureInt(APM_USER_TOGGLED_BLUETOOTH, USED); if (isFirstTimeNotification(APM_BT_ENABLED_NOTIFICATION)) { final long callingIdentity = Binder.clearCallingIdentity(); try { mBluetoothAirplaneModeListener.sendApmNotification( "bluetooth_enabled_apm_title", "bluetooth_enabled_apm_message", APM_BT_ENABLED_NOTIFICATION); } catch (Exception e) { Log.e(TAG, "APM enhancement BT enabled notification not shown"); } finally { Binder.restoreCallingIdentity(callingIdentity); } } } // waive WRITE_SECURE_SETTINGS permission check sendEnableMsg(false, BluetoothProtoEnums.ENABLE_DISABLE_REASON_APPLICATION_REQUEST, packageName); Loading Loading @@ -1361,12 +1437,15 @@ public class BluetoothManagerService extends IBluetoothManager.Stub { } synchronized (mReceiver) { if (!isBluetoothPersistedStateOnAirplane()) { if (isAirplaneModeOn() && isApmEnhancementOn()) { setSettingsSecureInt(BLUETOOTH_APM_STATE, BLUETOOTH_OFF_APM); setSettingsSecureInt(APM_USER_TOGGLED_BLUETOOTH, USED); } if (persist) { persistBluetoothSetting(BLUETOOTH_OFF); } mEnableExternal = false; } sendDisableMsg(BluetoothProtoEnums.ENABLE_DISABLE_REASON_APPLICATION_REQUEST, packageName); } Loading Loading @@ -1571,7 +1650,7 @@ public class BluetoothManagerService extends IBluetoothManager.Stub { mBluetoothAirplaneModeListener.start(mBluetoothModeChangeHelper); } registerForProvisioningStateChange(); mBluetoothDeviceConfigListener = new BluetoothDeviceConfigListener(this, DBG); mBluetoothDeviceConfigListener = new BluetoothDeviceConfigListener(this, DBG, mContext); } /** Loading Loading @@ -2428,6 +2507,7 @@ public class BluetoothManagerService extends IBluetoothManager.Stub { Log.d(TAG, "MESSAGE_USER_SWITCHED"); } mHandler.removeMessages(MESSAGE_USER_SWITCHED); mBluetoothNotificationManager.createNotificationChannels(); /* disable and enable BT when detect a user switch */ if (mBluetooth != null && isEnabled()) { Loading service/java/com/android/server/bluetooth/BluetoothModeChangeHelper.java +71 −0 Original line number Diff line number Diff line Loading @@ -16,7 +16,11 @@ package com.android.server.bluetooth; import static com.android.server.bluetooth.BluetoothAirplaneModeListener.BLUETOOTH_APM_STATE; import static com.android.server.bluetooth.BluetoothAirplaneModeListener.BT_DEFAULT_APM_STATE; import android.annotation.RequiresPermission; import android.app.ActivityManager; import android.bluetooth.BluetoothA2dp; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothHearingAid; Loading @@ -24,8 +28,12 @@ import android.bluetooth.BluetoothLeAudio; import android.bluetooth.BluetoothProfile; import android.bluetooth.BluetoothProfile.ServiceListener; import android.content.Context; import android.content.pm.PackageManager; import android.content.res.Resources; import android.os.Process; import android.os.UserHandle; import android.provider.Settings; import android.util.Log; import android.widget.Toast; import com.android.internal.annotations.VisibleForTesting; Loading @@ -35,12 +43,16 @@ import com.android.internal.annotations.VisibleForTesting; * complex logic. */ public class BluetoothModeChangeHelper { private static final String TAG = "BluetoothModeChangeHelper"; private volatile BluetoothA2dp mA2dp; private volatile BluetoothHearingAid mHearingAid; private volatile BluetoothLeAudio mLeAudio; private final BluetoothAdapter mAdapter; private final Context mContext; private String mBluetoothPackageName; BluetoothModeChangeHelper(Context context) { mAdapter = BluetoothAdapter.getDefaultAdapter(); mContext = context; Loading Loading @@ -127,6 +139,24 @@ public class BluetoothModeChangeHelper { name, value); } /** * Helper method to get Settings Secure Int value */ public int getSettingsSecureInt(String name, int def) { Context userContext = mContext.createContextAsUser( UserHandle.of(ActivityManager.getCurrentUser()), 0); return Settings.Secure.getInt(userContext.getContentResolver(), name, def); } /** * Helper method to set Settings Secure Int value */ public void setSettingsSecureInt(String name, int value) { Context userContext = mContext.createContextAsUser( UserHandle.of(ActivityManager.getCurrentUser()), 0); Settings.Secure.putInt(userContext.getContentResolver(), name, value); } @VisibleForTesting public void showToastMessage() { Resources r = mContext.getResources(); Loading Loading @@ -158,4 +188,45 @@ public class BluetoothModeChangeHelper { } return leAudio.getConnectedDevices().size() > 0; } /** * Helper method to check whether BT should be enabled on APM */ public boolean isBluetoothOnAPM() { Context userContext = mContext.createContextAsUser( UserHandle.of(ActivityManager.getCurrentUser()), 0); int defaultBtApmState = getSettingsInt(BT_DEFAULT_APM_STATE); return Settings.Secure.getInt(userContext.getContentResolver(), BLUETOOTH_APM_STATE, defaultBtApmState) == 1; } /** * Helper method to retrieve BT package name with APM resources */ public String getBluetoothPackageName() { if (mBluetoothPackageName != null) { return mBluetoothPackageName; } var allPackages = mContext.getPackageManager().getPackagesForUid(Process.BLUETOOTH_UID); for (String candidatePackage : allPackages) { Resources resources; try { resources = mContext.getPackageManager() .getResourcesForApplication(candidatePackage); } catch (PackageManager.NameNotFoundException e) { // ignore, try next package Log.e(TAG, "Could not find package " + candidatePackage); continue; } catch (Exception e) { Log.e(TAG, "Error while loading package" + e); continue; } if (resources.getIdentifier("bluetooth_and_wifi_stays_on_title", "string", candidatePackage) == 0) { continue; } mBluetoothPackageName = candidatePackage; } return mBluetoothPackageName; } } Loading
android/app/res/values/strings.xml +5 −0 Original line number Diff line number Diff line Loading @@ -248,4 +248,9 @@ <string name="a2dp_sink_mbs_label">Bluetooth Audio</string> <string name="bluetooth_opp_file_limit_exceeded">Files bigger than 4GB cannot be transferred</string> <string name="bluetooth_connect_action">Connect to Bluetooth</string> <string name="bluetooth_enabled_apm_title">You turned on Bluetooth</string> <string name="bluetooth_enabled_apm_message">Your phone will keep Bluetooth on in airplane mode, unless you turn it off while in this mode</string> <string name="bluetooth_stays_on_title">Bluetooth stays on</string> <string name="bluetooth_and_wifi_stays_on_title">Bluetooth and Wi-Fi stays on</string> <string name="bluetooth_and_wifi_stays_on_message">Your phone will keep both on in airplane mode, unless you turn it off while in this mode</string> </resources>
service/java/com/android/server/bluetooth/BluetoothAirplaneModeListener.java +114 −7 Original line number Diff line number Diff line Loading @@ -18,6 +18,8 @@ package com.android.server.bluetooth; import android.annotation.RequiresPermission; import android.content.Context; import android.content.pm.PackageManager; import android.content.res.Resources; import android.database.ContentObserver; import android.os.Handler; import android.os.Looper; Loading @@ -41,18 +43,44 @@ public class BluetoothAirplaneModeListener { private static final String TAG = "BluetoothAirplaneModeListener"; @VisibleForTesting static final String TOAST_COUNT = "bluetooth_airplane_toast_count"; // keeps track of whether wifi should remain on in airplane mode public static final String WIFI_APM_STATE = "wifi_apm_state"; // keeps track of whether wifi and bt remains on notification was shown public static final String APM_WIFI_BT_NOTIFICATION = "apm_wifi_bt_notification"; // keeps track of whether bt remains on notification was shown public static final String APM_BT_NOTIFICATION = "apm_bt_notification"; // keeps track of whether airplane mode enhancement feature is enabled public static final String APM_ENHANCEMENT = "apm_enhancement_enabled"; // keeps track of whether user changed bt state in airplane mode public static final String APM_USER_TOGGLED_BLUETOOTH = "apm_user_toggled_bluetooth"; // keeps track of whether bt should remain on in airplane mode public static final String BLUETOOTH_APM_STATE = "bluetooth_apm_state"; // keeps track of what the default value for bt should be in airplane mode public static final String BT_DEFAULT_APM_STATE = "bt_default_apm_state"; // keeps track of whether user enabling bt notification was shown public static final String APM_BT_ENABLED_NOTIFICATION = "apm_bt_enabled_notification"; private static final int MSG_AIRPLANE_MODE_CHANGED = 0; public static final int NOTIFICATION_NOT_SHOWN = 0; public static final int NOTIFICATION_SHOWN = 1; public static final int UNUSED = 0; public static final int USED = 1; @VisibleForTesting static final int MAX_TOAST_COUNT = 10; // 10 times private final BluetoothManagerService mBluetoothManager; private final BluetoothAirplaneModeHandler mHandler; private final Context mContext; private BluetoothModeChangeHelper mAirplaneHelper; private BluetoothNotificationManager mNotificationManager; @VisibleForTesting int mToastCount = 0; BluetoothAirplaneModeListener(BluetoothManagerService service, Looper looper, Context context) { BluetoothAirplaneModeListener(BluetoothManagerService service, Looper looper, Context context, BluetoothNotificationManager notificationManager) { mBluetoothManager = service; mNotificationManager = notificationManager; mContext = context; mHandler = new BluetoothAirplaneModeHandler(looper); context.getContentResolver().registerContentObserver( Loading Loading @@ -117,9 +145,29 @@ public class BluetoothAirplaneModeListener { // BLUETOOTH_ON_AIRPLANE mode. mAirplaneHelper.setSettingsInt(Settings.Global.BLUETOOTH_ON, BluetoothManagerService.BLUETOOTH_ON_AIRPLANE); if (!isApmEnhancementEnabled() || !isBluetoothToggledOnApm()) { if (shouldPopToast()) { mAirplaneHelper.showToastMessage(); } } else { if (isWifiEnabledOnApm() && isFirstTimeNotification(APM_WIFI_BT_NOTIFICATION)) { try { sendApmNotification("bluetooth_and_wifi_stays_on_title", "bluetooth_and_wifi_stays_on_message", APM_WIFI_BT_NOTIFICATION); } catch (Exception e) { Log.e(TAG, "APM enhancement BT and Wi-Fi stays on notification not shown"); } } else if (!isWifiEnabledOnApm() && isFirstTimeNotification(APM_BT_NOTIFICATION)) { try { sendApmNotification("bluetooth_stays_on_title", "bluetooth_enabled_apm_message", APM_BT_NOTIFICATION); } catch (Exception e) { Log.e(TAG, "APM enhancement BT stays on notification not shown"); } } } return; } if (mAirplaneHelper != null) { Loading @@ -132,10 +180,69 @@ public class BluetoothAirplaneModeListener { if (mAirplaneHelper == null) { return false; } if (!mAirplaneHelper.isBluetoothOn() || !mAirplaneHelper.isAirplaneModeOn() || !mAirplaneHelper.isMediaProfileConnected()) { return false; boolean apmEnhancementUsed = isApmEnhancementEnabled() && isBluetoothToggledOnApm(); // APM feature disabled or user has not used the feature yet by changing BT state in APM // BT will only remain on in APM when media profile is connected if (!apmEnhancementUsed && mAirplaneHelper.isBluetoothOn() && mAirplaneHelper.isAirplaneModeOn() && mAirplaneHelper.isMediaProfileConnected()) { return true; } // APM feature enabled and user has used the feature by changing BT state in APM // BT will only remain on in APM based on user's last action in APM if (apmEnhancementUsed && mAirplaneHelper.isBluetoothOn() && mAirplaneHelper.isAirplaneModeOn() && mAirplaneHelper.isBluetoothOnAPM()) { return true; } // APM feature enabled and user has not used the feature yet by changing BT state in APM // BT will only remain on in APM if the default value is set to on if (isApmEnhancementEnabled() && !isBluetoothToggledOnApm() && mAirplaneHelper.isBluetoothOn() && mAirplaneHelper.isAirplaneModeOn() && mAirplaneHelper.isBluetoothOnAPM()) { return true; } return false; } private boolean isApmEnhancementEnabled() { return mAirplaneHelper.getSettingsInt(APM_ENHANCEMENT) == 1; } private boolean isBluetoothToggledOnApm() { return mAirplaneHelper.getSettingsSecureInt(APM_USER_TOGGLED_BLUETOOTH, UNUSED) == USED; } private boolean isWifiEnabledOnApm() { return mAirplaneHelper.getSettingsInt(Settings.Global.WIFI_ON) != 0 && mAirplaneHelper.getSettingsSecureInt(WIFI_APM_STATE, 0) == 1; } private boolean isFirstTimeNotification(String name) { return mAirplaneHelper.getSettingsSecureInt( name, NOTIFICATION_NOT_SHOWN) == NOTIFICATION_NOT_SHOWN; } /** * Helper method to send APM notification */ public void sendApmNotification(String titleId, String messageId, String notificationState) throws PackageManager.NameNotFoundException { String btPackageName = mAirplaneHelper.getBluetoothPackageName(); if (btPackageName == null) { Log.e(TAG, "Unable to find Bluetooth package name with " + "APM notification resources"); return; } Resources resources = mContext.getPackageManager() .getResourcesForApplication(btPackageName); int title = resources.getIdentifier(titleId, "string", btPackageName); int message = resources.getIdentifier(messageId, "string", btPackageName); mNotificationManager.sendApmNotification( resources.getString(title), resources.getString(message)); mAirplaneHelper.setSettingsSecureInt(notificationState, NOTIFICATION_SHOWN); } }
service/java/com/android/server/bluetooth/BluetoothDeviceConfigListener.java +34 −2 Original line number Diff line number Diff line Loading @@ -16,7 +16,12 @@ package com.android.server.bluetooth; import static com.android.server.bluetooth.BluetoothAirplaneModeListener.APM_ENHANCEMENT; import static com.android.server.bluetooth.BluetoothAirplaneModeListener.BT_DEFAULT_APM_STATE; import android.content.Context; import android.provider.DeviceConfig; import android.provider.Settings; import android.util.Log; import java.util.ArrayList; Loading @@ -35,10 +40,22 @@ public class BluetoothDeviceConfigListener { private final BluetoothManagerService mService; private final boolean mLogDebug; private final Context mContext; private static final int DEFAULT_APM_ENHANCEMENT = 0; private static final int DEFAULT_BT_APM_STATE = 0; private boolean mPrevApmEnhancement; private boolean mPrevBtApmState; BluetoothDeviceConfigListener(BluetoothManagerService service, boolean logDebug) { BluetoothDeviceConfigListener(BluetoothManagerService service, boolean logDebug, Context context) { mService = service; mLogDebug = logDebug; mContext = context; mPrevApmEnhancement = Settings.Global.getInt(mContext.getContentResolver(), APM_ENHANCEMENT, DEFAULT_APM_ENHANCEMENT) == 1; mPrevBtApmState = Settings.Global.getInt(mContext.getContentResolver(), BT_DEFAULT_APM_STATE, DEFAULT_BT_APM_STATE) == 1; DeviceConfig.addOnPropertiesChangedListener( DeviceConfig.NAMESPACE_BLUETOOTH, (Runnable r) -> r.run(), Loading @@ -59,6 +76,22 @@ public class BluetoothDeviceConfigListener { } Log.d(TAG, "onPropertiesChanged: " + String.join(",", flags)); } boolean apmEnhancement = properties.getBoolean( APM_ENHANCEMENT, mPrevApmEnhancement); if (apmEnhancement != mPrevApmEnhancement) { mPrevApmEnhancement = apmEnhancement; Settings.Global.putInt(mContext.getContentResolver(), APM_ENHANCEMENT, apmEnhancement ? 1 : 0); } boolean btApmState = properties.getBoolean( BT_DEFAULT_APM_STATE, mPrevBtApmState); if (btApmState != mPrevBtApmState) { mPrevBtApmState = btApmState; Settings.Global.putInt(mContext.getContentResolver(), BT_DEFAULT_APM_STATE, btApmState ? 1 : 0); } boolean foundInit = false; for (String name : properties.getKeyset()) { if (name.startsWith("INIT_")) { Loading @@ -72,5 +105,4 @@ public class BluetoothDeviceConfigListener { mService.onInitFlagsChanged(); } }; }
service/java/com/android/server/bluetooth/BluetoothManagerService.java +87 −7 Original line number Diff line number Diff line Loading @@ -21,6 +21,13 @@ import static android.os.PowerExemptionManager.TEMPORARY_ALLOW_LIST_TYPE_FOREGRO import static android.permission.PermissionManager.PERMISSION_GRANTED; import static android.permission.PermissionManager.PERMISSION_HARD_DENIED; import static com.android.server.bluetooth.BluetoothAirplaneModeListener.APM_BT_ENABLED_NOTIFICATION; import static com.android.server.bluetooth.BluetoothAirplaneModeListener.APM_ENHANCEMENT; import static com.android.server.bluetooth.BluetoothAirplaneModeListener.APM_USER_TOGGLED_BLUETOOTH; import static com.android.server.bluetooth.BluetoothAirplaneModeListener.BLUETOOTH_APM_STATE; import static com.android.server.bluetooth.BluetoothAirplaneModeListener.NOTIFICATION_NOT_SHOWN; import static com.android.server.bluetooth.BluetoothAirplaneModeListener.USED; import android.Manifest; import android.annotation.NonNull; import android.annotation.RequiresPermission; Loading Loading @@ -182,6 +189,9 @@ public class BluetoothManagerService extends IBluetoothManager.Stub { @VisibleForTesting static final int BLUETOOTH_ON_AIRPLANE = 2; private static final int BLUETOOTH_OFF_APM = 0; private static final int BLUETOOTH_ON_APM = 1; private static final int SERVICE_IBLUETOOTH = 1; private static final int SERVICE_IBLUETOOTHGATT = 2; Loading Loading @@ -227,6 +237,8 @@ public class BluetoothManagerService extends IBluetoothManager.Stub { private BluetoothDeviceConfigListener mBluetoothDeviceConfigListener; private BluetoothNotificationManager mBluetoothNotificationManager; // used inside handler thread private boolean mQuietEnable = false; private boolean mEnable; Loading Loading @@ -542,6 +554,8 @@ public class BluetoothManagerService extends IBluetoothManager.Stub { mUserManager = mContext.getSystemService(UserManager.class); mBluetoothNotificationManager = new BluetoothNotificationManager(mContext); mIsHearingAidProfileSupported = BluetoothProperties.isProfileAshaCentralEnabled().orElse(false); Loading Loading @@ -602,7 +616,8 @@ public class BluetoothManagerService extends IBluetoothManager.Stub { if (airplaneModeRadios == null || airplaneModeRadios.contains( Settings.Global.RADIO_BLUETOOTH)) { mBluetoothAirplaneModeListener = new BluetoothAirplaneModeListener( this, mBluetoothHandlerThread.getLooper(), context); this, mBluetoothHandlerThread.getLooper(), context, mBluetoothNotificationManager); } int systemUiUid = -1; Loading @@ -627,6 +642,13 @@ public class BluetoothManagerService extends IBluetoothManager.Stub { Settings.Global.AIRPLANE_MODE_ON, 0) == 1; } /** * Returns true if airplane mode enhancement feature is enabled */ private boolean isApmEnhancementOn() { return Settings.Global.getInt(mContext.getContentResolver(), APM_ENHANCEMENT, 0) == 1; } private boolean supportBluetoothPersistedState() { // Set default support to true to copy config default. return BluetoothProperties.isSupportPersistedStateEnabled().orElse(true); Loading Loading @@ -685,6 +707,43 @@ public class BluetoothManagerService extends IBluetoothManager.Stub { } } /** * Set the Settings Secure Int value for foreground user */ private void setSettingsSecureInt(String name, int value) { if (DBG) { Log.d(TAG, "Persisting Settings Secure Int: " + name + "=" + value); } // waive WRITE_SECURE_SETTINGS permission check final long callingIdentity = Binder.clearCallingIdentity(); try { Context userContext = mContext.createContextAsUser( UserHandle.of(ActivityManager.getCurrentUser()), 0); Settings.Secure.putInt(userContext.getContentResolver(), name, value); } finally { Binder.restoreCallingIdentity(callingIdentity); } } /** * Return whether APM notification has been shown */ private boolean isFirstTimeNotification(String name) { boolean firstTime = false; // waive WRITE_SECURE_SETTINGS permission check final long callingIdentity = Binder.clearCallingIdentity(); try { Context userContext = mContext.createContextAsUser( UserHandle.of(ActivityManager.getCurrentUser()), 0); firstTime = Settings.Secure.getInt(userContext.getContentResolver(), name, NOTIFICATION_NOT_SHOWN) == NOTIFICATION_NOT_SHOWN; } finally { Binder.restoreCallingIdentity(callingIdentity); } return firstTime; } /** * Returns true if the Bluetooth Adapter's name and address is * locally cached Loading Loading @@ -1321,6 +1380,23 @@ public class BluetoothManagerService extends IBluetoothManager.Stub { synchronized (mReceiver) { mQuietEnableExternal = false; mEnableExternal = true; if (isAirplaneModeOn() && isApmEnhancementOn()) { setSettingsSecureInt(BLUETOOTH_APM_STATE, BLUETOOTH_ON_APM); setSettingsSecureInt(APM_USER_TOGGLED_BLUETOOTH, USED); if (isFirstTimeNotification(APM_BT_ENABLED_NOTIFICATION)) { final long callingIdentity = Binder.clearCallingIdentity(); try { mBluetoothAirplaneModeListener.sendApmNotification( "bluetooth_enabled_apm_title", "bluetooth_enabled_apm_message", APM_BT_ENABLED_NOTIFICATION); } catch (Exception e) { Log.e(TAG, "APM enhancement BT enabled notification not shown"); } finally { Binder.restoreCallingIdentity(callingIdentity); } } } // waive WRITE_SECURE_SETTINGS permission check sendEnableMsg(false, BluetoothProtoEnums.ENABLE_DISABLE_REASON_APPLICATION_REQUEST, packageName); Loading Loading @@ -1361,12 +1437,15 @@ public class BluetoothManagerService extends IBluetoothManager.Stub { } synchronized (mReceiver) { if (!isBluetoothPersistedStateOnAirplane()) { if (isAirplaneModeOn() && isApmEnhancementOn()) { setSettingsSecureInt(BLUETOOTH_APM_STATE, BLUETOOTH_OFF_APM); setSettingsSecureInt(APM_USER_TOGGLED_BLUETOOTH, USED); } if (persist) { persistBluetoothSetting(BLUETOOTH_OFF); } mEnableExternal = false; } sendDisableMsg(BluetoothProtoEnums.ENABLE_DISABLE_REASON_APPLICATION_REQUEST, packageName); } Loading Loading @@ -1571,7 +1650,7 @@ public class BluetoothManagerService extends IBluetoothManager.Stub { mBluetoothAirplaneModeListener.start(mBluetoothModeChangeHelper); } registerForProvisioningStateChange(); mBluetoothDeviceConfigListener = new BluetoothDeviceConfigListener(this, DBG); mBluetoothDeviceConfigListener = new BluetoothDeviceConfigListener(this, DBG, mContext); } /** Loading Loading @@ -2428,6 +2507,7 @@ public class BluetoothManagerService extends IBluetoothManager.Stub { Log.d(TAG, "MESSAGE_USER_SWITCHED"); } mHandler.removeMessages(MESSAGE_USER_SWITCHED); mBluetoothNotificationManager.createNotificationChannels(); /* disable and enable BT when detect a user switch */ if (mBluetooth != null && isEnabled()) { Loading
service/java/com/android/server/bluetooth/BluetoothModeChangeHelper.java +71 −0 Original line number Diff line number Diff line Loading @@ -16,7 +16,11 @@ package com.android.server.bluetooth; import static com.android.server.bluetooth.BluetoothAirplaneModeListener.BLUETOOTH_APM_STATE; import static com.android.server.bluetooth.BluetoothAirplaneModeListener.BT_DEFAULT_APM_STATE; import android.annotation.RequiresPermission; import android.app.ActivityManager; import android.bluetooth.BluetoothA2dp; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothHearingAid; Loading @@ -24,8 +28,12 @@ import android.bluetooth.BluetoothLeAudio; import android.bluetooth.BluetoothProfile; import android.bluetooth.BluetoothProfile.ServiceListener; import android.content.Context; import android.content.pm.PackageManager; import android.content.res.Resources; import android.os.Process; import android.os.UserHandle; import android.provider.Settings; import android.util.Log; import android.widget.Toast; import com.android.internal.annotations.VisibleForTesting; Loading @@ -35,12 +43,16 @@ import com.android.internal.annotations.VisibleForTesting; * complex logic. */ public class BluetoothModeChangeHelper { private static final String TAG = "BluetoothModeChangeHelper"; private volatile BluetoothA2dp mA2dp; private volatile BluetoothHearingAid mHearingAid; private volatile BluetoothLeAudio mLeAudio; private final BluetoothAdapter mAdapter; private final Context mContext; private String mBluetoothPackageName; BluetoothModeChangeHelper(Context context) { mAdapter = BluetoothAdapter.getDefaultAdapter(); mContext = context; Loading Loading @@ -127,6 +139,24 @@ public class BluetoothModeChangeHelper { name, value); } /** * Helper method to get Settings Secure Int value */ public int getSettingsSecureInt(String name, int def) { Context userContext = mContext.createContextAsUser( UserHandle.of(ActivityManager.getCurrentUser()), 0); return Settings.Secure.getInt(userContext.getContentResolver(), name, def); } /** * Helper method to set Settings Secure Int value */ public void setSettingsSecureInt(String name, int value) { Context userContext = mContext.createContextAsUser( UserHandle.of(ActivityManager.getCurrentUser()), 0); Settings.Secure.putInt(userContext.getContentResolver(), name, value); } @VisibleForTesting public void showToastMessage() { Resources r = mContext.getResources(); Loading Loading @@ -158,4 +188,45 @@ public class BluetoothModeChangeHelper { } return leAudio.getConnectedDevices().size() > 0; } /** * Helper method to check whether BT should be enabled on APM */ public boolean isBluetoothOnAPM() { Context userContext = mContext.createContextAsUser( UserHandle.of(ActivityManager.getCurrentUser()), 0); int defaultBtApmState = getSettingsInt(BT_DEFAULT_APM_STATE); return Settings.Secure.getInt(userContext.getContentResolver(), BLUETOOTH_APM_STATE, defaultBtApmState) == 1; } /** * Helper method to retrieve BT package name with APM resources */ public String getBluetoothPackageName() { if (mBluetoothPackageName != null) { return mBluetoothPackageName; } var allPackages = mContext.getPackageManager().getPackagesForUid(Process.BLUETOOTH_UID); for (String candidatePackage : allPackages) { Resources resources; try { resources = mContext.getPackageManager() .getResourcesForApplication(candidatePackage); } catch (PackageManager.NameNotFoundException e) { // ignore, try next package Log.e(TAG, "Could not find package " + candidatePackage); continue; } catch (Exception e) { Log.e(TAG, "Error while loading package" + e); continue; } if (resources.getIdentifier("bluetooth_and_wifi_stays_on_title", "string", candidatePackage) == 0) { continue; } mBluetoothPackageName = candidatePackage; } return mBluetoothPackageName; } }