Search code examples
androidbluetoothbroadcastreceiveralarmmanager

Can't get an alarmmanager to trigger scanning of bluetooth devices


I am a beginner with Android and Java (I was fairly advanced in Z80 machine language back in the eighties). I want to create an app to scan for bluetooth devices every 5 minutes in the background. The aim is to monitor usage of various devices around my household. I have 3 classes: MainActivity which has code for the UI, MbtScanner which as code for the scanning for bluetooth devices and BluetoothTScheduler which has the code for calling the scan when the alarm goes. The alarm is set to repeat every 60 seconds for testing purposes but once the code works I will set it to every 5 minutes. The results are then written to file.

The scanning works well when called from MainActivity but not when called from the BluetoothTScheduler as a result of the alarmmanager.

I have searched stackoverflow and the internet for answers and had no luck. I think the problem lies with the context I am passing to MbtScanner activity which is the wrong type of context but I don't really understand the problem and don't know how to solve it.

This is the code for my MainActivity

public class MainActivity extends AppCompatActivity {

AlarmManager alarmManager;
Intent intent;
public PendingIntent pendingIntent;

MbtScanner mbtscanner;
BluetoothTScheduler bluetoothTScheduler;

public static ArrayAdapter<String> mArrayAdapter;
ListView listView;



@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    listView = (ListView) findViewById(R.id.listView);
    mArrayAdapter = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, 0);
    listView.setAdapter(mArrayAdapter);

    mbtscanner = new MbtScanner(this);

    setScheduler();
    mbtscanner.mbtScannerInit();
    mbtscanner.scanResult();
    setBtReceiver();
}


// SCAN FOR BLUETOOTH DEVICES - CALLED BY UI SCAN BUTTON
public void btScan(View v) {
    if (!mbtscanner.stillScanning) {
        mArrayAdapter.clear();
    }
    mbtscanner.mbtScan();
}


// ASK USER TO SWITCH ON BLUETOOTH (CALLED IF BLUETOOTH OFF)
private void turnOnBT() {
    Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
    startActivityForResult(enableBtIntent, 1);
}



// SET BLUETOOTH RECEIVER AND IF FOUND CAPTURE DATA TO FILE AND TO LISTVIEW
private void setBtReceiver() {

    //CHECK FOR BLUETOOTH CAPABILITY AND IF IT IS SWITHCED ON
    if (mbtscanner.mBluetoothAdapter == null) {
        Toast.makeText(getApplicationContext(), "Bluetooth not supported", Toast.LENGTH_SHORT).show();
        finish();
    } else {
        if (!mbtscanner.mBluetoothAdapter.isEnabled()) {
            turnOnBT();
        }

        if (mbtscanner.mBluetoothAdapter.isDiscovering()) mbtscanner.mBluetoothAdapter.cancelDiscovery();
    }


}

public static void updateListView(String s){
    mArrayAdapter.add(s);
}



@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    super.onActivityResult(requestCode, resultCode, data);

    // IF USER REFUSES TO SWITCH ON BLUETOOTH THEN TERMINATE APPLICATION
    if (resultCode == RESULT_CANCELED) {
        Toast.makeText(getApplicationContext(), "Bluetooth must be enabled to Continue", Toast.LENGTH_SHORT).show();
        finish();
    }
}


@Override
protected void onPause() {

    Toast.makeText(this, "On Pause", Toast.LENGTH_SHORT).show();

    super.onPause();
}

@Override
protected void onResume() {
    super.onResume();
    // init();
    // setBtReceiver();
}


@Override
protected void onDestroy() {
    Toast.makeText(this, "On Destroy", Toast.LENGTH_SHORT).show();
    if (mbtscanner.mBluetoothAdapter.isDiscovering()) {
        mbtscanner.mBluetoothAdapter.cancelDiscovery();
    }
    unregisterReceiver(mbtscanner.mReceiver);

    super.onDestroy();
}


// THIS IS CALLED FROM A UI BUTTON "CANCEL ALARM"
public void cancelAlarm(View view) {
    alarmManager = (AlarmManager) getSystemService(ALARM_SERVICE);
    intent = new Intent(this, BluetoothTScheduler.class);
    pendingIntent = PendingIntent.getBroadcast(
            this.getApplicationContext(), 234324243, intent, 0);
    alarmManager.cancel(pendingIntent);
    Toast.makeText(this, "Alarm cancelled", Toast.LENGTH_LONG).show();
}



public void setScheduler() {

    bluetoothTScheduler = new BluetoothTScheduler();

    alarmManager = (AlarmManager) getSystemService(ALARM_SERVICE);
    intent = new Intent(this, BluetoothTScheduler.class);
    pendingIntent = PendingIntent.getBroadcast(
            this.getApplicationContext(), 234324243, intent, 0);

    alarmManager.setRepeating(AlarmManager.RTC_WAKEUP, System.currentTimeMillis()
            + (30 * 1000), 60 * 1000, pendingIntent);
    Toast.makeText(this, "Alarm set in 10 seconds for every 60 seconds", Toast.LENGTH_LONG).show();

}
}

