3

Goal

  • If an already connected bluetooth device disconnects, and an Activity is already running, close the Activity

Problem

  • When the bluetooth device connection state changes through BluetoothAdapterProperties: CONNECTION_STATE_CHANGE, it seems like a new Activity is created or the current one restarts.

There is nothing in the code that listens and/or should react to bluetooth connection state changes.

The problem manifests itself in the use of BroadcastReceivers which in turn starts the Activity using intents. For some reason the Activity keep running through its lifecycle, spawning up new windows, even if the only change in bluetooth connectivity is BluetoothAdapterProperties: CONNECTION_STATE_CHANGE

I've tested this solely on a Nexus 6P with Android N. I have no idea yet what kind of implications this implementation means for any other devices yet. But I at least need to get this working on one device.

UPDATE

I have done a fair bit of experimentation and found that if I don't register the BroadcastReceiver in AndroidManifest, the problem with onDestroy being called disappears. But, I want to be able to react to Bluetooth connecting devices, so that I can launch my activity and then process input. If the activity gets destroyed every time a new device connects/disconnects, this won't work at all. What's the reasoning for having the BroadcastReceiver finishing an activity if it's already running and can I control that behaviour?

UPDATE 2

I can also conclude that disabling the statically declared BroadcastReceiver using this method https://stackoverflow.com/a/6529365/975641 doesn't improve things. As soon as the Manifest-BroadcastReceiver catches the ACL_CONNECTED intent from Android, and start my custom activity, it will ruthlessly call onDestroy on it when the connection state changes (which is usually just before an ACL_DISCONNECTED). It does not matter if I have ACL_DISCONNECTED declared in the Manifest or not. As long as I have my receiver listening for ACL_CONNECTED intents and I launch my Activity based on that, onDestroy will be called when the connection state changes. So frustrating.

Manifest

<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.VIBRATE" />

<application
    android:allowBackup="true"
    android:icon="@mipmap/ic_launcher"
    android:label="@string/app_name"
    android:supportsRtl="true"
    android:theme="@style/AppTheme">
    <activity android:name=".MainActivity">
        <intent-filter>
            <action android:name="android.intent.action.MAIN" />

            <category android:name="android.intent.category.LAUNCHER" />
        </intent-filter>
    </activity>
    <activity
        android:name=".BtActivity"
        android:launchMode="singleTop" />
    <receiver android:name=".BtConnectionBroadcastReceiver" android:priority="100000">
        <intent-filter>
            <action android:name="android.bluetooth.device.action.ACL_CONNECTED" />
            <action android:name="android.bluetooth.device.action.ACL_DISCONNECTED" />
            <action android:name="android.bluetooth.device.action.ACL_DISCONNECT_REQUESTED" />
            <action android:name="android.intent.action.MEDIA_BUTTON" />
            <action android:name="android.media.VOLUME_CHANGED_ACTION" />
        </intent-filter>
    </receiver>
</application>

BtConnectionBroadcastReceiver

public class BtConnectionBroadcastReceiver extends BroadcastReceiver {
    private static final String TAG = "BT";
    public static final String BROADCAST_ACTION_CONNECTED = "CONNECTED";
    public static final String BROADCAST_ACTION_DISCONNECTED = "DISCONNECTED";
    SharedPreferences mSharedPreferences;

    @Override
    public void onReceive(Context context, Intent intent) {
        String action = intent.getAction();

        // When discovery finds a device
        if (BluetoothDevice.ACTION_ACL_CONNECTED.equals(action)) {
            // Get the BluetoothDevice object from the Intent
            Log.d(TAG, "DEVICE CONNECTED");
            BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
            Log.d("DEVICE NAME", device.getName());
            Log.d("DEVICE ADDRESS", device.getAddress());
            Intent i = new Intent(context, BtActivity.class);
            i.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
            context.startActivity(i);
        } else if (BluetoothDevice.ACTION_ACL_DISCONNECTED.equals(action)) {
            Log.d(TAG, "DEVICE DISCONNECTED");
            intent = new Intent();
            intent.setAction(BtConnectionBroadcastReceiver.BROADCAST_ACTION_DISCONNECTED);
            context.sendBroadcast(intent);
        }
    }

BtActivity

public class BtActivity extends AppCompatActivity {
private static final String TAG = "BT";
Window mWindow;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    setContentView(R.layout.activity_bt);

