I would like to test my BroadcastReceiver, which depends on sticky broadcasts, with Robolectric. By default Robolectric does not support sticky broadcasts so I created my custom Context to get sticky broadcasts working like this:
public class MyContext extends MockContext {
public MyContext() {
super();
}
#Override
public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter) {
if(receiver == null) { // A sticky broadcast was requested
Intent request = new Intent();
String action = filter.getAction(0);
if(Intent.ACTION_BATTERY_CHANGED.equals(action)) {
request.putExtra(BatteryManager.EXTRA_PLUGGED, 1);
} else if(Intent.ACTION_HEADSET_PLUG.equals(action)) {
request.putExtra("state", 1);
}
return request;
}
return super.registerReceiver(receiver, filter);
}
}
My problem is that I have to use RuntimeEnvironment.application.getApplicationContext to get a valid Context object (I tried to simply call the constructor of my custom Context but that does not work). So how can I get a valid instance of my custom Context or isn't that possible with robolectric?
EDIT: Here is the code from my test and my BroadcastReceiver:
#Before
public void setup() {
context = RuntimeEnvironment.application.getApplicationContext();
receiver = new MyBroadcastReceiver(); // Create Receiver
}
#After
public void finish() {
context.unregisterReceiver(receiver);
}
#Test
public void validateUsbChargingChange() {
IntentFilter filter = new IntentFilter("android.intent.action.ACTION_POWER_CONNECTED");
context.registerReceiver(receiver, filter);
// Simmulate SocketCharging by sending the corresponding Intent
Intent chargingChange = new Intent("android.intent.action.ACTION_POWER_CONNECTED");
RuntimeEnvironment.application.sendBroadcast(chargingChange);
validatePreferences();
}
BroadcastReceiver:
#Override
public void onReceive(Context context, Intent intent) {
IntentFilter iFilter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED);
Intent chargingIntent = appContext.registerReceiver(null, iFilter); // sticky
int pluggedState = chargingIntent.getIntExtra(BatteryManager.EXTRA_PLUGGED, -1);
boolean usbCharge = (pluggedState == BatteryManager.BATTERY_PLUGGED_USB);
if(usbCharge) { /* Write values to preferences */ }
}
you can explicitly sendStickBroadcast in you test class constructor, just like below
#RunWith(RobolectricTestRunner.class)
public class BatterySettingActivityTest {
{
ApplicationProvider.getApplicationContext().sendStickyBroadcast(new Intent(Intent.ACTION_BATTERY_CHANGED).putExtra(BatteryManager.EXTRA_LEVEL, 75).putExtra(BatteryManager.EXTRA_PLUGGED, 0));
}
#Rule
public ActivityScenarioRule<BatterySettingActivity> activityScenarioRule = new ActivityScenarioRule<>(BatterySettingActivity.class);
#Test
public void testStickBroadcast() {
activityScenarioRule.getScenario().onActivity(activity -> {
System.out.println("intentForStickyBroadcast=" + activity.registerReceiver(null, new IntentFilter(Intent.ACTION_BATTERY_CHANGED)));
});
}
}
Related
I need to invoke a method on main thread, for that purpose i am using #UiThreadTest and calling the method inside my test.
That method is responsible to send broadcast receiver.
So i am registering the receiver and setting a boolean value to test if broadcast has been sent or not.
In logs i am able to see the broadcast is executed.
But getting assertion failure error inside test.
Is this some threading issue?
//broadcase receiver
private class Receiver extends BroadcastReceiver {
#Override
public void onReceive(Context context, final Intent intent) {
if (mUiUpdateReceiver != null && intent.getAction().equals(MessageCenter.INTENT_UI_REFRESH)) {
mbroadcastFlag = true;
}
}
}
//test
#UiThreadTest
public void test_broadcast () throws Exception{
mUiUpdateReceiver = new Receiver();
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(SampleClass.INTENT_REFRESH);
LocalBroadcastManager.getInstance(mAppContext).registerReceiver(mUiUpdateReceiver, intentFilter);
mbroadcastFlag = false;
sampleClass.method();
Thread.sleep(2000);
assertTrue(mbroadcastFlag);
LocalBroadcastManager.getInstance(mAppContext).unregisterReceiver(mUiUpdateReceiver);
}
Konstantin Loginov's answear is ok, but it uses JUnit3 (because he extends ApplicationTestCase; if you extend any TestCase class Android framework will run your tests with JUnit3 runner by default).
If you want to use newer JUnit 4 You can use something like this:
public class MyReceiver extends BroadcastReceiver {
boolean flag;
#Override
public void onReceive(Context context, Intent intent) {
Uri packageName = intent.getData();
if(packageName.toString().equals("package:" + context.getPackageName())){
flag = true;
}
}
}
And the Test itself:
public class MyReceiverTest {
Context context;
#Before
public void init() {
context = new RenamingDelegatingContext(InstrumentationRegistry.getInstrumentation().getTargetContext(), "test_");
}
#Test
public void myReceiverTest() throws InterruptedException {
MyReceiver receiver = new MyReceiver();
//remeber to change Intent.ACTION_PACKAGE_REPLACED
//with your action name from your mainfest file
LocalBroadcastManager.getInstance(context).registerReceiver(receiver, new IntentFilter(Intent.ACTION_PACKAGE_REPLACED));
String pn = context.getPackageName();
Intent i = new Intent(Intent.ACTION_PACKAGE_REPLACED);
i.setData(Uri.parse("package:" + context.getPackageName()));
LocalBroadcastManager.getInstance(context).sendBroadcast(i);
Thread.sleep(2000);
boolean b = receiver.flag;
assertTrue(b);
LocalBroadcastManager.getInstance(context).unregisterReceiver(receiver);
assertTrue(true);
}
I wrote for you a small hello-world with broadcast receiver and unit test for it.
Here's my broadcast receiver:
public class UiRefreshReceiver extends BroadcastReceiver {
boolean broadcastFlag;
public UiRefreshReceiver() {}
#Override
public void onReceive(Context context, Intent intent) {
broadcastFlag = true;
}
}
And test class for it:
public class ApplicationTest extends ApplicationTestCase<Application> {
public ApplicationTest() {
super(Application.class);
}
#UiThreadTest
public void test_broadcast () throws Exception{
UiRefreshReceiver mUiUpdateReceiver = new UiRefreshReceiver();
LocalBroadcastManager.getInstance(getContext()).registerReceiver(mUiUpdateReceiver, new IntentFilter("my-event"));
assertFalse(mUiUpdateReceiver.broadcastFlag);
LocalBroadcastManager.getInstance(getContext()).sendBroadcast(new Intent("my-event"));
Thread.sleep(2000);
assertTrue(mUiUpdateReceiver.broadcastFlag);
LocalBroadcastManager.getInstance(getContext()).unregisterReceiver(mUiUpdateReceiver);
}
}
I hope, it'd help.
Though, in my humble opinion - it's enough to simply unit-test onReceive() code.
I want to create an app that will know when user is using the phone(start the screen and close the screen). After a period of time I need to call doSomething() method.
Question:
1.How can I know when user start using the phone and when he close the screen?
2.Should I use Service or IntentService? Which is better in my case?
You can try something like this using a BroadcastReceiver and a Service:
Your class using The BroadcastReceiver:
public class ScreenReceiver extends BroadcastReceiver {
private boolean screenOff;
#Override
public void onReceive(Context context, Intent intent) {
if (intent.getAction().equals(Intent.ACTION_SCREEN_OFF)) {
screenOff = true;
} else if (intent.getAction().equals(Intent.ACTION_SCREEN_ON)) {
screenOff = false;
}
Intent i = new Intent(context, UpdateService.class);
i.putExtra("screen_state", screenOff);
context.startService(i);
}
}
And the service:
public static class ScreenService extends Service {
#Override
public void onCreate() {
super.onCreate();
IntentFilter filter = new IntentFilter(Intent.ACTION_SCREEN_ON);
filter.addAction(Intent.ACTION_SCREEN_OFF);
BroadcastReceiver mReceiver = new ScreenReceiver();
registerReceiver(mReceiver, filter);
}
#Override
public void onStart(Intent intent, int startId) {
boolean screenOn = intent.getBooleanExtra("screen_state", false);
if (!screenOn) {
//Implement here your code
} else {
//Implement here your code
}
}
}
If I call notifyDataSetChanged() on the custom adapter associated to my ListView, all the views should refresh themself (getView() will be called).
Now I have a BroadcastReceiver that is listening to an event. When the event fires, the ListView must be refreshed. How can I achieve this?
Thanks!
If you refresh listview from receiver you'll have code like this:
BroadcastReceiver br;
public final static String BROADCAST_ACTION = "BROADCAST_ACTION";
br = new BroadcastReceiver() {
public void onReceive(Context context, Intent intent) {
//code refreshing...
}
};
IntentFilter intFilt = new IntentFilter(BROADCAST_ACTION);
registerReceiver(br, intFilt);
And you call it with code:
Intent intent = new Intent(BROADCAST_ACTION);
sendBroadcast(intent);
If you need the refresh to be another action you just need to add (after action):
Intent intent = new Intent(BROADCAST_ACTION);
sendBroadcast(intent);
As requested, please see the sample code below:
public interface OnDataUpdateListener {
void onDataAvailable(ArrayList<String> newDataList);
}
public class MyTestReceiver extends BroadcastReceiver {
public static final String DATA_LIST = "DATA_LIST";
private OnDataUpdateListener mDataUpdateListener = null;
public MyTestReceiver(OnDataUpdateListener dataUpdateListener) {
mDataUpdateListener = dataUpdateListener;
}
#Override
public void onReceive(Context ctx, Intent intent) {
// assuming data is available in the delivered intent
ArrayList<String> dataList = intent.getSerializableExtra(DATA_LIST);
if (null != mDataUpdateListener) {
mDataUpdateListener.onDataAvailable(dataList);
}
}
}
public class MyActivity extends FragmentActivity implements OnDataUpdateListener {
public static final String ACTION_DATA_UPDATE_READY = "ACTION_DATA_UPDATE_READY";
private MyTestReceiver mTestReceiver = null;
private <SomeAdapterClass> mAdapter = null;
#Override
protected void onCreate(Bundle savedInstanceState) {
// other required initialization
mTestReceiver = new MyTestReceiver(this);
}
#Override
public void onResume() {
super.onResume();
if (null != mTestReceiver) {
registerReceiver(mTestReceiver, new IntentFilter(ACTION_DATA_UPDATE_READY));
}
}
void onDataAvailable(ArrayList<String> newDataList) {
// assuming you want to replace existing data and not willing to append to existing dataset
mAdapter.clear();
mAdapter.addAll(newDataList);
mAdapter.notifyDataSetChanged();
}
}
In the code where your data is updated, fire off a message signalling that data has been changed...
(You will need access to either the Activity or the Application context to do this)
Intent intent = new Intent("ListViewDataUpdated");
LocalBroadcastManager.getInstance(context.sendBroadcast(intent));
Then just catch the catch the message using the following code in your activity, and tell your ListAdapter to update...
private BroadcastReceiver mMessageReceiver = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
myListAdapter.notifyDataSetChanged();
}
};
#Override
public void onResume(){
super.onResume();
LocalBroadcastManager.getInstance(this).registerReceiver(mMessageReceiver, new IntentFilter("ListViewDataUpdated"));
myListAdapter.notifyDataSetChanged();//in case our data was updated while this activity was paused
}
#Override
protected void onPause() {
LocalBroadcastManager.getInstance(this).unregisterReceiver(mMessageReceiver);
super.onPause();
}
Credit: adapted from Vogella
LocalBroadcastManager.getInstance(context.sendBroadcast(intent));
change to
LocalBroadcastManager.getInstance(context).sendBroadcast(intent);
I might be wrong but it works for me...
I have interrogation about the way to use a BroadcastReceiver with a ResultReceiver in it.
I know that if "A BroadcastReceiver hasn't finished executing within 10 seconds.", there is an ANR.
I have an application that respond to an Intent, declared in the Manifest.
It is a BroadcastReceiver that start a service because it needs to make some networks operations:
public class MyReceiver extends BroadcastReceiver {
private Context context = null;
private MyResultReceiver myResultReceiver = null;
#Override
public void onReceive(Context context, Intent intent) {
this.context = context;
myResultReceiver = new MyResultReceiver(new Handler());
Intent i = new Intent();
i.setClass(context, MyService.class);
i.putExtra(Constants.EXTRA_RESULT_RECEIVER, myResultReceiver);
context.startService(i);
}
public class MyResultReceiver extends ResultReceiver {
public MyResultReceiver(Handler handler) {
super(handler);
}
#Override
protected void onReceiveResult(int resultCode, Bundle resultData) {
if (resultCode == 42) {
// Something
} else {
// Something else
}
}
}
}
My service looks like this:
public class MyService extends Service {
private Context context = null;
private ResultReceiver resultReceiver = null;
#Override
public IBinder onBind(Intent intent) {
// TODO Auto-generated method stub
return null;
}
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
this.context = this;
resultReceiver = intent.getParcelableExtra(Constants.EXTRA_RESULT_RECEIVER);
MyTask myTask = new MyTask();
myTask.execute();
return super.onStartCommand(intent, flags, startId);
}
public class MyTask extends AsyncTask<Void, Void, Boolean> {
#Override
protected Boolean doInBackground(Void... params) {
// Network operation
return status;
}
#Override
protected void onPostExecute(final Boolean status) {
Bundle bundle = new Bundle();
if (status == true) {
if (resultReceiver != null) {
resultReceiver.send(42, null);
}
} else {
if (resultReceiver != null) {
resultReceiver.send(-1, null);
}
}
}
}
}
My question is, am I sure that the resultReceiver still exist and will do what it have to do if the network operation is longer than 10 seconds ?
Here's the relevant documentation from the SDK:
If this BroadcastReceiver was launched through a tag, then
the object is no longer alive after returning from this function. This
means you should not perform any operations that return a result to
you asynchronously -- in particular, for interacting with services,
you should use startService(Intent) instead of bindService(Intent,
ServiceConnection, int). If you wish to interact with a service that
is already running, you can use peekService(Context, Intent).
The Intent filters used in registerReceiver(BroadcastReceiver,
IntentFilter) and in application manifests are not guaranteed to be
exclusive. They are hints to the operating system about how to find
suitable recipients. It is possible for senders to force delivery to
specific recipients, bypassing filter resolution. For this reason,
onReceive() implementations should respond only to known actions,
ignoring any unexpected Intents that they may receive.
Bottom line:
If you start a service, use startService(Intent).
Don't do long running applications on onReceive.
AsyncTasks may be destroyed, your best bet is to use a Service. If you are using an AsyncTask inside of a Service, it should be fine.
I want to open my MainActivity class when screen is off. In order to do that i make two class
ScreenReceiver.java to handle Screen OFF & Screen ON Intents:
public class ScreenReceiver extends BroadcastReceiver {
private boolean screenOff;
#Override
public void onReceive(Context context, Intent intent) {
if (intent.getAction().equals(Intent.ACTION_SCREEN_OFF)) {
screenOff = true;
} else if (intent.getAction().equals(Intent.ACTION_SCREEN_ON)) {
screenOff = false;
}
Intent i = new Intent(context, UpdateService.class);
i.putExtra("screen_state", screenOff);
context.startService(i);
}
}
And UpdateService for implementing ScreenReceiver:
public class UpdateService extends Service {
#Override
public void onCreate() {
super.onCreate();
// register receiver that handles screen on and screen off logic
IntentFilter filter = new IntentFilter(Intent.ACTION_SCREEN_ON);
filter.addAction(Intent.ACTION_SCREEN_OFF);
BroadcastReceiver mReceiver = new ScreenReceiver();
registerReceiver(mReceiver, filter);
}
public void onStart(Context context, Intent intent, int startId) {
boolean screenOn = intent.getBooleanExtra("screen_state", false);
if (!screenOn) {
// your code
Intent intent11 = new Intent(context,MainActivity.class);
intent11.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(intent11);
} else {
// your code
Intent intent11 = new Intent(context,MainActivity.class);
intent11.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
}
}
#Override
public IBinder onBind(Intent intent) {
// TODO Auto-generated method stub
return null;
}
}
So, when i install my app, there are no event when screen is off. please show me the problem.
Did you start the UpdateService in foreground somewhere in your application??
Firstly, intents ACTION_SCREEN_OFF and ACTION_SCREEN_ON can only be handled by a receiver registered via function registerReceiver(). Defining an IntentFilter in manifest.xml does not work for these intents.
Then, you need to make sure UpdateService:onCreate() be called in your application, otherwise ScreenReceiver:onReceiver() will never be called. You may want to do this when get intent BOOT_COMPLETED.
You may change the code to this, and do not forget define the service in manifest:
public class UpdateService extends Service {
BroadcastReceiver mReceiver = new BroadcastReceiver {
private boolean screenOff;
#Override
public void onReceive(Context context, Intent intent) {
if (intent.getAction().equals(Intent.ACTION_SCREEN_OFF)) {
screenOff = true;
} else if (intent.getAction().equals(Intent.ACTION_SCREEN_ON)) {
screenOff = false;
}
handleScreenAction(screenOff);
}
private void handleScreenAction(boolean screenOff) {
if (screenOff) {
// your code
Intent intent11 = new Intent(context,MainActivity.class);
intent11.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(intent11);
} else {
// your code
Intent intent11 = new Intent(context,MainActivity.class);
intent11.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
}
}
#Override
public void onCreate() {
super.onCreate();
// register receiver that handles screen on and screen off logic
IntentFilter filter = new IntentFilter(Intent.ACTION_SCREEN_ON);
filter.addAction(Intent.ACTION_SCREEN_OFF);
registerReceiver(mReceiver, filter);
}
#Override
public void onDestory() {
super.onDestory();
unRegisterReceiver(mReceiver);
}
public void onStart(Context context, Intent intent, int startId) {
}
#Override
public IBinder onBind(Intent intent) {
// TODO Auto-generated method stub
return null;
}
}