Vandermonde
Vandermonde

Reputation: 117

Intent constructor and BroadcastReceiver

Based on the sample project below. (Taken from here)

Why does BroadcastReceiver fire up when an action string is passed to the Intent constructor but not when Context and SomeOtherActivity.class are passed to it?

           // this works
           Intent intent = new Intent(PROX_ALERT_INTENT);        

           //this does not
           Intent intent = new Intent(this, ProximityIntentReceiver.class);  
           intent.setAction(PROX_ALERT_INTENT);                             

ProximityAlertActivity.java:

package com.androidmyway.demo.proxymityalert;

import android.app.Activity;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.location.LocationManager;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;


public class ProximityAlertActivity extends Activity {
    private static final long POINT_RADIUS = 100; // in Meters
    private static final long PROX_ALERT_EXPIRATION = -1; // It will never expire
    private static final String PROX_ALERT_INTENT = "com.androidmyway.demo.ProximityAlert";
    private LocationManager locationManager;
    private EditText latitudeEditText;
    private EditText longitudeEditText;
    private Button addAlertButton;

    @Override
    public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_proxymity);

            locationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE);

            latitudeEditText = (EditText) findViewById(R.id.point_latitude);
            longitudeEditText = (EditText) findViewById(R.id.point_longitude);
            addAlertButton = (Button) findViewById(R.id.add_alert_button);

            addAlertButton.setOnClickListener(new OnClickListener() {
                  public void onClick(View v) {
                         addProximityAlert();
                  }
            });

    }

    private void addProximityAlert() {
           double latitude = Double.parseDouble(latitudeEditText.getText().toString());
           double longitude = Double.parseDouble(longitudeEditText.getText().toString());
           //Intent intent = new Intent(PROX_ALERT_INTENT);           
           Intent intent = new Intent(this, ProximityIntentReceiver.class); 
           intent.setAction(PROX_ALERT_INTENT);
           PendingIntent proximityIntent = PendingIntent.getBroadcast(this, 2, intent,         PendingIntent.FLAG_UPDATE_CURRENT);
           locationManager.addProximityAlert(
                  latitude, // the latitude of the central point of the alert region
                  longitude, // the longitude of the central point of the alert region
                  POINT_RADIUS, // the radius of the central point of the alert region, in meters
                  PROX_ALERT_EXPIRATION, // time for this proximity alert, in milliseconds, or -1 to     indicate no                           expiration
                  proximityIntent // will be used to generate an Intent to fire when entry to or exit from the alert region is detected
           );

           IntentFilter filter = new IntentFilter(PROX_ALERT_INTENT);
           registerReceiver(new ProximityIntentReceiver(), filter);
           Toast.makeText(getApplicationContext(),"Alert Added",Toast.LENGTH_SHORT).show();
    }    
}

ProximityIntentReceiver:

package com.androidmyway.demo.proxymityalert;

import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.graphics.Color;
import android.location.LocationManager;
import android.util.Log;

public class ProximityIntentReceiver extends BroadcastReceiver {
    private static final int NOTIFICATION_ID = 1000;

    @SuppressWarnings("deprecation")
    @Override
    public void onReceive(Context context, Intent intent) {
        String key = LocationManager.KEY_PROXIMITY_ENTERING;
        Boolean entering = intent.getBooleanExtra(key, false);
        if (entering) {
            Log.d(getClass().getSimpleName(), "entering");
        }else {
            Log.d(getClass().getSimpleName(), "exiting");
        }
        NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);

