Android Intents: Difference between revisions
Line 371: | Line 371: | ||
} | } | ||
</syntaxhighlight> | </syntaxhighlight> | ||
===And Finally | ===And Finally Esther=== | ||
This was done under SDK 29 however when I moved to SDK 30 it failed. | This was done under SDK 29 however when I moved to SDK 30 it failed. |
Revision as of 03:32, 28 January 2021
Introduction
Intents
There are two types of intents
- Explicit
- Implicit
Explicit
We can start an explicit intent with
val intent = Intent(this.MyActivityClass::class.java_
startActivity(intent)
Implicit
No destination intent is defined. The user will be prompted for which application to use. Not the use of the apply operator.
val intent = Intent().apply {
action = Intent.ACTION_SEND
putExtra(Intent.EXTRA_TEXT,"Hello World")
type = "text/plain"
}
startActivity(intent)
Quite nice compared with the code without the apply.
val intent = Intent()
intent.action = Intent.ACTION_SEND
intent.putExtra(Intent.EXTRA_TEXT,"Hello World")
intent.type = "text/plain"
startActivity(intent)
Implicit With Choice
Android looks at the action, and prompts the user for all app which handle this.The user can make their choice a default however we can override this and force a choice. Notice we should always check for a valid intent or the app will crash
val chooser = Intent.createChooser(myIntent, title)
if(intent.resolveActivity(packageManager) !=null) {
startActivity(chooser)
} else {
Log.d(...)
}
Common Intents
What is Required
For common intents we need to go to https://developer.android.com/guide/components/intents-common#Clock and look at what is required this includes
- Action Type
- Permissions
- Sample Code
- Pass the appropriate Parameter
Working Example 1 SET_ALARM
This creates an alarm for Mon-Fri at 17:00.
- Set Permissions
- Implementation
Set Permissions
<uses-permission android:name="com.android.alarm.permission.SET_ALARM"/>
Implementation
val intent = Intent(AlarmClock.ACTION_SET_ALARM).apply {
putExtra(AlarmClock.EXTRA_MESSAGE, "My Great Alarm")
putExtra(AlarmClock.EXTRA_HOUR, 17)
putExtra(AlarmClock.EXTRA_MINUTES, 0)
putExtra(
AlarmClock.EXTRA_DAYS,
arrayOf(
java.util.Calendar.MONDAY,
java.util.Calendar.TUESDAY,
java.util.Calendar.WEDNESDAY,
java.util.Calendar.THURSDAY,
java.util.Calendar.FRIDAY
)
)
}
if (intent.resolveActivity(packageManager) != null) {
startActivity(intent)
}
Working Example 2 CREATE_NOTE
It seems that the documentation is a bit poor around the intents. So I thought it wise just to see how it worked for this for me
- Set Permissions
- Implementation
Set Permissions
None specified
Implementation
This is the documentation at the time
val intent = Intent(NoteIntents.ACTION_CREATE_NOTE).apply {
putExtra(NoteIntents.EXTRA_NAME, "test subject")
putExtra(NoteIntents.EXTRA_TEXT, "text")
}
if (intent.resolveActivity(packageManager) != null) {
startActivity(intent)
}
Fixing
This failed to work so rather than following the instructor I googled my way so I might reuse this approach next time
Initial
I google the documentation for NoteIntents and arrived on https://developers.google.com/android/reference/com/google/android/gms/actions/NoteIntents. There is no mention of a library required
Next Stack at https://stackoverflow.com/questions/50145470/how-do-i-use-noteintents shows this did show play services was required and then a light bulb moment, I pressed F12 lead me to the NoteIntents.class which gives
public class NoteIntents {
@RecentlyNonNull
public static final String ACTION_CREATE_NOTE = "com.google.android.gms.actions.CREATE_NOTE";
...
Packager
So with this install the error was now that the packer is null
if(intent.resolveActivity(packageManager) !=null)
This led me to move off my emulator, genymotion which does not use playservices. So I installed play services on the android emulator and install Keep Notes because this is said to work with Noteintents. I had another go with no joy. I read that SDK 30 means you need to do something different https://developer.android.com/about/versions/11/privacy/package-visibility so I lowered the emulator to 29 and install play services. But no joy.
Light Bulb #2
Well I read above
val chooser = Intent.createChooser(myIntent, title)
So I implemented a chooser and still no prompt using
val intent = Intent(NoteIntents.ACTION_CREATE_NOTE).apply {
putExtra(NoteIntents.EXTRA_NAME, "My Subject")
putExtra(NoteIntents.EXTRA_TEXT, "Iain you found it")
type = "text/plain"
}
So I removed the type and finally it said no apps can perform this action. So adding the type back in showed nothing.
Resolution
To me this said that there are no clients which supports "text/plain" so I googled how make a client and basically implemented an app with
<activity
android:name=".MainActivity"
android:label="@string/app_name"
android:theme="@style/Theme.TestNoteIntent.NoActionBar" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<intent-filter>
<action android:name="com.google.android.gms.actions.CREATE_NOTE" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="*/*" />
</intent-filter>
</activity>
And in the Activity
when {
intent?.action == NoteIntents.ACTION_CREATE_NOTE -> {
if ("text/plain" == intent.type) {
intent.getStringExtra(NoteIntents.EXTRA_TEXT)?.let {
Toast.makeText(this,it,
Toast.LENGTH_LONG).show();
}
}
else {
Toast.makeText(this, "We go this ok 1!",
Toast.LENGTH_LONG).show();
}
...
This finally produced a prompt for both my Test app and Keep Notes. Using the test app all went well. I could not get Keep Notes to work
Working Example 3 ACTION_VIEW
Thought I would add a geo example just in case.
val address = "254 Badger Avenue, Crewe"
val intent = Intent(Intent.ACTION_VIEW).apply {
data = Uri.parse("geo:0,0?q=$address")
}
if (intent.resolveActivity(packageManager) != null) {
startActivity(intent)
}
Working Example 4 ACTION_PICK
Introduction=
So thought I might look at getting contacts. This looked quite simple but of course it is not. Android seems to move quickly with its api. Firstly,
Add Permissions
We need to add this in the example I did.
<uses-permission android:name="android.permission.READ_CONTACTS" />
Call The Intent
The documentation pointed to doing the following. But this is depreciated
val intent = Intent(Intent.ACTION_PICK).apply {
type = ContactsContract.Contacts.CONTENT_TYPE
}
if (intent.resolveActivity(packageManager) != null) {
startActivityForResult(intent, REQUEST_SELECT_CONTACT)
}
Currently ha ha we need to now do,
val intent = Intent(Intent.ACTION_PICK).apply {
type = ContactsContract.Contacts.CONTENT_TYPE
}
if (intent.resolveActivity(packageManager) != null) {
someActivityResultLauncher.launch(intent);
}
Where someActivityResultLauncher is a declarative function
var someActivityResultLauncher = registerForActivityResult(
StartActivityForResult()
) { result ->
if (result.resultCode == Activity.RESULT_OK) {
val uri: Uri = result?.data?.data ?: return@registerForActivityResult
getDetails(uri)
}
}
Permissions
I received Permission issues with this so added the standard
fun isReadContactsPermissionGranted(): Boolean {
return if (Build.VERSION.SDK_INT >= 23) {
if (checkSelfPermission(Manifest.permission.READ_CONTACTS)
== PackageManager.PERMISSION_GRANTED
) {
Log.v(TAG, "Permission is granted")
true
} else {
Log.v(TAG, "Permission is revoked")
ActivityCompat.requestPermissions(
this,
arrayOf(Manifest.permission.READ_CONTACTS),
1
)
false
}
} else { //permission is automatically granted on sdk<23 upon installation
Log.v(TAG, "Permission is granted")
true
}
}
Getting the Contact
I was using an emulator with my own contacts. Using the API showed that the call to contentResolver.query failed because of the column ContactsContract.CommonDataKinds.Phone.NUMBER. Digging into this, not all columns are available and you can query if there is a phone number. This is part of that code.
private fun getDetails(uri: Uri) {
val cr = contentResolver
val cur = cr.query(ContactsContract.Contacts.CONTENT_URI, null, null, null, null)
val projection = arrayOf(
ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME,
ContactsContract.CommonDataKinds.Phone.HAS_PHONE_NUMBER,
// ContactsContract.CommonDataKinds.Phone.NUMBER,
// ContactsContract.CommonDataKinds.Email.DATA
)
val names = contentResolver.query(uri, projection, null, null, null)
val indexName = names!!.getColumnIndex(ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME)
val indexHasPhoneNumber = names!!.getColumnIndex(ContactsContract.CommonDataKinds.Phone.HAS_PHONE_NUMBER)
// val indexNumber = names.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER)
names.moveToFirst()
do {
val name = names.getString(indexName)
Log.e("Name new:", name)
val hasPhoneNumberAsInt = names.getInt(indexHasPhoneNumber)
val hasPhoneNumber = hasPhoneNumberAsInt.toString().toBoolean()
Log.e("Has Phone Number:", hasPhoneNumber.toString())
// val number = names.getString(indexNumber)
// Log.e("Number new:", "::$number")
} while (names.moveToNext())
You can query the columns available on the cursor with
if(cur !=null)
{
var temp = cur.getColumnNames()
}
Sending Emails
Finally some sanity with Common Intents. We can send emails with
- ACTION_SENDTO, one email no attachments
- ACTION_SEND, one attachment
- ACTION_SEND_MULTIPLE, multiple attachments
To send to multiple people just
putExtra(Intent.EXTRA_EMAIL,arrayOf(
"someone@gmail1.com",
"someone@gmail2.com")
Capturing Images
To do this we need to
- Get a Uri
- Call ACTION_IMAGE_CAPTURE with Uri
- Using the Result
Get Uri
Searching the web I found this example.
private fun getImageFileUri(): Uri? {
// Create a storage directory for the images
// To be safe(er), you should check that the SDCard is mounted
// using Environment.getExternalStorageState() before doing this
val imagePath = File(
Environment.getExternalStoragePublicDirectory(
Environment.DIRECTORY_PICTURES
), "Tuxuri"
)
Log.d(TAG, "Find " + imagePath.getAbsolutePath())
if (!imagePath.exists()) {
if (!imagePath.mkdirs()) {
Log.d("CameraTestIntent", "failed to create directory")
return null
} else {
Log.d(TAG, "create new Tux folder")
}
}
// Create an image file name
val timeStamp: String = SimpleDateFormat("yyyyMMdd_HHmmss").format(Date())
val image = File(imagePath, "TUX_$timeStamp.jpg")
if (!image.exists()) {
try {
image.createNewFile()
} catch (e: IOException) {
// TODO Auto-generated catch block
e.printStackTrace()
}
}
//return image;
// Create an File Uri
return Uri.fromFile(image)
}
Call ACTION_IMAGE_CAPTURE with Uri
Next we call the action
mUriSavedImage = getImageFileUri()
val intent = Intent(MediaStore.ACTION_IMAGE_CAPTURE).apply {
putExtra(MediaStore.EXTRA_OUTPUT, mUriSavedImage)
}
if (intent.resolveActivity(packageManager) != null) {
attachmentActivityResultLauncher.launch(intent);
} }
Using the Result
Like the contacts where we retrieve a result we need to declare a handler. I do not recommend putting the code in the handler so this is just for show
var attachmentActivityResultLauncher = registerForActivityResult(
StartActivityForResult()
) { result ->
if (result.resultCode == Activity.RESULT_OK ) {
val bitmap = MediaStore.Images.Media.getBitmap(contentResolver,mUriSavedImage)
imageViewAttachmentPreview.visibility = View.VISIBLE
imageViewAttachmentPreview.setImageBitmap(bitmap)
}
}
And Finally Esther
This was done under SDK 29 however when I moved to SDK 30 it failed.