    Log.d(TAG, "onCreate");
    IntentFilter filter = new IntentFilter(BtConnectionBroadcastReceiver.INTENT_FILTER);
    filter.addAction(BtConnectionBroadcastReceiver.BROADCAST_ACTION_CONNECTED);
    filter.addAction(BtConnectionBroadcastReceiver.BROADCAST_ACTION_DISCONNECTED);
    //registerReceiver(mReceiver, filter);

    mWindow = getWindow();
    WindowManager.LayoutParams params = new WindowManager.LayoutParams();
    //params.screenBrightness = WindowManager.LayoutParams.BRIGHTNESS_OVERRIDE_OFF;
    params.screenBrightness = 0.2f;
    mWindow.setAttributes(params);
    mWindow.addFlags(WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD);
    mWindow.addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED);
    mWindow.addFlags(WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON);
    mWindow.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
    mWindow.addFlags(WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE);
    mWindow.getDecorView().setSystemUiVisibility(
            View.SYSTEM_UI_FLAG_LAYOUT_STABLE |
                    View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION |
                    View.SYSTEM_UI_FLAG_HIDE_NAVIGATION |
                    View.SYSTEM_UI_FLAG_FULLSCREEN |
                    View.SYSTEM_UI_FLAG_IMMERSIVE);
    }

@Override
protected void onResume() {
    super.onResume();
    Log.d(TAG, "onResume");
}

@Override
protected void onDestroy() {
    super.onDestroy();
    Log.d(TAG, "onDestroy");
}

BroadcastReceiver mReceiver = new BroadcastReceiver() {

    @Override
    public void onReceive(Context context, Intent intent) {
        Log.d(TAG, "BROADCAST RECEIVED IN ACTIVITY");
        String mac;
        if(intent.getAction().equals(BtConnectionBroadcastReceiver.BROADCAST_DEVICE_CONNECTED)) {
            Log.d(TAG, "CONNECT BROADCAST RECEIVED");
            mac = intent.getStringExtra("mac");
            checkConnectedDevice(mac, true); // This adds a device to an internal list
            Log.d(TAG, "Activity nr of devices:" +mNrOfDevices);
        }
        if(intent.getAction().equals(BtConnectionBroadcastReceiver.BROADCAST_DEVICE_DISCONNECTED)) {
            Log.d(TAG, "DISCONNECT BROADCAST RECEIVED");
            mac = intent.getStringExtra("mac");
            checkConnectedDevice(mac, false); // This removes a device from an internal list
            Log.d(TAG, "Activity nr of devices:" +mNrOfDevices);
            if(mNrOfDevices < 1) {
                Log.d(TAG, "No more connected devices");
                finish();
            }
        }
        abortBroadcast();
    }
};
}

When I run this code, I get the following chain:

  1. Start MainActivity (not included, it only contains an activity with the default main layout, so that the applications receiver is registered)
  2. Switch on a bluetooth device (This has been paired earlier, so android knows about it)
  3. Wait until it connects and get this:
    • DEVICE CONNECTED
    • onCreate
    • onResume
  4. Switch off the bluetooth device and I then get this:
    • DEVICE DISCONNECTED
    • onDestroy
    • onCreate
    • onResume

I can't grasp why the activity is getting destroyed restarting at this point. The activity is already running, the BroadcastReceiver only sends a broadcast to an already running activity. I can't figure out why there's a reason for the Activity to kill itself and then restart again. This leaves me in a state of the Activity still running, but it is not the original Activity that was started.

I do however see something in the logcats that seem to have something to do with this, and it's in this sequencing;

