Retrieve text from a RemoteViews Object - android

I need to retrieve some text from a RemoteViews object. It is possible for me to get the LayoutId, but I have no idea how to retrieve text from a TextView that is in this RemoteView (namely a notification).
Also the RemoteView only contains setters, but no getters, so I guess I have to use the LayoutId (somehow).
Can you help me with that? Thanks!
/edit: The reason why I am asking this, is because I have an AccessibilityService that retrieves the notification. Therefore this is the only way of retrieving the value.
/edit2: I use this code for receiving the notification:
#Override
public void onAccessibilityEvent(AccessibilityEvent event) {
if (event.getEventType() == AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED) {
List<CharSequence> notificationList = event.getText();
for (int i = 0; i < notificationList.size(); i++) {
Toast.makeText(this.getApplicationContext(), notificationList.get(i), 1).show();
}
if (!(parcel instanceof Notification)) {
return;
}
final Notification notification = (Notification) parcel;
doMoreStuff();
}
}
With the notification object I have access to a RemoteViews (notification.contentView) and to a PendingIntent (notification.contentIntent).
To get the layoutId, I can call contentView.getLayoutId()

I proposed a similar solution here that also uses reflection to solve the problem, but in a more approachable fashion. This is my solution. In this context, the RemoteViews came from a Notification, so the first three lines can probably be ignored if you already have access to the RemoteViews object. The link on the page provides a much more detailed explanation of what is actually going on. I hope this will help anyone with a similar problem.
public static List<String> getText(Notification notification)
{
// We have to extract the information from the view
RemoteViews views = notification.bigContentView;
if (views == null) views = notification.contentView;
if (views == null) return null;
// Use reflection to examine the m_actions member of the given RemoteViews object.
// It's not pretty, but it works.
List<String> text = new ArrayList<String>();
try
{
Field field = views.getClass().getDeclaredField("mActions");
field.setAccessible(true);
#SuppressWarnings("unchecked")
ArrayList<Parcelable> actions = (ArrayList<Parcelable>) field.get(views);
// Find the setText() and setTime() reflection actions
for (Parcelable p : actions)
{
Parcel parcel = Parcel.obtain();
p.writeToParcel(parcel, 0);
parcel.setDataPosition(0);
// The tag tells which type of action it is (2 is ReflectionAction, from the source)
int tag = parcel.readInt();
if (tag != 2) continue;
// View ID
parcel.readInt();
String methodName = parcel.readString();
if (methodName == null) continue;
// Save strings
else if (methodName.equals("setText"))
{
// Parameter type (10 = Character Sequence)
parcel.readInt();
// Store the actual string
String t = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(parcel).toString().trim();
text.add(t);
}
// Save times. Comment this section out if the notification time isn't important
else if (methodName.equals("setTime"))
{
// Parameter type (5 = Long)
parcel.readInt();
String t = new SimpleDateFormat("h:mm a").format(new Date(parcel.readLong()));
text.add(t);
}
parcel.recycle();
}
}
// It's not usually good style to do this, but then again, neither is the use of reflection...
catch (Exception e)
{
Log.e("NotificationClassifier", e.toString());
}
return text;
}

Taken from Extract notification text from parcelable, contentView or contentIntent :
Notification notification = (Notification) event.getParcelableData();
RemoteViews views = notification.contentView;
Class secretClass = views.getClass();
try {
Map<Integer, String> text = new HashMap<Integer, String>();
Field outerFields[] = secretClass.getDeclaredFields();
for (int i = 0; i < outerFields.length; i++) {
if (!outerFields[i].getName().equals("mActions")) continue;
outerFields[i].setAccessible(true);
ArrayList<Object> actions = (ArrayList<Object>) outerFields[i]
.get(views);
for (Object action : actions) {
Field innerFields[] = action.getClass().getDeclaredFields();
Object value = null;
Integer type = null;
Integer viewId = null;
for (Field field : innerFields) {
field.setAccessible(true);
if (field.getName().equals("value")) {
value = field.get(action);
} else if (field.getName().equals("type")) {
type = field.getInt(action);
} else if (field.getName().equals("viewId")) {
viewId = field.getInt(action);
}
}
if (type == 9 || type == 10) {
text.put(viewId, value.toString());
}
}
System.out.println("title is: " + text.get(16908310));
System.out.println("info is: " + text.get(16909082));
System.out.println("text is: " + text.get(16908358));
}
} catch (Exception e) {
e.printStackTrace();
}