This is the code for my MbtScanner class:

public class MbtScanner {


public String path = Environment.getExternalStorageDirectory().getAbsolutePath() + "/btTracker";
FileWriter fileWriter;
File file;

ProgressDialog progress;

Context mContext;

BluetoothAdapter mBluetoothAdapter;
IntentFilter filter;
BroadcastReceiver mReceiver;
boolean stillScanning = false;

String s;
int count =0;

public MbtScanner(Context mContext) {
    this.mContext = mContext;
}


public void mbtScan (){
    mBluetoothAdapter.startDiscovery();
    stillScanning = true;

    // DIALOG BOX SAYING PLEASE WAIT
    progress = new ProgressDialog(mContext);
    progress.setTitle("Scanning");
    progress.setMessage("Please wait...");
    //   progress.setCancelable(false);
    progress.show();

}

public void mbtScannerInit(){
    mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
    filter = new IntentFilter(BluetoothDevice.ACTION_FOUND);


    File dir = new File(path);
    dir.mkdirs();
    file = new File(path + "/savedfile.csv");
}


public void scanResult() {
    mReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            String action = intent.getAction();
            // When discovery finds a device


            // IF DEVICE FOUND
            if (BluetoothDevice.ACTION_FOUND.equals(action)) {
                // Get the BluetoothDevice object from the Intent


                // CAPTURE DEVICE DATA TO LISTVIEW
                BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);

                count++;

                int rssi = intent.getShortExtra(BluetoothDevice.EXTRA_RSSI, Short.MIN_VALUE);
                String sName, sAddress, sUuids, sDate, sFile;
                int iBondState, iMajorClass;

                sName = device.getName();
                sAddress = device.getAddress();
                iBondState = device.getBondState();
                iMajorClass = device.getBluetoothClass().getMajorDeviceClass();
                sUuids = "" + device.getUuids();
                sDate = DateFormat.getDateTimeInstance().format(new Date());

                s = "Count= " + count + "\n";
                s += "Name= " + sName + "\n";
                s += "Address= " + sAddress + "\n";
                s += "Bond state= " + iBondState + ";  ";
                s += "Major class= " + iMajorClass + "\n";
                s += "Uuids= " + sUuids + ";  ";
                s += "RSSI= " + rssi + "dBm" + "\n";
                s += "Date= " + sDate + "\n";

                MainActivity.updateListView(s);

                // CREATE STRING OF DEVICE DATA TO SAVE TO FILE
                sFile = count + "," + sName + "," + sAddress + "," + iBondState + "," + iMajorClass + "," + sUuids + "," + rssi + "," + sDate;


                // SAVE DEVICE DATA TO FILE
                try {
                    commitToFile(sFile);
                } catch (IOException e) {
                    e.printStackTrace();
                }

            } else if (BluetoothAdapter.ACTION_DISCOVERY_STARTED.equals(action)) {
                Toast.makeText(mContext, "Discovery started", Toast.LENGTH_SHORT).show();

                // SCANNING FINISHED SO INFORM USER
            } else if (BluetoothAdapter.ACTION_DISCOVERY_FINISHED.equals(action)) {
                progress.dismiss();
                Toast.makeText(mContext, "Discovery finished. Last device found: " + s, Toast.LENGTH_SHORT).show();
                stillScanning = false;


                // IF SOMEHOW BLUETOOTH HAS BEEN SWITCHED OFF THEN TRY TO SWITCH IT BACK ON
            } else if (BluetoothAdapter.ACTION_STATE_CHANGED.equals(action)) {
                if (mBluetoothAdapter.getState() == mBluetoothAdapter.STATE_OFF) {
                    // Toast.makeText(mActivity, "Bluetooth state changed", Toast.LENGTH_SHORT).show();

                    // turnOnBT(); ALTERNATIVE CODE TO NOTIFY USER THAT APP CAN NO LONGER WORK BCAUSE BLUTOOTH IS OFF, AND CANCEL ALARM
                }
            }
        }
    };


    mContext.registerReceiver(mReceiver, filter); 
    filter = new IntentFilter(BluetoothAdapter.ACTION_DISCOVERY_STARTED);
    mContext.registerReceiver(mReceiver, filter);
    filter = new IntentFilter(BluetoothAdapter.ACTION_DISCOVERY_FINISHED);
    mContext.registerReceiver(mReceiver, filter);
    filter = new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED);
    mContext.registerReceiver(mReceiver, filter);

}

// SAVE DATA TO FILE
private void commitToFile(String str) throws IOException {
    fileWriter = new FileWriter(file, true);
    BufferedWriter bufferWriter = new BufferedWriter(fileWriter);
    PrintWriter printWriter = new PrintWriter(bufferWriter);

    printWriter.print(str + "\n");
    printWriter.close();

    Toast.makeText(mContext, "written to file: ", Toast.LENGTH_SHORT).show();
}

}