06-02 15:45:09.156 26431 26431 D BT : DEVICE DISCONNECTED

06-02 15:45:09.213 19547 19547 D BluetoothAdapterService: handleMessage() - MESSAGE_PROFILE_CONNECTION_STATE_CHANGED

06-02 15:45:09.213 26431 26431 D BT : onDestroy

06-02 15:45:09.214 19547 19547 D BluetoothAdapterProperties: CONNECTION_STATE_CHANGE: FF:FF:20:00:C1:47: 2 -> 0

06-02 15:45:09.216 3502 3805 D CachedBluetoothDevice: onProfileStateChanged: profile HID newProfileState 0

06-02 15:45:09.237 414 414 W SurfaceFlinger: couldn't log to binary event log: overflow.

06-02 15:45:09.239 26431 26431 D BT : onCreate

06-02 15:45:09.243 26431 26431 D BT : onResume

shellström
  • 1,417
  • 13
  • 29
  • is BtActivity listening for the broadcast BROADCAST_ACTION_DISCONNECTED ? If so, can you post the code snippet of onReceive() of the broadcast receiver which is listening for BROADCAST_ACTION_DISCONNECTED action. ? – GRK Jun 05 '17 at 16:46
  • I'll do it as soon as I can. I'm currently at an Aerosmith concert so access to code is spotty at best. Latest I'll have it for you by tomorrow. – shellström Jun 05 '17 at 16:58
  • I've added the code for the BroadcastReceiver inside BtActivity. – shellström Jun 06 '17 at 09:58
  • if(mNrOfDevices < 1) { Log.d(TAG, "No more connected devices"); finish(); } – GRK Jun 06 '17 at 16:12
  • if(mNrOfDevices < 1) { Log.d(TAG, "No more connected devices"); finish(); } initially one device is connected, and when that gets disconnected, the above code finishes the activity. But I did not see any code in the provided snippets that starts the activity without receiving any broadcast. You probably will have to search in the code where and all BtActivity is getting launched. Inside receiver in the activity, you have to use if-else instead of 2 if stmts for optimization, though condition satisfies for only one action.(not related to pbm) – GRK Jun 06 '17 at 16:20
  • I have figured out one thing. The problem is in the statically declared BroadcastReceiver in my XML. I tried with only having the dynamically declared receiver in the BtActivity, commenting out the receiver and intent-filter in the AndroidManifest, and that made the onDestroy calls stop occurring. I just don't understand how or even if I can prevent that onDestory from happening even when I do have the receiver declared in the Manifest... – shellström Jun 06 '17 at 21:07
  • I added another update of findings. It's really circling in the problem now, however there's no sign of a solution on how to control the behaviour. – shellström Jun 07 '17 at 11:29
  • And everything comes to an end. This https://developer.android.com/guide/components/broadcasts.html#effects_on_process_state explains what I think is going on, in the fact that the receiver actually pulls the Activity down with it, once it's done running. – shellström Jun 07 '17 at 18:44

3 Answers3

6

In the AndroidManifest.xml add the following for the Activity, it is worked for me.

android:configChanges="keyboard|keyboardHidden"
Tamás
  • 93
  • 2
  • 10
0

Having read this https://developer.android.com/guide/components/broadcasts.html#effects_on_process_state I can probably safely conclude that the reason for why onDestroy gets called is because the receiver affects the process in which it is run, effectively meaning when the receiver has run its onReceive method, it will destroy itself and take the Activity with it.

I would of course have wished it was working differently, but I believe this is what effectively is going on and need to take another approach.

shellström
  • 1,417
  • 13
  • 29
0

I know this answer is very late, but I was facing this issue with my activity tag. In Manifest file I have added below line for configChange.

android:configChanges="keyboard|orientation|screenSize|keyboardHidden|navigation|screenLayout"

Now my application does not kill itself.

Ruli
  • 2,592
  • 12
  • 30
  • 40
Raheel
  • 1