Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 1ff93b6c authored by Treehugger Robot's avatar Treehugger Robot Committed by Gerrit Code Review
Browse files

Merge "Bluetooth APM enhancement"

parents 62ea5bdd 3198c171
Loading
Loading
Loading
Loading
+5 −0
Original line number Diff line number Diff line
@@ -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>
+114 −7
Original line number Diff line number Diff line
@@ -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;
@@ -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(
@@ -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) {
@@ -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);
    }
}
+34 −2
Original line number Diff line number Diff line
@@ -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;
@@ -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(),
@@ -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_")) {
@@ -72,5 +105,4 @@ public class BluetoothDeviceConfigListener {
                    mService.onInitFlagsChanged();
                }
            };

}
+87 −7
Original line number Diff line number Diff line
@@ -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;
@@ -180,6 +187,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;

@@ -224,6 +234,8 @@ public class BluetoothManagerService extends IBluetoothManager.Stub {

    private BluetoothDeviceConfigListener mBluetoothDeviceConfigListener;

    private BluetoothNotificationManager mBluetoothNotificationManager;

    // used inside handler thread
    private boolean mQuietEnable = false;
    private boolean mEnable;
@@ -539,6 +551,8 @@ public class BluetoothManagerService extends IBluetoothManager.Stub {

        mUserManager = mContext.getSystemService(UserManager.class);

        mBluetoothNotificationManager = new BluetoothNotificationManager(mContext);

        mIsHearingAidProfileSupported =
                BluetoothProperties.isProfileAshaCentralEnabled().orElse(false);

@@ -599,7 +613,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;
@@ -624,6 +639,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);
@@ -682,6 +704,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
@@ -1299,6 +1358,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);
@@ -1339,12 +1415,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);
        }
@@ -1549,7 +1628,7 @@ public class BluetoothManagerService extends IBluetoothManager.Stub {
            mBluetoothAirplaneModeListener.start(mBluetoothModeChangeHelper);
        }
        registerForProvisioningStateChange();
        mBluetoothDeviceConfigListener = new BluetoothDeviceConfigListener(this, DBG);
        mBluetoothDeviceConfigListener = new BluetoothDeviceConfigListener(this, DBG, mContext);
    }

    /**
@@ -2397,6 +2476,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()) {
+71 −0
Original line number Diff line number Diff line
@@ -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;
@@ -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;
@@ -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;
@@ -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();
@@ -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