Lab 02. Communication between Android components: Intents, Broadcast Receivers

Objectives

  • Learn how to communicate between Android components using Intents and Broadcast Receivers
  • Learn how to listen for events from the system using Broadcast Receivers
  • Use several types of Intents and Broadcast Receivers inside an app to communicate between activities and to start other apps.

What are Intents?

In Android, applications and application components can interact with one another by using Intent objects. An Intent is a messaging object used to request an action from another application component. It can be used between components of the same app or from different applications.

Usually an intent is used for:

  • Launching an activity from the same Android application or other activities from other applications;
  • For delivering a broadcast message;
  • For starting a service or communicate with a background Service.

An Intent is an object of type android.content.Intent. This is sent as a parameter for methods, startActivity(), startService() or sendBroadcast(), for starting an activity, a service or sending a broadcast message. The intent can transmit data to the components via a Bundle object.

Explicit Intents

They specify the target component name, using the Java class name. Usually there are used inside applications. A good example is when an activity starts another activity as presented below:

Intent intent = new Intent(this, SecondActivity.class);
startActivity(intent);

The intent uses a Context and the Class of the second activity. A context represents an interface to the current state of the application. It allows access to application specific resources and operations such as launching an activity or starting a service. In order to obtain the context you can use a reference to the current instance of the activity class by using this.

Task 1 - Use explicit intents to start another activity (2p)

a) startActivity

Create a new project and replace the content of the activity layout with activity_main.xml from the lab archive. Get a reference to “Go to secondary activity” button in the onCreate method of MainActivity. Add a new Activity to the project. You can automatically add a new activity by right clicking on the project and selecting New > Activity > Empty Activity from the menu. This will start a wizard similar to the one at application creation.

Back in MainActivity, add code to the onClick of the View.OnClickListener so that the new activity gets started. To start a new Activity you have to create an intent first which will explicitly take two parameters: a Context and the Class of the second activity.

Before launching the new activity, it must be declared in the AndroidManifest.xml file. If you manually created the activity, you will have to add a similar line to the following one to the manifest file.

<activity android:name="com.smd.lab2.SecondActivity" />

If you created the Activity manually, in the onCreate of the second activity make sure you call the onCreate of the super class and use setContentView to the layout of the second activity for which you will have to create an empty layout. If not you will get a NPE.

b) Add extras

Now we are going to add some extras to our intent. Get a reference to the EditText in the main activity and add a TextView to your second activity. When clicking the button, get the text from the EditText element and add it to the intent.

intent.putExtra("key", "value");

Then, in your second activity, first, you have to access the intent which launched the activity. This can be done using the getIntent method. Afterwards, get the string from the intent and display it in the TextView. (Hint: Intent.getStringExtra ​and Activity.getIntent)

Implicit intents