        Intent notificationIntent = new Intent(context, ProximityAlertActivity.class);
        PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, notificationIntent, 0);
        Notification notification = createNotification();
        notification.setLatestEventInfo(context, "Proximity Alert!", "You are near your point of interest.", pendingIntent);

        notificationManager.notify(NOTIFICATION_ID, notification);
    }

    private Notification createNotification() {
        Notification notification = new Notification();
        notification.icon = R.drawable.ic_launcher;
        notification.when = System.currentTimeMillis();
        notification.flags |= Notification.FLAG_AUTO_CANCEL;
        notification.flags |= Notification.FLAG_SHOW_LIGHTS;
        notification.defaults |= Notification.DEFAULT_VIBRATE;
        notification.defaults |= Notification.DEFAULT_LIGHTS;
        notification.ledARGB = Color.WHITE;
        notification.ledOnMS = 1500;
        notification.ledOffMS = 1500;
        return notification;
    }
}

AndroidManifest.xml:

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.androidmyway.demo.proxymityalert"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-sdk
        android:minSdkVersion="8"
        android:targetSdkVersion="15" />

    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
    <uses-permission android:name="android.permission.ACCESS_MOCK_LOCATION" />
    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
    <uses-permission android:name="android.permission.VIBRATE" />
    <uses-permission android:name="android.permission.INTERNET"></uses-permission>


    <application
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@android:style/Theme.Black.NoTitleBar" >
        <activity
            android:name=".ProximityAlertActivity"
        >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

Upvotes: 2

Views: 2189

Answers (2)

Vandermonde
Vandermonde

Reputation: 117

Turns out I'm asking two questions:

Q1: Why does BroadcastReceiver fire up when an Action string is passed to the Intent constructor but not when Context and SomeOtherActivity.class are passed to it? (Which I think is a general question and not just specific to the code I posted)

Q2: Why does BroadcastReceiver fire up when an Action string is passed to the Intent constructor but not when Context and NameOfReceiver.class are passed to it? (Which is specific to the code I posted because it uses a programmatically registered receiver)

       //this works
       Intent intent = new Intent(PROX_ALERT_INTENT);        

       //this does not
       Intent intent = new Intent(this, ProximityIntentReceiver.class);  
       intent.setAction(PROX_ALERT_INTENT); 

After some research I believe I have the right answers:

ANS1: Even adding an Action manually to the Intent after calling the constructor with new Intent(context, SomeOtherActivity.class) still won't trigger BroadcastReceiver. The Android developers page on Intents gives an explanation:

"Android delivers an explicit intent to an instance of the designated target class. Nothing in the Intent object other than the component name matters for determining which component should get the intent."

Therefor Manually adding an Action has no effect. Additionally, once using an explicit Intent the only class which can be set in the Intent constructor is the class of the BroadcastReceiver which you explicitly want to call. Passing any other class makes no sense in this case.

This brings me to ANS2:

CommonsWare offers a very clear answer on the following SO question:

Why don't I get proximity alterts even though I've registered alerts?

The problem is that I'm programmatically registering the receiver. This forces me to use an IntentFilter. As stated above, once using an explicit Intent all that matters is the component name. I'm guessing that when dynamically registering the receiver, it's listening for an Action defined in the IntentFilter instead of the component name (the receiver name).

Using an explicit Intent:

Intent intent = new Intent(this, ProximityIntentReceiver.class); 

along with a statically registered receiver in the manifest:

<receiver android:name="ProximityIntentReceiver" />

fixed the problem.

Please correct me if I'm wrong about anything.

Upvotes: 4

cYrixmorten
cYrixmorten

Reputation: 7108

I believe, correct me if I am wrong, that the construction:

Intent intent = new Intent(this, SomeActivity.class); 

Is designed to start another activity, intent service or similar, not for broadcasting.

This also makes sense for the reason that other applications can pick up broadcasts and if the broadcast contained a class reference, then that other app would not stand a chance to call that piece of code.

Javadocs for the above intent construction:

Create an intent for a specific component. All other fields (action, data, type, class) are null, though they can be modified later with explicit calls. This provides a convenient way to create an intent that is intended to execute a hard-coded class name, rather than relying on the system to find an appropriate class for you; see setComponent(ComponentName) for more information on the repercussions of this.

I see that you do set the action afterwards but I would think that under the Android hood, that intent is still considered being for a specific component anyhow.

Upvotes: 0

Related Questions