Currently applications like Google Hangouts and Facebook Messenger are able to accept voice input from Android Wearables, translate them to text and send reply messages to users. I have followed the tutorial at https://developer.android.com/training/wearables/notifications/voice-input.html and when I call the method outlined there:
private CharSequence getMessageText(Intent intent) {
Bundle remoteInput = RemoteInput.getResultsFromIntent(intent);
if (remoteInput != null) {
return remoteInput.getCharSequence(EXTRA_VOICE_REPLY);
}
}
return null;
}
I receive an error with the line RemoteInput.getResultsFromIntent(intent) stating that my API level is too low. Currently using a Samsung Galaxy S3, 4.4.2 API 19. Clearly, this method is not accessible to me, so my question is, how are applications like Hangouts and Facebook Messenger accepting voice input and getting that input onto my device?
developers.Android states that RemoteInput.getResultsFromIntent(intent); is a conveince so that we do not need to parse the ClipData, so I did some research and discorvered what exactly was needed to parse this ClipData and this is how I solved my Problem:
private void getMessageText(Intent intent){
ClipData extra = intent.getClipData();
Log.d("TAG", "" + extra.getItemCount()); //Found that I only have 1 extra
ClipData.Item item = extra.getItemAt(0); //Retreived that extra as a ClipData.Item
//ClipData.Item can be one of the 3 below types, debugging revealed
//The RemoteInput is of type Intent
Log.d("TEXT", "" + item.getText());
Log.d("URI", "" + item.getUri());
Log.d("INTENT", "" + item.getIntent());
//I edited this step multiple times until I discovered that the
//ClipData.Item intent contained extras, or rather 1 extra, which was another bundle
//The key for that bundle was "android.remoteinput.resultsData"
//and the key to get the voice input from wearable notification was EXTRA_VOICE_REPLY which
//was set in my previous activity that generated the Notification.
Bundle extras = item.getIntent().getExtras();
Bundle bundle = extras.getBundle("android.remoteinput.resultsData");
for (String key : bundle.keySet()) {
Object value = bundle.get(key);
Log.d("TAG", String.format("%s %s (%s)", key,
value.toString(), value.getClass().getName()));
}
tvVoiceMessage.setText(bundle.get(EXTRA_VOICE_REPLY).toString());
}
This answer should be useful for anyone that is interested in developing a wearable application using notifications and voice input replies prior to the release of Android-L.
Related
My Android app at the moment consumes a URL for a Google spreadsheet through pasting from the clipboard, reading a QR code, or reading from NFC. I'm having trouble writing to an NFC tag and I get this error:
[ERROR:nfa_rw_act.cc(1571)] Unable to write NDEF. Tag maxsize=137, request write size=171
I cannot write to this tag because the payload I'm trying to write it is larger than the writable space on it.
All I'm trying to do is write the URL I've already read (from clipboard or QR) to an NFC tag, and also add my app record so it launches the Play Store to my app in case the user doesn't have it installed already. Unfortunately, it seems this is too much data. I thought about maybe only including the spreadsheetID value, but I think this will add complications in the future when I inevitably want to add support for spreadsheets outside of Google Sheets.
Here's how I'm writing it currently:
public NdefMessage createNdefMessage() {
String text = "https://docs.google.com/spreadsheets/d/1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms/edit?usp=sharing";
NdefRecord appRecord = NdefRecord.createApplicationRecord(context.getPackageName());
NdefRecord relayRecord = new NdefRecord(NdefRecord.TNF_MIME_MEDIA, new String("application/" + context.getPackageName()).getBytes(Charset.forName("US-ASCII")), null, text.getBytes());
return new NdefMessage(new NdefRecord[] {relayRecord, appRecord});
}
Is writing just the ID ("1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms" in this case) my only option? I'm also assuming the NFC tag I'm using is an average size and not a super tiny one for 2018.
EDIT:
Thanks to Michael I was able to get it to fit, though barely (134/137 bytes). I write the URI to NFC via this:
NdefRecord relayRecord = NdefRecord.createUri(text);
I added this intent filter to catch it:
<intent-filter>
<action android:name="android.nfc.action.NDEF_DISCOVERED"/>
<category android:name="android.intent.category.DEFAULT"/>
<data android:scheme="https" android:host="docs.google.com"/>
</intent-filter>
And I read the NFC tag with this:
NdefMessage[] messages = new NdefMessage[rawMessages.length];
for (int i = 0; i < rawMessages.length; i++) {
messages[i] = (NdefMessage) rawMessages[i];
}
for (NdefRecord r : messages[0].getRecords()) {
if (r.getTnf() == NdefRecord.TNF_WELL_KNOWN) {
byte[] payload = r.getPayload();
try {
String payloadText = new String(payload, 1, payload.length - 1, "UTF-8");
int firstByte = payload[0];
return getUriPrefix(firstByte) + payloadText;
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
return "Read error";
}
}
}
And the first byte from the URI compression I get via this, even though I only ever assign "04" (4 when I read it as int) in my app:
private String getUriPrefix(int firstByte) {
if (firstByte == 0) {
return "";
} else if (firstByte == 1) {
return "http://www.";
} else if (firstByte == 2) {
return "https://www.";
} else if (firstByte == 3) {
return "http://";
} else if (firstByte == 4) {
return "https://";
} else {
return "";
}
}
The error message that you got in the log is pretty clear. Your NDEF message is too large to fit the data area of the tag. The obvious solution would be to use NFC tags with a sufficiently large storage capacity -- there's quite a few larger tags available. There's nothing else you could do about it if you want to store exactly that NDEF message.
However, there is ways to improve the NDEF message itself. You currently use a MIME type record to store a URL. That's definitely not the best choice of a record type to store a URL. (or actually any application-specific data). The MIME type that you chose costs ("application/" + context.getPackageName()).length() = 31 bytes (assuming your package name is 19 bytes).
If you used an NFC Forum external type record instead, you could create a much shorter type name of the form "mydomain.tld:appurl", so this would save quite a few bytes.
relayRecord = NdefRecord.createExternal("mydomain.tld", "appurl", text.getBytes());
Since you want to store a URL, there's even a more efficient (and readily available) record type: the NFC Forum URI well-known type. The type name of that record consists only of the single letter "U", so this would already save 30 bytes compared to your MIME type record. Moreover, the URI record type uses a compression scheme to further reduce the size of the record: there is a list of well-known URI prefixes (e.g. "https://") that can be represented as a single byte in the URI record (the method NdefRecord.createUri() will automatically take care of that compression). Consequently, you would save another 7 bytes.
relayRecord = NdefRecord.createUri(text);
I want to know can ı search youtube or another application on my application.
String s=" hi ";
Intent intent= new Intent(Intent.ACTION_VIEW, Uri.parse("https://www.youtube.com/results?search_query=" + s));
startActivity(intent);
I can search using this code for youtube web site but I want to search in youtube android application (com.google.android.youtube ) . I will use below code for voice search .
if (resultCode == RESULT_OK && null != data) {
ArrayList<String> result = data.getStringArrayListExtra(RecognizerIntent.EXTRA_RESULTS);
mVoiceInputTv.setText(result.get(0));
String text =mVoiceInputTv.getText().toString();
String filename = text; // full file name
String[] parts = filename.split("\\ "); // String array, each element is text between spaces
String beforeFirstSpace = parts[0]; // Text before the first space
if(beforeFirstSpace.equalsIgnoreCase("Youtube")) {
Intent intent = new Intent(getPackageManager().getLaunchIntentForPackage("com.google.android.youtube"));
intent.setAction(Intent.ACTION_SEARCH);
intent.putExtra(SearchManager.QUERY,text);
startActivity(intent);
finish(); System.exit(0);
}
With this code I open the youtube application but I can't search in application.
You can't do it easily since other apps may (and most likely) offer another API that is different than youtube.
For example:
youtube search API:
https://www.youtube.com/results?search_query=facebook
facebook search API:
http://www.facebook.com/search/web/direct_search.php?q=%gmail
gmail search API:
Gmail: http://mail.google.com/mail/?search=query&view=tl&start=0&init=1&fs=1&q=%youtube
You see there is a different pattern. Thus, you need to implement it manually, 1 by 1, and according to the search API offered by every single app.
I'm trying to tell the google cloud printing app to print a document, be it a .png file or an pdf. After checking if the app is installed via
public static boolean isCloudPrintInstalled(Context ctx){
String packageName = "com.google.android.apps.cloudprint";
android.content.pm.PackageManager mPm = ctx.getPackageManager();
try{
PackageInfo info = mPm.getPackageInfo(packageName, 0);
boolean installed = (info != null);
return installed;
}catch(NameNotFoundException e){
return false;
}
}
i send the user to the PrintingDialogActivity if it is missing, as described here: https://developers.google.com/cloud-print/docs/android
But i would like to use the app, if it is installed. If i send the following intent:
File theFile = new File(filePath); //file is NOT missing
Intent printIntent = new Intent(Intent.ACTION_SEND);
printIntent.setType(Helper.getMimeType(filePath));
printIntent.putExtra(Intent.EXTRA_TITLE, titleOfFile);
printIntent.putExtra(Intent.EXTRA_STREAM, Uri.fromFile(theFile));
ctx.startActivity(printIntent);
The getMimeType method is this:
public static String getMimeType(String url) {
String method = "Helper.getMimeType";
String type = null;
String extension = MimeTypeMap.getFileExtensionFromUrl(url);
//Fix for Bug: https://code.google.com/p/android/issues/detail?id=5510
if(extension == null || extension.equals("")){
extension = url.substring(url.lastIndexOf('.'));
}
type = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension);
//should i consider just not using the MimeTypeMap? -.-
if(type == null || type.equals("")){
if(extension.toLowerCase(Locale.getDefault()).equals(".txt") == true){
type = "text/plain";
}else{
Log.e(method, "Unknown extension");
}
}
return type;
}
it returns "application/pdf" for pdf files.
On my android 4.0.3 device i get the action chooser and can choose the google cloud print app, which then allows to do stuff like saving the file to google drive. It works.
But if i start this intent on my Android 5.1.1 device (Nexus 5), the action chooser also opens, but it doesn't have the google cloud printing app or anything else printing related in it. Cloud print is preinstalled and currently on version 1.17b. I didn't kill it's process with some form of energy saving app. The device is not routed. What am i missing?
I also tried entering "text/html" by hand as the mime type, because that was the solution to another stackoverflow thread - but it doesn't solve mine.
Setting the mimetype to */* also doesn't make the actionchooser offer me the printer
After a lot of googling and testing it is rather unclear to me, if printing via intent is still possible on android 5 with google cloud print. BUT what does work is to create a custom PrintDocumentAdapter, as described in this post: https://stackoverflow.com/a/20719729/1171328
I have written a program in which I am trying to write and read NFC tag, which may help user in sending message. I have successfully written to the tag but whenever I am trying to read, the Tags app shows vnd.android.nfc://ext/nfclab.com:smsService but does not allow me to send the message.
WriteSmsActivity.java:
#Override
public void onNewIntent(Intent intent) {
Log.i("Foreground dispatch", "Discovered tag with intent: " + intent);
Tag tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
String externalType = "nfclab.com:smsService";
String smsNumber = smsNumberEditText.getText().toString();
String smsBody = smsBodyEditText.getText().toString();
String urlAddress = "sms:"+smsNumber+"?body="+smsBody;
NdefRecord extRecord = new NdefRecord(NdefRecord.TNF_EXTERNAL_TYPE, externalType.getBytes(), new byte[0], urlAddress.getBytes());
NdefMessage newMessage = new NdefMessage(new NdefRecord[] { extRecord});
writeNdefMessageToTag(newMessage, tag);
}
}
Android displays the unrecognized NFC Forum external type nfclab.com:smsService (note that only lower-case letters should be used for external type names, see my answer here), because you stored that record type on your tag. That type is a custom type created by nfclab.com (for rather artificial examples in their book that won't work without a customized reader app) and by no means standardized. Consequently, Android does not know what it should do with that record.
The standard way to store ready-made SMS messages on NFC tags is the sms: URI scheme. Hence, you would typically create a URI record containing your sms: URI:
String smsUri = "sms:" + phone_number + "?body=" + text_message;
NdefRecord smsUriRecord = NdefRecord.createUri(smsUri);
Since Android 4.1 (or so), such records should be handled automatically by Android and will permit you to open the sms: URI in the default SMS app.
I have developped 2 Android applications. The first one, to write into an NFC tag, and the second to read the contents I have written .
This is what i did in the first application (WriteNFC)
private NdefRecord createRecord1(String data)
{
byte[] payload = data.getBytes(Charset.forName("UTF-8"));
byte[] empty = new byte[] {};
return new NdefRecord(NdefRecord.TNF_ABSOLUTE_URI, empty, empty, payload);
}
private NdefRecord createRecord2(String data)
{
byte[] payload = data.getBytes(Charset.forName("UTF-8"));
byte[] empty = new byte[] {};
return new NdefRecord(NdefRecord.TNF_ABSOLUTE_URI, payload, empty, empty);
}
And in the second application (ReadNFC)
NdefRecord cardRecord = msg.getRecords()[1];//Extract the second Record
String url_data = new String(cardRecord.getType());//Read data type
When I read with my own application (ReadNFC), of course I had on screen only the payload of the second Record, which I stored through "Record Type". But with a third-party application, especially that natively installed ("tag") -shown in photo-, It display correctly the first record, and for the second it's an empty field. How can I hide this field. Otherwise, how can I force the other third-party applications to not read the second record?
You simply cannot do that. Android will read the complete NDEF messages (i.e. all records) and pass it on in the Intent to an app.
Upps, it is no wonder this is happening, look at your code. First
return new NdefRecord(NdefRecord.TNF_ABSOLUTE_URI, empty, empty, payload);
then
return new NdefRecord(NdefRecord.TNF_ABSOLUTE_URI, payload, empty, empty);
So the 3rd party apps are really showing the correct data, you have a bug in your record creation.
The native NDEF support in android is somewhat crude (byte-array based), so I've written a library which helps in creating records - which you might find interesting. The above issue might be simple to resolve, but there are many other much more complex record types, so some help might come in handy ;-)
Edit: So if this is the preferred result, rather create an Unknown Record and put your 'secret' data as the payload - the will not be any good way for any 3rd party app to display that data - whereas the ID of the absolute URI Record certainly can be displayed by any NDEF-reading app (like mine?)
This third party app was bothersome to me, so I had to use foregroundDispatch to read the tag contents manually, there you have the freedom to read or not read anything you want.
This snippet is from the OnResume(). `
mNfcAdapter.enableForegroundDispatch(this, pendingIntent,
intentFiltersArray, techListsArray);
Toast.makeText(getApplicationContext(), "TAG disscovered",
Toast.LENGTH_LONG).show();
Parcelable[] rawMsgs = getIntent()
.getParcelableArrayExtra(NfcAdapter.EXTRA_NDEF_MESSAGES);
if (rawMsgs != null) {
NdefMessage[] msgs = new NdefMessage[rawMsgs.length];
for (int i = 0; i < rawMsgs.length; i++) {
msgs[i] = (NdefMessage) rawMsgs[i];
setText=new String(msgs[i].getRecords()[0].getPayload());
}
mInfoText.setText(setText);
}
}`
Here I get payload of the 1st record.