CommonsWare in this question says:
... App widgets are write-only: you can push data to them, but you
cannot read them. Instead, when you update your app widget with
new text, you will need to store that text somewhere, perhaps in a
file.
His answer seems to be logical.

If you are targeting on Android 19+, you can use the following code for getting title/text from a Notification object without using any private APIs.
Notification noty = ...;
Bundle extras = noty.extras;
if (extras != null) {
String title = extras.getString(Notification.EXTRA_TITLE);
String text = extras.getString(Notification.EXTRA_TEXT);
}

Related

Android + ObjectBox Search Query Issue

I am stuck with the ObjectBox Like Query. I have done as below when I search for something.
QueryBuilder<MItemDetail> builder = mItemDetailListBox.query();
builder.contains(MItemDetail_.productName, search);
itemList = builder.build().find();
For example, My data is:
paracetamol
paracetamol potest
paracetamol_new
Problem:
Now as you know the contains works simply as that returns a list of items that contain a given search string.
What I Want:
If I search para new, I want the result paracetamol_new
If I search para p, I want the result paracetamol potest
If I search para e e, I want the result paracetamol potest and paracetamol_new
Is there any function or utility available in ObjectBox that can help me to achieve this?
Do let me know If you have any questions.
Edited:
The given links in a comment, My question is different. I know all the methods contains(), startsWith, and endsWith but my problem not getting solved using that.
With Reference to this answer I have done some changes as given and I got a perfect solution as I wanted.
QueryBuilder<MItemDetail> builder = mItemDetailListBox.query();
// builder.contains(MItemDetail_.productName, search);
builder.filter(new QueryFilter<MItemDetail>() {
#Override
public boolean keep(#NonNull MItemDetail entity) {
return like(entity.getProductName(), "%"+ search + "%");
}
}).order(MItemDetail_.productName);
businessModels = builder.build().find();
In the following methods, I have added one more replace statement .replace(" ",".*?")
private static boolean like(final String str, final String expr) {
String safeString = (str == null) ? "" : str;
String regex = quoteMeta(expr);
regex = regex.replace("_", ".").replace(" ",".*?").replace("%", ".*?");
Pattern p = Pattern.compile(regex,
Pattern.CASE_INSENSITIVE | Pattern.DOTALL);
return p.matcher(safeString).matches();
}
private static String quoteMeta(String s) {
if (s == null) {
throw new IllegalArgumentException("String cannot be null");
}
int len = s.length();
if (len == 0) {
return "";
}
StringBuilder sb = new StringBuilder(len * 2);
for (int i = 0; i < len; i++) {
char c = s.charAt(i);
if ("[](){}.*+?$^|#\\".indexOf(c) != -1) {
sb.append("\\");
}
sb.append(c);
}
return sb.toString();
}
Thank you.

Is there any possibility to get userDataHeader from android SmsMessage?

