I´m looking for a way to get the messages from WhatsApp notifications when there is more than one line.
I´m trying to get the value from a private variable inside an inner class via Reflection in Android. I´m trying to get the 'mTexts' ArrayList of charsequence used to build an InboxStyle Notification. I´m looking for the messages from whatsapp since the last whats update there is no EXTRA on Notifications listened by notificationListener() that have the multiple lines notification (I can only get the first line).
Any way to get the lines is worth it.
This is my code.
#Override
public void onNotificationPosted(StatusBarNotification sbn) {
super.onNotificationPosted(sbn);
Class[] declaredClasses = sbn.getNotification().getClass().getClasses();
for (Class c : declaredClasses){
if(c.getName().contains("Notification$InboxStyle")){
Class inboxStyleClass = c.getClass();
Field[] fields = inboxStyleClass.getDeclaredFields();
for(Field field : fields){
if(field.getName().contains("mText")){
Field fmText = null;
try {
fmText = inboxStyleClass.getDeclaredField("mTexts");
} catch (NoSuchFieldException e) {
e.printStackTrace();
}
ArrayList<CharSequence> mTextsArrayList = null;
fmText.setAccessible(true);
try{
mTextsArrayList = (ArrayList<CharSequence>) fmText.get(**WICH OBJECT MAY USE HERE**);
}catch(IllegalAccessException e){
e.printStackTrace();
}
for(CharSequence value : mTextsArrayList){
Log.i("XXX","Results are: "+value.toString());
}
}
}
}
}
}
I reach the mText field correctly but I can´t get the value from it.
I tried to use a new Notification.InboxStyle() object
Notification.InboxStyle iStyleObjectToGetTheValue = new Notification.InboxStyle();
to see if it works well, and it does
inboxStyle = (ArrayList<CharSequence>) fmText.get(iStyleObjectToGetTheValue);
but I need the values from the notification. Any idea on how can I achieve that?
I also tried to get the message lines inflating the RemoteViews that you can retrieve by StatusBarNotification.getNotification().THE_REMOTE_VIEW because using DeviceMonitor you can take an screenshoot of the device and see the IDs of the views... but had no lucky with that.
Any way to get the lines is worth it.
All the help is welcomed!!
Thanks!!
According to the docs for RemoteView.apply():
View apply(Context context, ViewGroup parent) - Inflates the view hierarchy represented by this object and applies all of the actions.
You can create a parent and provide the context to RemoteView.apply(). Inspect the resulting View to find the text:
#Nullable
public View getBigContentView (StatusBarNotification statusBarNotification) {
RemoteViews bigContentView = statusBarNotification.getNotification().bigContentView;
return bigContentView != null ? bigContentView.apply(ctx, null) : null;
}
This might not work in Nougat phones, as according to the documentation (reading this more carefully tells me that Google probably did not mean for you to read this parameter and that it should only be used when building the notification):
* As of N, this field may be null. The expanded notification view is determined by the
* inputs to {#link Notification.Builder}; a custom RemoteViews can optionally be
* supplied with {#link Notification.Builder#setCustomBigContentView(RemoteViews)}.
Related
I am trying to build a lazy loaded list that shows a set of adaptive cards using adaptive cards android build 2.7.0. I was able to get the first set of data successfully parsed and rendered. But when the second set of data is fetched from the backend, the deserializer complains by throwing the following error.
JNI DETECTED ERROR IN APPLICATION: JNI FindClass called with pending exception java.lang.NullPointerException: null upcall object in AdaptiveCards::BaseCardElementParser::Deserialize
java_vm_ext.cc:577] at long io.adaptivecards.objectmodel.AdaptiveCardObjectModelJNI.AdaptiveCard_DeserializeFromString__SWIG_0(java.lang.String, java.lang.String, long, io.adaptivecards.objectmodel.ParseContext) (AdaptiveCardObjectModelJNI.java:-2)
java_vm_ext.cc:577] at io.adaptivecards.objectmodel.ParseResult io.adaptivecards.objectmodel.AdaptiveCard.DeserializeFromString(java.lang.String, java.lang.String, io.adaptivecards.objectmodel.ParseContext) (AdaptiveCard.java:211)
java_vm_ext.cc:577] at io.adaptivecards.objectmodel.ParseResult com.mrg.cardfeed.PSCardLoader.deserialize(java.lang.String)
error is thrown at the following call
ParseResult parseResult = AdaptiveCard.DeserializeFromString(card, AdaptiveCardRenderer.VERSION, context);
// the simplified version of the method I use to render a card
public void renderCardView(CardDO cardDO) {
try {
RawDO rawcard = cardDO.getRowData();
String card = rawcard.raw_json;
ParseResult parseResult = AdaptiveCard.DeserializeFromString(card, AdaptiveCardRenderer.VERSION, context);
JsonObject jsonObject = JsonParser.parseString(additionalData).getAsJsonObject(); // other data
RenderedAdaptiveCard renderedCard = AdaptiveCardRenderer.getInstance().render(activity, ((FragmentActivity) activity).getSupportFragmentManager(), parseResult.GetAdaptiveCard(), (ICardActionHandler) activity, PSHostConfig.psconfig.current);
for (AdaptiveWarning warning : renderedCard.getWarnings()) {
System.out.println(warning.getMessage());
}
View rendered = renderedCard.getView();
rendered.canScrollVertically(1);
String additionalData = parseResult.GetAdaptiveCard().GetAdditionalProperties().getString(); // for other data
// Present the view
} catch (Exception e) {
System.out.println(e);
}
}
I am using an async task to fetch the data, parse and render the cards. So I assume the problem has something to do with thread safety. Has anyone run in to this problem with adaptive cards android.
Please let me know if I am making a rookie mistake as I am very new to adaptive cards.
Any help is highly appreciated.
The mistake I had made was calling AdaptiveCard.DeserializeFromString() in a seperate thread. Once I moved the deserializing and rendering of the adaptivecard to the main thread, the problem was solved.
sorry if this is a convoluted question. Working on creating an app for a college course and I'm running into (what appears to be) a race condition in my OnCreate method.
TL;DR - sometimes my spinner populates and I can get an index from it. Sometimes it's not populated yet when trying to get a specific index. Details and code below.
The app is a "course scheduler" for a college student.
I'm creating an Activity that displays existing course information and allows you to edit it. In the OnCreate method for this Activity, I am filling a spinner for "Mentors" for the course and a spinner for which "Term" the course belongs in. This information is being pulled from a Room DB.
I have a seperate activity for a new course and for editing a course. For the "new course" activity, everything works fine. I getAllMentors() or getAllTerms() successfully and fill the spinner list.
For the "Edit Course" Activity, there's an extra step involved and it seems to be causing me some issues.
When editing a course, I pass the intent from the originating Activity with all the necessary EXTRAS. This is successful.
In OnCreate for EditCourseActivity, I do the following:
I get the mentorID from the EXTRA that's passed in from the originating Activity.
I access my MentorViewModel and call my getAllMentors() method which returns LiveData> of all mentors in the db.
because it returns LiveData, I use an observer and loop through the LiveData adding the Name of each mentor to a List and the
entire mentor to a List.
I populate my spinner with the information in List full of mentor names.
then I do a for loop, looping through List looking for one that has the same id as what I grabbed form the EXTRA in step 1.
If I find a match in that list, I call a getMentorName() method to snag their name as a string.
I have a methond getIndex(spinner, string) that will loop through the provided spinner, trying to find a match for the string that's
passed in (mentors name) that I grabbed that should match the ID of
the mentor assigned to the course. This method returns index location
of the matched string in the spinner.
I set the spinner selection to the index found.
I do basically the same process for term.
Me being a new developer, I'm not used to OnCreate running the code synchronously.
Because of this, it appears that I have a race condition somewhere between populating the List of mentor names that populates the spinner, and calling my getIndex() method.
Sometimes the spinner is populated and getIndex works properly and sets the correct mentor. Sometimes the spinner is empty and my getIndex() returns -1 (which it should do in a no-find situation) that populates the spinner with the first item in the list (once it's populated).
protected void onCreate(Bundle savedInstanceState) {
//////////////////////////Handling Mentor spinner menu/////////////////////////////////////////////////
int mentorId = courseData.getIntExtra(EXTRA_COURSE_MENTOR_ID, -1);
final ArrayAdapter<String> sp_CourseMentorAdapter = new ArrayAdapter<String>(this, android.R.layout.simple_spinner_item, mentorNameList);
sp_CourseMentorAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
sp_CourseMentor.setAdapter(sp_CourseMentorAdapter);
final MentorViewModel mentorViewModel = ViewModelProviders.of(this).get(MentorViewModel.class);
//Mentor test = mentorViewModel.getMentorById(mentorId);
mentorViewModel.getAllMentors().observe(this, new Observer<List<Mentor>>() {
#Override
public void onChanged(#Nullable List<Mentor> mentorList) {
if (mentorList != null) {
for (Mentor m : mentorList) {
mentorNameList.add(m.getMentor_name());
mentorListMentor.add(m);
}
}
sp_CourseMentorAdapter.notifyDataSetChanged();
}
});
for(Mentor m: mentorListMentor){
if (m.getMentor_id()==mentorId){
String test = m.getMentor_name();
int spinnerSelectionM2 = getIndexM(sp_CourseMentor, test);
sp_CourseMentor.setSelection(spinnerSelectionM2);
}
}
Is there a way to get them to run asynchronously? Somehow to get the observer doing my getAllMentors() to complete first and populate the spinner, THEN have the for loop run?
Or a better way to handle this?
Thanks in advance.
Room always runs the code on a separated thread, not the Main/UI thread. You can change that behavior with
allowMainThreadQueries()
after initializating your database. This will make the query run first, populate your list and then run your for-loop code. I do not recommend this approach, since it is a bad practice to make queries on the UI thread.
You have two options:
Change your foor loop to a function and call it after adding the values from the observer:
mentorViewModel.getAllMentors().observe(this, new Observer<List<Mentor>>() {
#Override
public void onChanged(#Nullable List<Mentor> mentorList) {
if (mentorList != null) {
for (Mentor m : mentorList) {
mentorNameList.add(m.getMentor_name());
mentorListMentor.add(m);
}
lookForMentor();
}
}
});
private void lookForMentor() {
for(Mentor m: mentorListMentor){
if (m.getMentor_id()==mentorId){
String test = m.getMentor_name();
int spinnerSelectionM2 = getIndexM(sp_CourseMentor, test);
sp_CourseMentor.setSelection(spinnerSelectionM2);
}
}
}
Put the for inside the observer, change the Room DAO to return a List and use LiveData on your own viewmodel:
MentorViewModel.java:
MentorViewModel extends ViewModel {
private MutableLiveData<List<Mentor>> _mentorsLiveData = new MutableLiveData<List<Mentor>>();
public LiveData<List<Mentor>> mentorsLiveData = (LiveData) _mentorsLiveData;
void getAllMentors(){
//room db query
_mentorsLiveData.postValue(mentorsList);
}
}
EditActivity.java:
mentorsViewModel.getAllMentors();
mentorViewModel.mentorsLiveData.observe(this, new Observer<List<Mentor>>() {
#Override
public void onChanged(#Nullable List<Mentor> mentorList) {
mentorsListMentor.addAll(mentorList);
sp_CourseMentorAdapter.notifyDataSetChanged();
for(Mentor m: mentorListMentor){
if (m.getMentor_id()==mentorId){
String test = m.getMentor_name();
int spinnerSelectionM2 = getIndexM(sp_CourseMentor, test);
sp_CourseMentor.setSelection(spinnerSelectionM2);
}
}
}
}
});
I'm trying to use a ListView as an Editor for Strings, that come out of a custom data model. I use TextFieldListCells with an appropriate StringConverter for the cells.
There is an add button next to the ListView that calls this method on action:
#FXML
private void addElement() {
WordListItem newItem = new WordListItem(-1, "");
wordListItems.add(newItem);
wordListView.setEditable(true);
wordListView.getSelectionModel().select(wordListItems.indexOf(newItem));
wordListView.edit(wordListItems.indexOf(newItem));
wordListView.setEditable(false);
}
Where wordListView is the ListView and wordListItems is the ObservableList containing the data for the wordListView.
This does work, except for when the list is empty (not null), and I couldn't quite explain why, so I inspected the Java source code for help.
Here's what I found out so far: the edit(int) call on ListView changes the ListViews internal editIndex value, which is supposed to call the EDIT_START Event. The editIndex is an ReadOnlyIntegerWrapper in which I found some weird code that I can't quite understand and I'm not sure if thats actually producing a bug or I just can't see why they did it:
#Override
protected void fireValueChangedEvent() {
super.fireValueChangedEvent();
if (readOnlyProperty != null) {
readOnlyProperty.fireValueChangedEvent();
}
}
This method is called whenever the editIndex property of ListView is changed. The problem: readOnlyProperty is null, because it's not set anywhere. The only place I could find where it got set is in the getter:
public ReadOnlyIntegerProperty getReadOnlyProperty() {
if (readOnlyProperty == null) {
readOnlyProperty = new ReadOnlyPropertyImpl();
}
return readOnlyProperty;
}
(ReadOnlyIntegerImpl is an inner private class and readOnlyProperty is it's type)
Now to my actual question: Is this a bug or am I overseeing something? Is there a reason why I can't add and edit a newly created Element in my list like that when it's empty, or is it really just this getter not being called yet?
The source code you found just is code for lazy initializing the property.
Unless new value is assigned to the property or the property itself is requested, null can be used as the property to avoid unnecessary creation of property objects. This is not an issue here.
The issue seems to be the ListView cells not being updated before edit is called. This happens during layout, so "manually" calling layout before starting the edit should work:
private void addElement() {
WordListItem newItem = new WordListItem(-1, "");
wordListItems.add(newItem);
wordListView.setEditable(true);
wordListView.layout();
wordListView.edit(wordListItems.size()-1);
wordListView.setEditable(false);
}
Intro to me and my application school project
Hi,
iam pretty new with android and for some school project iam building an application where users can configure regions to recieve alerts from. The app need also make it posible to recieve alerts around the current location of the app user.
The app gets its info from a xml feed and sorts the data by the configured regions. The workflow is 1. to get the alerts which are in the configured regions. 2. When gps alerts are enabled the app need to get the location and when it is known it needs to do the first step again but this time the gps region is included. (i need to optimize this proces LATER)
(questions bellow)
intro to my app and problem
I'm using a asynctask in my application to download some xml feed. When the asynctask is ready i need to call 3 places for do something with the result.
1 class saves the result in the local database (alertmanager)
2 fragments (in a tabview) needs to show the results (1 in a map an one in a listview)
Now i use weakreferences for giving the call back "references" to the asynctask. in the onPostExecute() i use theWeakReference.get().updateMethod(result); for updating the class/fragments.
The alertmanager (the class who needs to recieve the updates) also calls a gps manager in the same method where it calls the asynctask to get the gps location. When i comment out (in my case with a if) the line what calls the gps manager the weak reference of the alertmanager will go to null in the asynctask between the constructor (all references are filled) and the doInBackground (the alertmanager reference is null, the other 2 still filled) which results in a crashing app.
When i dont comment out the if the app works fine.....
Alertmanager information
This is the method in the alertmanager who calls the async task. The references are filled on this place.
public void GetAlerts(List<WeakReference<e_Alerts>> callbackReferences, Context context) {
//Update the alerts in the listview and mapview with the local alerts.
List<Alert> localAlerts = internalDc.GetAllAlerts();
try {
for (WeakReference<e_Alerts> callback : callbackReferences) {
callback.get().UpdateAlerts(localAlerts);
}
} catch (Exception e) {
Log.e("AlertManager", e.getMessage());
}
//If connected to the internet then update the local db and the views
if (isConnectingToInternet(context)) {
WeakReference<e_Alerts> wr = new WeakReference<e_Alerts>(this);
callbackReferences.add(wr);
// Update the alerts where no location is needed for so the user has a quick result
externalDc.getAlerts(callbackReferences, areaManager.GetActiveAreas(false));
// If gps region is enabled then find the phones location and update the alerts
if (areaManager.GetGpsArea().IsActive()) {
new GpsManager(this.context, this, callbackReferences);
}
}
}
The GpsManager extends the LocationListener:
public class GpsManager extends Service implements LocationListener {
The listener is implemented by the Alertmanager
// This method is caled by the GPS Manager when the GPS location is changed
#Override
public void OnLocationChanged(Location location, List<WeakReference<e_Alerts>> references) {Area gpsArea = areaManager.GetGpsArea();
gpsArea.SetLocation(location);
areaManager.SaveArea(gpsArea);
externalDc.getAlerts(references, areaManager.GetActiveAreas(true));
}
Asynctask information
This are the asynctask methods:
Asynctask constructor:
Here the list callbackReferences contains 3 weakrefrences and all of them are filled (2x fragment reference 1x alertmanager reference)
public At_allAlerts(List<WeakReference<e_Alerts>> callbackReferences, List<Area> areas) {
this.mCallbackReferences = callbackReferences;
this.mAreas = areas;
}
doInBackground code:
The XmlDownloader: Downloads an xml feed an parses the xml to objects with a library
The AlertConverter: converts the xml object to the object i use in my app
Both classes can work without the asynctask class and don't use the references.
#Override
protected String doInBackground(String... inputUrl) {
Log.i("At_allAlerts", "Asynctask for downloading and parsing mAlerts is started");
try {
//Downloads the alert XMLs from the internet and parses it to xmlAlerts
this.mAlerts = new XmlDownloader().DownloadAlerts(inputUrl);
// Filters the mXml mAlerts so only the mAlerts where the enduser is interessed in will remain
this.mAlerts = filterAlerts(this.mAlerts);
// Converts the remaining xmlAlerts to Alerts;
this.mResult = new AlertConverter().Convert(this.mAlerts);
}catch (Exception e) {
Log.e("At_allAlerts",e.getMessage());
}
return null;
}
The onPostExecute method:
When the programm comes in this method the this.references.get(2) reference (alertmanager reference) = null, the other 2 references are still filed
#Override
protected void onPostExecute(String xml){
for (WeakReference<e_Alerts> reference : activityWeakReferences)
{
reference.get().UpdateAlerts(this.result);
}
}
filterAlerts Method:
private List<Item> filterAlerts(List<Item> alerts) {
List<Item> filteredXmlAlerts = new ArrayList<>();
for (Item alert : alerts)
{
Location alertLocation = new Location("");
alertLocation.setLatitude(alert.getGeometries().get(0).getLocations().get(0).getLat());
alertLocation.setLongitude(alert.getGeometries().get(0).getLocations().get(0).getLng());
for(Area area : this.mAreas)
{
if (area.IsOrganization() && alert.getCountryCode().toLowerCase().equals(area.getOrganizationcode().toLowerCase())){
filteredXmlAlerts.add(alert);
break;
}
else if(!area.IsOrganization() && isAlertInRegion(alertLocation, area)) {
filteredXmlAlerts.add(alert);
break;
}
}
}
return filteredXmlAlerts;
}
My Question(s)
I think Weakreference are the right way for giving references to asynctask is this correct or do i need to give it as an other object? (class or object or whatever?).
Why goes my reference to null? and only one of the 3? and only when i dont use the gps location class? and how to solve this?
I read something about the garbage collector what can be the cause of this problem, is this true and when yes how can i solve this?
It would be fine when the answere are simple to understand since android is pretty new for me.
I'm creating a program, and within which I need to create a list of all the different Android components but rather than figuring out and typing the List by hand. I'd like to figure out programmatically whether I could accomplish this to be added to String Arrays like the below?
Components[] = {"TextView", "ImageView", "RelativeLayout", "LinearLayout", "Random", "DecimalFormat ...
Similarly I would like to programatically create a List of all the different Data-Types e.g. Int, String, ArrayList etc. to be added to String Arrays like the below
DataTypes[] = {"Int", "String", "Object", "Double", "Char", "Boolean ...
What I have been able to do so far, is above. So far I've been physically typing them out as above.
How can I accomplish this? Thanks
Clarification
By data types: I mean variables types declared to holds data e.g Int, String, object, boolean, double, arrays, arraylists etc.
By Components: I mean any visual component which can be added to an Android's xml e.g. ImageView, TextView, LinearLayout, RelativeLayout etc.
Yes, I know the number of these components can be infinite (determined by the APIs in use), for which i would like to generate them dynamically
Preferably without using someone else's library
You don't have to use any third-party libraries, since Java has reflections.
This method will do everything for you and make the UI without problems:
for(String s : arrayWithNames){
View view = createViewInstance(0, s);
if(view instance of View){
//handle if it is view
}else{
//this is viewgroup
}
}
And createViewInstance():
private View createViewInstance(String name){
View view = null;
try{
if(name.equalsIgnoreCase("View"){ // if it is view
Class viewClass = Class.forName("android.view." + name);
view = (View) viewClass.getConstructor(Context.class).newInstance(new Object[]{ctx});
}else{ // if it is widget: ImageView, RelativeLayout etc
Class viewClass = Class.forName("android.widget." + name);
view = (View) viewClass.getConstructor(Context.class).newInstance(new Object[]{ctx});
}
} catch (ClassNotFoundException | InvocationTargetException | NoSuchMethodException
| InstantiationException | IllegalAccessException e) {
e.printStackTrace();
}
return view;
}
That's it. You have everything to handle any kind of View.
I've tested the code above and used in project. It works just fine.
Exactly same situation with other Objects. You cannot create int with reflection, but you can create Integer, so what basically is same.
One problem with it there are much more types than just View and ViewGroup. But it also depends on how many kinds of Objects you want to create... In given example let's say I'll do char, String, int, Object and then you can easily extend it.
for(String s : arrayWithNames){
if(s.equalsIgnoreCase("int")){
Integer integer = (Integer)createVarInstance(s); //ready to use. Integer, not int!
}else if(s.equalsIgnoreCase("String"){
String string = (String)createVarInstance(s);
}else if(s.equalsIgnoreCase("char"){
Character character = (Character)createVarInstance(s); //Character, not char!
}else if(s.equalsIgnoreCase("Object"){
Object object = (Object)createVarInstance(s);
}
}
Since all those data type are in the same package it makes much more easier for us. Method createVarInstance():
private Object createVarInstance(String name){
Object obj = null;
try{
Class varClass = Class.forName("java.lang." + name);
object = (Object) varClass.newInstance();
} catch (ClassNotFoundException | InvocationTargetException | NoSuchMethodException
| InstantiationException | IllegalAccessException e) {
e.printStackTrace();
}
return object;
}
If it will be desired it is possible to make the one method for different packages.
If in future you want to create more and different types of variables, which are in different packages, so you will have to check names or do similar operations as in example with Views.
You can list UI components all the classes into the android.view package or the android.view.View sub-classes using The Reflection Library:
Reflections reflections = new Reflections("android.view");
//Reflections reflections = new Reflections("android.view.View");
Set<Class<? extends Object>> allClasses = reflections.getSubTypesOf(Object.class);
To create a list of all the data-type you could do the same above using th Object class, but first, I'd personally ask myself what's the point doing this.
Reflections is an awesome library for Java. I'm not sure if it works on Android too. Someone opened an issue, it got closed soon after with no explanation.
If you can get it to work in Android, you can use this code to get all Components:
Reflections reflections = new Reflection("android.widget") //specifiy the package (TextView etc. are in here)
Set<Class<? extends View>> components = reflections.getSubTypesOf(View.class);
If you want, you can repeat this procedure with the android.view package, where some other classes such as ViewGroup are located.
Also, if you really want a String Array, get the names of the classes:
List<String> componentNames = new ArrayList<String>();
for (Class<? extends View> c: components) {
componentNames.add(c.getName());
}
String[] allComponents = componentNames.toArray();
If the above solution doesn't work for you, try getting the classes from the official website. The classes are listed in the packages android.view and android.widget. Just copy the relevant names and paste them in your source code. This wouldn't be dynamic, but it works.
how about use a Factory to create Component like this:
public class ComponentFactory {
private HashMap<String, View> mMap;
public ComponentFactory(Context context) {
mMap = new HashMap<String, View>();
mMap.put("TextView", new TextView(context));
...
...
}
public View getComponent(String name) {
View v = mMap.get(name);
if (v == null) {
throw new IlllegalArgumentException();
}
return v;
}
}