This is the code for my for the BluetoothTScheduler class which is called by the Alarmmanager:

public class BluetoothTScheduler extends BroadcastReceiver  {

public static final String MyPREFERENCES = "MyPrefs" ;
SharedPreferences sharedpreferences;
int brCounter;
MbtScanner sbtscanner;



@Override
public void onReceive(Context context, Intent intent) {
    sharedpreferences = context.getSharedPreferences(MyPREFERENCES, Context.MODE_PRIVATE);
    SharedPreferences.Editor editor = sharedpreferences.edit();
    brCounter = sharedpreferences.getInt("counter", 0);

    Vibrator vibrator = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE);
    vibrator.vibrate(1000);
    Toast.makeText(context, "Alarm...." + brCounter, Toast.LENGTH_LONG).show();

    brCounter = brCounter +1;
    editor.putInt("counter", brCounter);
    editor.commit();



    sbtscanner = new MbtScanner(context);
    sbtscanner.mbtScannerInit();
    if (sbtscanner.mBluetoothAdapter.isDiscovering()) sbtscanner.mBluetoothAdapter.cancelDiscovery();
    sbtscanner.scanResult();

}

}

This is my manifest file:

<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<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>
    <receiver android:name="BluetoothTScheduler" >
    </receiver>



</application>

Here is the logcat:

D/InputMethodManager: windowDismissed mLockisused = false
D/AndroidRuntime: Shutting down VM
E/AndroidRuntime: FATAL EXCEPTION: main
E/AndroidRuntime: Process: com.orinocosolutions.bluetracker2, PID: 29905
E/AndroidRuntime: java.lang.RuntimeException: Unable to start receiver com.orinocosolutions.bluetracker2.BluetoothTScheduler: android.content.ReceiverCallNotAllowedException: BroadcastReceiver components are not allowed to register to receive intents
E/AndroidRuntime:     at android.app.ActivityThread.handleReceiver(ActivityThread.java:3508)
E/AndroidRuntime:     at android.app.ActivityThread.access$1900(ActivityThread.java:218)
E/AndroidRuntime:     at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1795)
E/AndroidRuntime:     at android.os.Handler.dispatchMessage(Handler.java:102)
E/AndroidRuntime:     at android.os.Looper.loop(Looper.java:145)
E/AndroidRuntime:     at android.app.ActivityThread.main(ActivityThread.java:6917)
E/AndroidRuntime:     at java.lang.reflect.Method.invoke(Native Method)
E/AndroidRuntime:     at java.lang.reflect.Method.invoke(Method.java:372)
E/AndroidRuntime:     at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1404)
E/AndroidRuntime:     at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1199)
E/AndroidRuntime:  Caused by: android.content.ReceiverCallNotAllowedException: BroadcastReceiver components are not allowed to register to receive intents
E/AndroidRuntime:     at android.app.ReceiverRestrictedContext.registerReceiver(ContextImpl.java:275)
E/AndroidRuntime:     at android.app.ReceiverRestrictedContext.registerReceiver(ContextImpl.java:264)
E/AndroidRuntime:     at com.orinocosolutions.bluetracker2.MbtScanner.scanResult(MbtScanner.java:166)
E/AndroidRuntime:     at com.orinocosolutions.bluetracker2.BluetoothTScheduler.onReceive(BluetoothTScheduler.java:42)
E/AndroidRuntime:     at android.app.ActivityThread.handleReceiver(ActivityThread.java:3501)
E/AndroidRuntime:     at android.app.ActivityThread.access$1900(ActivityThread.java:218) 
E/AndroidRuntime:     at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1795) 
E/AndroidRuntime:     at android.os.Handler.dispatchMessage(Handler.java:102) 
E/AndroidRuntime:     at android.os.Looper.loop(Looper.java:145) 
E/AndroidRuntime:     at android.app.ActivityThread.main(ActivityThread.java:6917) 
E/AndroidRuntime:     at java.lang.reflect.Method.invoke(Native Method) 
E/AndroidRuntime:     at java.lang.reflect.Method.invoke(Method.java:372)

Any help is very much appreciated.


Solution

  • It looks like all you need to do to fix the error is change this:

    sbtscanner = new MbtScanner(context);
    

    To This:

    sbtscanner = new MbtScanner(context.getApplicationContext());
    

    This is because inside the scanResult() method in MbtScanner, you are using this Context to register a BroadcastReceiver at runtime, and the Context passed into the onReceive() method of BluetoothTScheduler is not allowed to be used to register another BroadcastReceiver.

    From the documentation:

    This exception is thrown from registerReceiver(BroadcastReceiver, IntentFilter) and bindService(Intent, ServiceConnection, int) when these methods are being used from an BroadcastReceiver component. In this case, the component will no longer be active upon returning from receiving the Intent, so it is not valid to use asynchronous APIs.

    With the fix above, your MbtScanner class will just use the application context when called from the BroadcastReceiver that was triggered from the Alarm.