Implicit intents do not specify the target component name. In exchange an action and optionally data are specified. The operating system checks all matching registered components. If only one component is appropriate the system starts it and sends it the Intent. If there are more components the system offers the user the option to choose from the ones available. For example, viewing an URL will present the user the option to choose between the installed browsers from the phone.

Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(https://www.google.com));
startActivity(i);

https://developer.android.com/guide/components/intents-filters#java

Fig. 1 Implicit intent through the system

The Intent contains the necessary info for the Android system to identify the component to start and the data needed for the specified action. These are specified in the <intent-filter> section in the manifest file, in which are declared the capabilities of the component (or what broadcast receiver can handle).

Intent format

An Intent contains the following primary info:

Component Name

Usually the fully qualified name, including the package name, com.smd.lab2.MainActivity. This is what creates an explicit intent. The intent will only be delivered to the specified component. Without this an intent is implicit. The system will check for available components that can receive this intent based on the provided information.

Action

It is represented by a string that describes the action, for example:

  • VIEW - transmit data that can be shown to the user in the activity - phone, adress, file
  • SEND - usually named share intent, in which an app can share data with another
  • DIAL - dial a number specified in the data
  • CALL - perform a call
  • MAIN - start as the main entry point, no data is needed
  • PICK for activities - to pick an item from the data an return it
  • BOOT_COMPLETED - used after the phone has finished booting
  • SHUTDOWN - the device has shutdown
  • PACKAGE_ADDED for broadcast receivers - a new package has been installed on the device

You can also add custom actions. They respect the same rules by adding the application package name before the name of the action:

static final String ACTION_START_ENGINE = "com.smd.lab2.START_ENGINE";

Data

Based on the specified action the data field can take different values. In general it is an URI and/or MIME type of the data. The URI is a reference to the data on which the action will be done. If the URIs are similar and the MIME type can not be inferred it should be specified.

These are some standard data fields used in an Intent: BCC, CC, EMAIL, TITLE, TEXT - these are all used to create an intent for an email message that will contain BCC(blind CC), CC(carbon copy), EMAIL(address), SUBJECT(email subject), TEXT(the text that forms the body of the email).

Category

This specifies which component can handle the intent. These are some standard categories used in an Intent:

  • DEFAULT - this is the default option to handle intents with the data specified in the intent filter
  • BROWSABLE - the target activity allows to be started by a web browser for display a link, image, e-mail message
  • LAUNCHER - for activities that can be included in the system application launcher.

If you want that one of your application components to accept implicit intents, you need to add the DEFAULT category. Without it the system will not accept that component as handler for an Intent even if it can handle it.

These properties are enough for defining an Intent. Using them the system is able to determine which application component to start. Besides that, an Intent can contain additional information.

Extras

Extras are key-value pairs which contain additional information needed to finish the specified action. Extra data is added through the putExtra() method, using key value pairs or by adding a Bundle object. The Intent class contains EXTRA* constants key for different data types. As in the example with custom actions you can create custom extras, using the same method of having the application package as a prefix.

static final String EXTRA_ANDROIDS = "com.smd.lab2.EXTRA_ANDROIDS";

Flags

These are needed for specifying metadata needed by the Android system. Check the setFlags() method.

Example explicit intent

Intent intent = new Intent(
  context, // Application context
  Activity.class); // Component name 
startActivity(intent); // Launch component

Example implicit intent

Intent intent = new Intent(Intent.ACTION_VIEW); // We've set the ACTION
setData(Uri.parse("https://ocw.cs.pub.ro/smd/lab2"); // We've set DATA

// We check before if there exists an application that can support our intent. 
// This blocks the app from crashing. We should inform the user about 
// this or to disable this feature in our app. 
if(intent.resolveActivity(getPackageManager())!=null) {
    startActivity(intent);
}

App chooser

Sometimes you want to let the user select the desired handler app every time. This can be done by forcing a chooser dialog. This will not override the default option but in our app the user will have to choose every time. This is most used when using the share action. As an example think about a user who wants to send a photo through another app of his choice. In this case we need to supply him a chooser dialog every time, considering the fact that he can use different apps for different cases.

Intent = new Intent(Intent.ACTION_SEND);
String chooserTitle = "Share photo with";
Intent intentChooser = Intent.createChooser(intent, chooserTitle);

if(intent.resolveActivity(getPackageManager()) != null) {
   startActivity(intentChooser);
}

Task 2 - Go to a web page (1p)

Let’s say that we want to send the user to a web page from our app. Get a reference to “Go to web page” button and add an intent with the web page URL and a ACTION_VIEW action in the click listener.

Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setData(Uri.parse("https://www.webpage.com"));
startActivity(intent);

Task 3 - Make a call (1p)

In this task we will try to make a call from our app. In order to do this our application will not have permission to make a call so we will only send an intent with the phone number to the main app that handles calls. Get a reference to “Make a call” button and write a click listener. Create an Intent with ACTION_DIAL and enter the phone number that you want to call.

Intent intent = new Intent(Intent.ACTION_DIAL);
intent.setData(Uri.parse("tel:"+"enter the phonenumber");
startActivity(intent);

Try to use a phone to run the app and make a call.

Task 4 - Send an email (2p)

For this task you need to have configured in your emulators email client an email address or to run the app on a phone which has been already configured

Now we are going to send an email. Edit the fields below with the receiver email, title and subject. The text of the email will be from the MainActivity EditText. Get a reference to “Send email” button and add a click listener as in the previous tasks.

String text = editText.getText().toString().trim();
// We only send an email if there is text in the EditText
if (TextUtils.isEmpty(text)) {
    Toast.makeText(MainActivity.this, "Insert text", Toast.LENGTH_SHORT).show();
} else {
    Intent sendEmailIntent = new Intent(Intent.ACTION_SEND);
    sendEmailIntent.setType("text/html");
    sendEmailIntent.putExtra(Intent.EXTRA_EMAIL, new String[]{"this@smd.com"});
    sendEmailIntent.putExtra(Intent.EXTRA_SUBJECT, "First email");
    sendEmailIntent.putExtra(Intent.EXTRA_TEXT, editText.getText().toString().trim());
    startActivity(sendEmailIntent);
}

Task 5 - Get data from an activity (2p)

Usually activities are not started to only receive data they can also be started for returning data.

Create a third activity in the project and add an EditText and a Button. In MainActivity, get a reference to “Get data from activity” button which will start the third activity. We will use startActivityForResult() in order to start the activity.

Intent intent = new Intent(this, ThirdActivity.class);
startActivityForResult(intent, 1); // 1 is the request code

Now we need to add some code that handles the Intent that will be received from the started activity. For this we need to override onActivityResult, which is called when the started activity is finished. You can see that we used a request code. This is used in order to differentiate between multiple requests code used for multiple activities. Besides the request code we have resultCode which identifies if the started activity acted as we wanted, by setting RESULT_OK before finishing.

   @Override
    protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        if (requestCode == 1) {
            if (resultCode == RESULT_OK) {
                TextView textView = findViewById(R.id.textView);
                textView.setText(data.getStringExtra("text"));
            }
        }
    }

In the third activity get a reference to the added Buttton and write a click listener in which we take the text from the EditText and send it to the activity that started it. We do this by adding the text into a simple Intent object and by setting the wanted result code. In order to close this activity will call finish().

String text = editText.getText().toString().trim();
// We only send text back if there is any
if (TextUtils.isEmpty(text)) {
    Toast.makeText(ThirdActivity.this, "Insert text", Toast.LENGTH_SHORT).show();
} else {
    Intent intent = new Intent();
    intent.putExtra("text", text);
    setResult(RESULT_OK, intent);
    finish();
}

Intent Filters

In order to receive implicit intents we need to define in the app manifest an <intent-filter>. This specifies the type of intent that we support using action, data and category. If we have defined multiple intents and the system verifies that we support one of them it will send us the intent or add us in the chooser dialog screen. The intent filter is defined in the target component declaration (<activity>, <service>, <receiver>). As stated before we need to define the intent type using action, data and category using the name attribute:

  • <action> - intent action, represented by a string value
  • <data> - type of data accepted - URI(scheme, host, port, path), MIME
  • <category> - intent category, represented by a string value.

Example intent filter:

<activity android:name="EditPhotoActivity">
	<intent-filter>
		<action android:name="android.intent.action.EDIT"/>
		<category android:name="android.intent.category.DEFAULT"/>
		<data android:mimeType="image"/>
	</intent-filter>
</activity>

Multiple action, category, data can be used in an intent filter or multiple intent filters can be defined. For both cases, the corresponding component must be checked if it can handle all of the defined intents.

As a security feature to do not declare intent-filters for components used just inside the app. You should use explicit intent for those. Intent-filters can be used by another app if it determines the exact name of the component. Then it can use explicit intents to start it.

If we need to define an intent filter for just a period of time for a broadcast receiver, we can do it by registering it dynamically. registerReceiver() and unregisterReceiver() are used for registering and unregistering.

Broadcast Receivers

A Broadcast Receiver is an Android component that allows an Android application to intercept broadcast events and perform an action based on that event.

There are multiple types of receivers:

  • Manifest declared
  • Context registered
  • Local using LocalBroacastManager

In Manifest declared receivers the system wakes the application by sending the intent to the app through the broadcast receiver. This can happen even when our app is not running. This is an example of receiver registered in the manifest:

<receiver android:name=".MyReceiver"
 android:exported="true/false"  // accesible/not accesible by other applications with different used id
 android:enabled="true/false" /> // can/cannot be registered by the system

A Context broadcast receiver is a receiver which is dynamically registered. In this case the receiver will live as long as the Activity is not destroyed or the receiver is unregistered. Also we can use the Application context, in this case the receiver will live as long our application is not destroyed.

If you define a Broadcast Receiver dynamically you have to pass to the registerReceiver() two objects: a receiver and an IntentFilter. The only condition is that we can not use an empty IntentFilter:

IntentFilter intentFilter = new IntentFilter();
registerReceiver(receiver, intentFilter);

In order for it to work we need to define an action. This will identify our broadcast receiver.

// MyReceiver.class.getName() - will return com.smd.lab2.MyReceiver
IntentFilter intentFilter = new IntentFilter(MyReceiver.class.getName());

Then in the broadcast intent we need to specify the action:

Intent intent = new Intent();
intent.setAction(MyReceiver.class.getName());
intent.putExtra("key", text);
sendBroadcast(intent);

If we want to create a receiver that only works inside our application we can use LocalBroadcastManager. This has the same methods registerReceiver(), sendBroadcast() and unregisterReceiver() and are used like this:

LocalBroadcastManager.getInstance(context).registerReceiver(receiver, intentFilter);
LocalBroadcastManager.getInstance(context).sendBroadcast(intent);
LocalBroadcastManager.getInstance(context).unregisterReceiver(receiver);

The difference is that the intent is sent and received only inside our app. When using Context.registerReceiver() or ApplicationContext.registerReceiver() we can receive broadcasts from outside the app as the receiver is globally registered, no matter if that is only for the lifetime of the activity or the app. By using this we avoid security concerns like other apps sending broadcasts to our receiver or other apps defining a receiver with the same action in which case when we send the broadcast intent it will be received by both receivers.

For all the types of broadcast receivers we need to create a class, for our example MyReceiver, and extend the BroadcastReceiver abstract class. Inside it we need to override the onReceive() method. This will be called when we receive the broadcast intent.

public class MyReceiver extends BroadcastReceiver {

    @Override
    public void onReceive(Context context, Intent intent) {
        String text = intent.getStringExtra("text");
        Toast.makeText(context, text, Toast.LENGTH_LONG).show();
    }
}

Task 6 - Broadcast receivers (2p)

Add a Broadcast Receiver to the project New > Other > Broadcast Receiver and name it MyReceiver.java. We will want to use this receiver to pop a simple message on the screen when clicking on the Send text to receiver button. The message will contain the text inserted in the EditText.

In order to achieve this, create a click listener with an Intent containing the context object and the receiver's class name. Add the text using putExtra. Afterwards, use sendBroadcast to send the intent to the BroadcastReceiver.

String text = editText.getText().toString().trim();
// We only send text back if there is any
if (TextUtils.isEmpty(text)) {
    Toast.makeText(MainActivity.this, "Insert text", Toast.LENGTH_SHORT).show();
} else {
    Intent i = new Intent(MainActivity.this, MyReceiver.class);
    i.putExtra("text", text);
    MainActivity.this.sendBroadcast(i);
}

Inside the receiver, in the onReceive method, retrieve the String added to the intent and display it using a toast notification.

Toast.makeText(context, text, Toast.LENGTH_LONG).show();

Do not use broadcast receivers to maintain state or to be interacted with. It “lives” only until the onReceive method finishes. Also, do not forget to register and unregister your broadcast receiver in the activity's lifecycle callbacks, e.g. in onPause and onResume.

Resources

smd/laboratoare/02.txt · Last modified: 2020/03/09 06:18 by vasile.cosovanu
CC Attribution-Share Alike 3.0 Unported
www.chimeric.de Valid CSS Driven by DokuWiki do yourself a favour and use a real browser - get firefox!! Recent changes RSS feed Valid XHTML 1.0