Interested in getting few fields (msgCount, refNumber and seqNumber) from userDataHeader in android SmsMessage object. Does anyone know if it's possible?
object structure tree
Those fields are about three levels deep in private/hidden classes. You can get them with reflection, but do you really need to? msgCount will be equal to the length of the PDU array. The PDUs are ordered, so figuring seqNumber for each is trivial. And I can't think of any reason you'd need refNumber, which is used by the internals and the backend to track submission success/failure and to issue status reports.
In any case, the following is a reflective method you can use to confirm this. Note that these values are only relevant to multipart messages. The data header will be null for single-part messages.
private void listMessageConcatRef(SmsMessage msg) {
try {
Field msgBaseField = SmsMessage.class.getDeclaredField("mWrappedSmsMessage");
Object msgBase = msgBaseField.get(msg);
Class<?> msgBaseCls = Class.forName(
"com.android.internal.telephony.SmsMessageBase");
Field dataHeaderField = msgBaseCls.getDeclaredField("mUserDataHeader");
dataHeaderField.setAccessible(true);
Object dataHeader = dataHeaderField.get(msgBase);
if (dataHeader == null) {
Log.d("SmsMessage", "null data header");
return;
}
Class<?> headerCls = Class.forName(
"com.android.internal.telephony.SmsHeader");
Field concatRefField = headerCls.getDeclaredField("concatRef");
Object concatRef = concatRefField.get(dataHeader);
Class<?> concatRefCls = Class.forName(
"com.android.internal.telephony.SmsHeader$ConcatRef");
Field msgCountField = concatRefCls.getDeclaredField("msgCount");
Field refNumberField = concatRefCls.getDeclaredField("refNumber");
Field seqNumberField = concatRefCls.getDeclaredField("seqNumber");
Field isEightBitsField = concatRefCls.getDeclaredField("isEightBits");
int msgCount = msgCountField.get(concatRef);
int refNumber = refNumberField.get(concatRef);
int seqNumber = seqNumberField.get(concatRef);
boolean isEightBits = isEightBitsField.get(concatRef);
Log.d("ConcatRef", "msgCount = " + msgCount);
Log.d("ConcatRef", "refNumber = " + refNumber);
Log.d("ConcatRef", "seqNumber = " + seqNumber);
Log.d("ConcatRef", "isEightBits = " + isEightBits);
}
catch (ClassNotFoundException | NoSuchFieldException | IllegalAccessException e) {
e.printStackTrace();
}
}

How to remove duplicates from ArrayList of type Object? [duplicate]

This question already has answers here:
How to remove duplicates from a list?
(15 answers)
Closed 8 years ago.
I want to remove duplicates from ArrayList of type Alerts where Alerts is a class.
Class Alerts -
public class Alerts implements Parcelable {
String date = null;
String alertType = null;
String discription = null;
public Alerts() {
}
public Alerts(String date, String alertType, String discription) {
super();
this.date = date;
this.alertType = alertType;
this.discription = discription;
}
}
Here is how I added the elements -
ArrayList<Alerts> alert = new ArrayList<Alerts>();
Alerts obAlerts = new Alerts();
obAlerts = new Alerts();
obAlerts.date = Date1.toString();
obAlerts.alertType = "Alert Type 1";
obAlerts.discription = "Some Text";
alert.add(obAlerts);
obAlerts = new Alerts();
obAlerts.date = Date2.toString();
obAlerts.alertType = "Alert Type 1";
obAlerts.discription = "Some Text";
alert.add(obAlerts);
What I want to remove from them-
I want all alerts which have unique obAlerts.date and obAlerts.alertType. In other words, remove duplicate obAlerts.date and obAlerts.alertType alerts.
I tried this -
Alerts temp1, temp2;
String macTemp1, macTemp2, macDate1, macDate2;
for(int i=0;i<alert.size();i++)
{
temp1 = alert.get(i);
macTemp1=temp1.alertType.trim();
macDate1 = temp1.date.trim();
for(int j=i+1;j<alert.size();j++)
{
temp2 = alert.get(j);
macTemp2=temp2.alertType.trim();
macDate2 = temp2.date.trim();
if (macTemp2.equals(macTemp1) && macDate1.equals(macDate2))
{
alert.remove(temp2);
}
}
}
I also tried-
HashSet<Alerts> hs = new HashSet<Alerts>();
hs.addAll(obAlerts);
obAlerts.clear();
obAlerts.addAll(hs);
You need to specify yourself how the class decides equality by overriding a pair of methods:
public class Alert {
String date;
String alertType;
#Override
public boolean equals(Object o) {
if (this == 0) {
return true;
}
if ((o == null) || (!(o instanceof Alert)))
return false;
}
Alert alert = (Alert) o;
return this.date.equals(alert.date)
&& this.alertType.equals(alert.alertType);
}
#Override
public int hashCode() {
int dateHash;
int typeHash;
if (date == null) {
dateHash = super.hashCode();
} else {
dateHash = this.date.hashCode();
}
if (alertType == null) {
typeHash = super.hashCode();
} else {
typeHash = this.alertType.hashCode();
}
return dateHash + typeHash;
}
}
You can then loop through your ArrayList and add elements if they aren't already there as Collections.contains() makes use of these methods.
public List<Alert> getUniqueList(List<Alert> alertList) {
List<Alert> uniqueAlerts = new ArrayList<Alert>();
for (Alert alert : alertList) {
if (!uniqueAlerts.contains(alert)) {
uniqueAlerts.add(alert);
}
}
return uniqueAlerts;
}
However, after saying all that, you may want to revisit your design to use a Set or one of its family that doesn't allow duplicate elements. Depends on your project. Here's a comparison of Collections types
You could use a Set<>. By nature, Sets do no include duplicates. You just need to make sure that you have a proper hashCode() and equals() methods.
In your Alerts class, override the hashCode and equals methods to be dependent on the values of the fields you want to be primary keys. Afterwards, you can use a HashSet to store already seen instances while iterating over the ArrayList. When you find an instance which is not in the HashSet, add it to the HashSet, else remove it from the ArrayList. To make your life easier, you could switch to a HashSet altogether and be done with duplicates per se.
Beware that for overriding hashCode and equals, some constraints apply.
This thread has some helpful pointers on how to write good hashCode functions. An important lesson is that simply adding together all dependent fields' hashcodes is not sufficient because then swapping values between fields will lead to identical hashCodes which might not be desirable (compare swapping first name and last name). Instead, some sort of shifting-operation is usually done before adding the next atomic hash, eg. multiplying with a prime.
First store your datas in array then split at as one by one string,, till the length of that data execute arry and compare with acyual data by if condition and retun it,,
HashSet<String> hs = new HashSet<String>();
for(int i=0;i<alert.size();i++)
{
hs.add(alert.get(i).date + ","+ alert.get(i).alertType;
}
alert.clear();
String alertAll[] = null;
for (String s : hs) {
alertAll = s.split(",");
obAlerts = new Alerts();
obAlerts.date = alertAll[0];
obAlerts.alertType = alertAll[1];
alert.add(obAlerts);
}

Listing all extras of an Intent

For debugging reasons I want to list all extras (and their values) of an Intent. Now, getting the keys isn't a problem
Set<String> keys = intent.getExtras().keySet();
but getting the values of the keys is one for me, because some values are strings, some are boolean... How could I get the values in a loop (looping through the keys) and write the values to a logfile? Thanks for any hint!
Here's what I used to get information on an undocumented (3rd-party) intent:
Bundle bundle = intent.getExtras();
if (bundle != null) {
for (String key : bundle.keySet()) {
Log.e(TAG, key + " : " + (bundle.get(key) != null ? bundle.get(key) : "NULL"));
}
}
Make sure to check if bundle is null before the loop.
This is how I define utility method to dump all extras of an Intent.
import java.util.Iterator;
import java.util.Set;
import android.os.Bundle;
public static void dumpIntent(Intent i){
Bundle bundle = i.getExtras();
if (bundle != null) {
Set<String> keys = bundle.keySet();
Iterator<String> it = keys.iterator();
Log.e(LOG_TAG,"Dumping Intent start");
while (it.hasNext()) {
String key = it.next();
Log.e(LOG_TAG,"[" + key + "=" + bundle.get(key)+"]");
}
Log.e(LOG_TAG,"Dumping Intent end");
}
}
You can do it in one line of code:
Log.d("intent URI", intent.toUri(0));
It outputs something like:
"#Intent;action=android.intent.action.MAIN;category=android.intent.category.LAUNCHER;launchFlags=0x10a00000;component=com.mydomain.myapp/.StartActivity;sourceBounds=12%20870%20276%201167; l.profile=0; end"
At the end of this string (the part that I bolded) you can find the list of extras (only one extra in this example).
This is according to the toUri documentation:
"The URI contains the Intent's data as the base URI, with an additional fragment describing the action, categories, type, flags, package, component, and extras."
private TextView tv;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
tv = new TextView(this);
tv.setText("Extras: \n\r");
setContentView(tv);
StringBuilder str = new StringBuilder();
Bundle bundle = getIntent().getExtras();
if (bundle != null) {
Set<String> keys = bundle.keySet();
Iterator<String> it = keys.iterator();
while (it.hasNext()) {
String key = it.next();
str.append(key);
str.append(":");
str.append(bundle.get(key));
str.append("\n\r");
}
tv.setText(str.toString());
}
}
A Kotlin solution useful for evaluation in debug mode:
// list: List<Pair<String!, Any?>>?
val list = intent.extras?.keySet()?.map { it to (intent.extras?.get(it) ?: "null") }
Log.d("list", list.toString();
That would print the list of all extras in the bundle extras
The get(String key) method of Bundle returns an Object. Your best bet is to spin over the key set calling get(String) on each key and using toString() on the Object to output them. This will work best for primitives, but you may run into issues with Objects that do not implement a toString().
I wanted a way to output the contents of an intent to the log, and to be able to read it easily, so here's what I came up with. I've created a LogUtil class, and then took the dumpIntent() method #Pratik created, and modified it a bit. Here's what it all looks like:
public class LogUtil {
private static final String TAG = "IntentDump";
public static void dumpIntent(Intent i){
Bundle bundle = i.getExtras();
if (bundle != null) {
Set<String> keys = bundle.keySet();
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append("IntentDump \n\r");
stringBuilder.append("-------------------------------------------------------------\n\r");
for (String key : keys) {
stringBuilder.append(key).append("=").append(bundle.get(key)).append("\n\r");
}
stringBuilder.append("-------------------------------------------------------------\n\r");
Log.i(TAG, stringBuilder.toString());
}
}
}
Hope this helps someone!
Bundle extras = getIntent().getExtras();
Set<String> ks = extras.keySet();
Iterator<String> iterator = ks.iterator();
while (iterator.hasNext()) {
Log.d("KEY", iterator.next());
}
You could use for (String key : keys) { Object o = get(key); to return an Object, call getClass().getName() on it to get the type, and then do a set of if name.equals("String") type things to work out which method you should actually be calling, in order to get the value?
I noticed in the Android source that almost every operation forces the Bundle to unparcel its data. So if (like me) you need to do this frequently for debugging purposes, the below is very quick to type:
Bundle extras = getIntent().getExtras();
extras.isEmpty(); // unparcel
System.out.println(extras);
Sorry if this is too verbose or too late, but this was the only way I could find to get the job done. The most complicating factor was the fact that java does not have pass by reference functions, so the get---Extra methods need a default to return and cannot modify a boolean value to tell whether or not the default value is being returned by chance, or because the results were not favorable. For this purpose, it would have been nicer to have the method raise an exception than to have it return a default.
I found my information here: Android Intent Documentation.
//substitute your own intent here
Intent intent = new Intent();
intent.putExtra("first", "hello");
intent.putExtra("second", 1);
intent.putExtra("third", true);
intent.putExtra("fourth", 1.01);
// convert the set to a string array
Set Documentation
String[] anArray = {};
Set<String> extras1 = (Set<String>) intent.getExtras().keySet();
String[] extras = (String[]) extras1.toArray(anArray);
// an arraylist to hold all of the strings
// rather than putting strings in here, you could display them
ArrayList<String> endResult = new ArrayList<String>();
for (int i=0; i<extras.length; i++) {
//try using as a String
String aString = intent.getStringExtra(extras[i]);
// is a string, because the default return value for a non-string is null
if (aString != null) {
endResult.add(extras[i] + " : " + aString);
}
// not a string
else {
// try the next data type, int
int anInt = intent.getIntExtra(extras[i], 0);
// is the default value signifying that either it is not an int or that it happens to be 0
if (anInt == 0) {
// is an int value that happens to be 0, the same as the default value
if (intent.getIntExtra(extras[i], 1) != 1) {
endResult.add(extras[i] + " : " + Integer.toString(anInt));
}
// not an int value
// try double (also works for float)
else {
double aDouble = intent.getDoubleExtra(extras[i], 0.0);
// is the same as the default value, but does not necessarily mean that it is not double
if (aDouble == 0.0) {
// just happens that it was 0.0 and is a double
if (intent.getDoubleExtra(extras[i], 1.0) != 1.0) {
endResult.add(extras[i] + " : " + Double.toString(aDouble));
}
// keep looking...
else {
// lastly check for boolean
boolean aBool = intent.getBooleanExtra(extras[i], false);
// same as default, but not necessarily not a bool (still could be a bool)
if (aBool == false) {
// it is a bool!
if (intent.getBooleanExtra(extras[i], true) != true) {
endResult.add(extras[i] + " : " + Boolean.toString(aBool));
}
else {
//well, the road ends here unless you want to add some more data types
}
}
// it is a bool
else {
endResult.add(extras[i] + " : " + Boolean.toString(aBool));
}
}
}
// is a double
else {
endResult.add(extras[i] + " : " + Double.toString(aDouble));
}
}
}
// is an int value
else {
endResult.add(extras[i] + " : " + Integer.toString(anInt));
}
}
}
// to display at the end
for (int i=0; i<endResult.size(); i++) {
Toast.makeText(this, endResult.get(i), Toast.LENGTH_SHORT).show();
}
The Kotlin version of Pratik's utility method which dumps all extras of an Intent:
fun dumpIntent(intent: Intent) {
val bundle: Bundle = intent.extras ?: return
val keys = bundle.keySet()
val it = keys.iterator()
Log.d(TAG, "Dumping intent start")
while (it.hasNext()) {
val key = it.next()
Log.d(TAG,"[" + key + "=" + bundle.get(key)+"]");
}
Log.d(TAG, "Dumping intent finish")
}
Get it as a string separated with "," in Kotlin!
val extras = intent?.extras?.keySet()?.map { "$it: ${intent.extras?.get(it)}" }?.joinToString { it }
based on ruX answer.
If for debugging all you want is a string (sort of implied by the OP but not explicitly stated), simply use toString on the extras Bundle:
intent.getExtras().toString()
It returns a string such as:
Bundle[{key1=value1, key2=value2, key3=value3}]
Documentation: Bundle.toString() (it's unfortunately the default Object.toString() javadoc and as such quite useless here.)

Android: Can't parse null values from SOAP response (kSoap2)

When parsing the SoapObject data into a String[], the empty fields in the response from the webservice do not get added to it and I can't identify the empty propeties by checking for null or "".
So my problem is basically: The SoapObject contains the right amount of properties, but the parsed result (String[]) does not contain the ones that are empty, nor can I check for empty properties and add "" to the String[].
This causes problems for me when saving to the SQLite DB since every e.g. "User" contains a different amount of fields.
public static String[] getStringArrayResponse(SoapObject node, Vector<String> strings) {
boolean isFirstCall = false;
if (strings == null) {
isFirstCall = true;
strings = new Vector<String>();
}
int count = node.getPropertyCount();
for (int i = 0; i < count; i++) {
Object obj1 = node.getProperty(i);
if (obj1 instanceof SoapObject) {
if (((SoapObject)obj1).getPropertyCount() > 0) {
// Returns the correct amount of properties
Log.d("PARSER", "propertycount = " +((SoapObject)obj1).getPropertyCount());
getStringArrayResponse((SoapObject)obj1, strings);
}
} else if (obj1 instanceof SoapPrimitive) {
strings.add(((SoapPrimitive)obj1).toString());
}
}
if (isFirstCall) {
return (String[])strings.toArray(new String[strings.size()]);
}
return null;
}
This is really giving me a headache and I'm grateful for any help I can get :)
I needed to check for "AnyType{}" :)
if (obj1.toString().equals("anyType{}")){
strings.add("");
}
Just added this block of code below the else if.

Categories

Resources