I'm making an accessibility service that includes notifications, I was wondering if it's possible to get the icon bitmap and "details" of the notifications, I know that the tickertext can provide the "title of the notification" but I would like to access the line that show the "details text" formerly the "getText" method of the contentview field of the notification.
It is possible to get more details and even reconstruct the Notification object. I haven't tested this, but it should work fine.
#Override
public void onAccessibilityEvent(AccessibilityEvent event) {
...
if (event.getEventType() == AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED) {
Parcelable parcelable = event.getParcelableData();
if (parcelable instanceof Notification) {
// Resource id within the package context of the notifying app.
int icon = ((Notification) parcelable).icon;
Resources res = getPackageManager().getResourcesForApplication(event.getPackageName());
Drawable image = res.getDrawable(icon);
}
}
}
Related
This is my code.In my custom AccessibilityService,I get all TextView by the method,onAccessibilityEvent.And then I want to mark the text.
public void onAccessibilityEvent(AccessibilityEvent event) {
AccessibilityNodeInfo source = event.getSource();
if (source == null) {
return;
}
for (int i = 0; i < source.getChildCount(); i++) {
AccessibilityNodeInfo node = source.getChild(i);
if (TEXTVIEW.equals(node.getClassName())) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
Bundle arguments = new Bundle();
arguments.putCharSequence(AccessibilityNodeInfo.ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE,
node.getText().toString()+1);//just want to mark
node.performAction(AccessibilityNodeInfo.ACTION_SET_TEXT, arguments);
}
}
}
}
This bit of code works fine for me!
public void onAccessibilityEvent(AccessibilityEvent e) {
if (e.getSource() == null) return;
if (e.getSource().getClassName().equals(EditText.class.getName())) {
Bundle arguments = new Bundle();
arguments.putCharSequence(AccessibilityNodeInfo.ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE,
"android");
e.getSource().performAction(AccessibilityNodeInfo.ACTION_SET_TEXT, arguments);
}
}
And happily places the text "android" into the editable text container! AH, now, if you want to change the text of a TextView, now, this is an illegal thing to try and do. The text of a TextView is a readonly property. You can't edit it. An accessibility node info is an accessibility services representation of a rendered view. You can't ask the App to change the text of a TextView. This has MASSIVE security implications. Oh, so you have a service that tells everyone that the Password field is the Username field and vice versa? Isn't that handy? Basically, you can only edit the text of a view that makes sense to be Editable.
That being said, you can tell the user that the text is whatever you want to tell them it is, when you provide feedback. Not that I would use an accessibility service that spoke "kitten" for every view in the hierarchy... but, your use case/what you want to accomplish is not clear. If you would provide another question that asks "This is what I want to accomplish..." instead of "this is what I tried, why doesn't it work?" I would happily help.
My app is using a NotificationListener to read out messages from various 3rd party apps, for example WhatsApp.
So far I was able to send a reply if only one chat is unread, the code is below.
However, in the case with WhatsApp, getNotification().actions returns a null object when more than two chats are unread, as the messages are bundled together. As you can see in the pictures below, if the notifications are extended there is an option to send a direct reply as well, therefore I am certain that it is possible to utilize this, also I think apps like PushBullet are using this method.
How could I access the RemoteInput of that notification?
public static ReplyIntentSender sendReply(StatusBarNotification statusBarNotification, String name) {
Notification.Action actions[] = statusBarNotification.getNotification().actions;
for (Notification.Action act : actions) {
if (act != null && act.getRemoteInputs() != null) {
if (act.title.toString().contains(name)) {
if (act.getRemoteInputs() != null)
return new ReplyIntentSender(act);
}
}
}
return null;
}
public static class ReplyIntentSender {
[...]
public final Notification.Action action;
public ReplyIntentSender(Notification.Action extractedAction) {
action = extractedAction;
[...]
}
private boolean sendNativeIntent(Context context, String message) {
for (android.app.RemoteInput rem : action.getRemoteInputs()) {
Intent intent = new Intent();
Bundle bundle = new Bundle();
bundle.putCharSequence(rem.getResultKey(), message);
android.app.RemoteInput.addResultsToIntent(action.getRemoteInputs(), intent, bundle);
try {
action.actionIntent.send(context, 0, intent);
} catch (Exception e) {
e.printStackTrace();
return false;
}
return true;
}
return false;
}
}
Some explanation how the above code works: Once a notification is received the app tries to get the actions and checks if the name is in the title of a remoteInput (normally it is in the format of "Reply to $NAME"), if that is found the Action is saved into a ReplyIntentSender class, which, when triggered by sendNativeIntent, cycles through all RemoteInputs of that Action and adds the message to the intent. If more than one chat is unread, getNotification().actions returns null.
Below are two screenshots, the first one where it is working without any problems and the second one where it doesn't.
You can consider this as my suggestion. I have done bit research on this and come up with following conclusions.(Also it looks like you have done plenty of research on this so it might be possible that you aware about what I wrote below)
Numerous apps send Wear specific notifications, and many of those contain actions accessible from an Android Wear device. We can grab those Wear notifications on the device, extracting the actions, finding the reply action (if one exists), populating it with our own response and then executing the PendingIntent which sends our response back the original app for it to send on to the recipient.
To do so you can refer this link (A nice workaround by Rob J). You can also refer this link in this context (Great research work done by MichaĆ Tajchert).(You might need to work around with NotificationCompat.isGroupSummary)
This is what I feel(Might be I am totally wrong)
.actions method returns Array of all Notification.Action
structures attached to current notification by addAction(int,
CharSequence, PendingIntent), Here addAction method is deprecated
one so it might not working as intended.
I am not able to test this at my end otherwise I will love to provide a working solution with code.
Hope this will help you. Happy Coding!!!
I'm creating a notification management app and I want to get the contents of notifications which other apps show. currently I use codes like this :
statusBarNotification.getNotification().extras.getString(Notification.EXTRA_TITLE);
and this :
statusBarNotification.getNotification().extras.getString(Notification.EXTRA_TEXT);
to read the title and text of notifications. but after a few hours I couldn't find a way to get the image which comes along with the notification's text. for example a profile picture which is showed in Whatsapp's notification. I know it's not the Small or Large icons, I checked a few times.
So if anyone could help in any way, it would be much appreciated
I assume you use NotificationListenerService to listen to notification from other app.
In you NotificationService class, extract icon ressource id in extra Notification.EXTRA_SMALL_ICON and access the other app package ressources to get the Drawable.
Notification.EXTRA_PICTURE contains the large image sent in the notification :
public class NotificationService extends NotificationListenerService {
Context context;
#Override
public void onCreate() {
super.onCreate();
context = getApplicationContext();
}
#Override
public void onNotificationPosted(StatusBarNotification statusBarNotification) {
// a notification is posted
String pack = statusBarNotification.getPackageName();
Bundle extras = statusBarNotification.getNotification().extras;
int iconId = extras.getInt(Notification.EXTRA_SMALL_ICON);
try {
PackageManager manager = getPackageManager();
Resources resources = manager.getResourcesForApplication(pack);
Drawable icon = resources.getDrawable(iconId);
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
}
if (extras.containsKey(Notification.EXTRA_PICTURE)) {
// this bitmap contain the picture attachment
Bitmap bmp = (Bitmap) extras.get(Notification.EXTRA_PICTURE);
}
}
#Override
public void onNotificationRemoved(StatusBarNotification statusBarNotification) {
//call when notification is removed
}
}
First off I am using Xamarin Forms for a WP8, iOS and Android app.
Goal:
I want to go to a specific page when the toast is clicked depending
upon the payload information of the toast notification.
I have push notifications using Azure Notification Hubs all setup and working well. I use MVVMLight and their dependency injection to setup push notifications specifically for each platform.
Each payload needs to be sent a little different due to the different formats required. With each you will notice I want to send a SignalId in the payload to perform a different action as required on the receiving device from regular push notifications.
Android
{
"data" : {
"msg" : "message in here",
"signalId" : "id-in-here",
},
}
iOS
{
"aps" : { "alert" : "message in here" },
"signalId" : "id-in-here"
}
Windows Phone 8
<?xml version="1.0" encoding="utf-8"?>
<wp:Notification xmlns:wp="WPNotification">
<wp:Toast>
<wp:Text1>category</wp:Text1>
<wp:Text2>message in here</wp:Text2>
<wp:Param>?signalId=id-in-here</wp:Param>
</wp:Toast>
</wp:Notification>
.
Question:
How do I get this information in a Xamarin Forms app and redirect to
the appropriate page when the application is reactivated because the
user clicked on the toast notification?
I want to get the payload information when the app loads, then say, yes this contains a SignalId, lets redirect to this page.
At the moment all it does it show the application when a toast notification is clicked. Must I do it specific to the app, or is there a Xamarin Forms way?
Any help appreciated even if you only know how to do it for one platform, I can probably work my way around the other platforms from there.
I have found the way to do it for all platforms. Windows has been tested, Android and iOS haven't.
Windows and iOS work on a show toast notification if the app is in the background, or let your code deal with it if the app is in the foreground. Android shows the toast regardless of application status.
With Windows Phone 8 I need to go to the MainPage.xaml.cs and add in this override.
protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e)
{
base.OnNavigatedTo(e);
if (this.NavigationContext.QueryString.ContainsKey("signalId"))
{
var signalId = this.NavigationContext.QueryString["signalId"];
var id = Guid.Empty;
if (signalId != null
&& Guid.TryParse(signalId, out id)
&& id != Guid.Empty)
{
this.NavigationContext.QueryString.Clear();
Deployment.Current.Dispatcher.BeginInvoke(() =>
{
// Do my navigation to a new page
});
}
}
}
For Android in the GcmService
protected override void OnMessage(Context context, Intent intent)
{
Log.Info(Tag, "GCM Message Received!");
var message = intent.Extras.Get("msg").ToString();
var signalId = Guid.Empty;
if (intent.Extras.ContainsKey("signalId"))
{
signalId = new Guid(intent.Extras.Get("signalId").ToString());
}
// Show notification as usual
CreateNotification("", message, signalId);
}
Then in the CreateNotification function put some extra information in the Intent.
var uiIntent = new Intent(this, typeof(MainActivity));
if (signalId != Guid.Empty)
{
uiIntent.PutExtra("SignalId", signalId.ToString());
}
Then in the MainActivity.cs override this function
protected override void OnActivityResult(int requestCode, Result resultCode, Intent data)
{
if (data.HasExtra("SignalId"))
{
Guid signalId = new Guid(data.GetStringExtra("SignalId"));
if (signalId != Guid.Empty)
{
data.RemoveExtra("SignalId");
// Do you navigation
}
}
}
In iOS you will notice I have enhanced the default ProcessNotification()
void ProcessNotification(NSDictionary options, bool fromFinishedLaunching)
{
// Check to see if the dictionary has the aps key. This is the notification payload you would have sent
if (null != options && options.ContainsKey(new NSString("aps")))
{
//Get the aps dictionary
var aps = options.ObjectForKey(new NSString("aps")) as NSDictionary;
var alert = string.Empty;
//Extract the alert text
// NOTE: If you're using the simple alert by just specifying
// " aps:{alert:"alert msg here"} " this will work fine.
// But if you're using a complex alert with Localization keys, etc.,
// your "alert" object from the aps dictionary will be another NSDictionary.
// Basically the json gets dumped right into a NSDictionary,
// so keep that in mind.
if (aps.ContainsKey(new NSString("alert")))
alert = ((NSString) aps[new NSString("alert")]).ToString();
// If this came from the ReceivedRemoteNotification while the app was running,
// we of course need to manually process things like the sound, badge, and alert.
if (!fromFinishedLaunching)
{
//Manually show an alert
if (!string.IsNullOrEmpty(alert))
{
var signalId = new Guid(options.ObjectForKey(new NSString("signalId")) as NSString);
// Show my own toast with the signalId
}
}
}
}
Then in the FinishedLaunching function check if there is any payload
// Check if any payload from the push notification
if (options.ContainsKey("signalId"))
{
var signalId = new Guid(options.ObjectForKey(new NSString("signalId")) as NSString);
// Do the navigation here
}
Urban Airship recommends creating a custom notification with CustomPushNotificationBuilder if you want to make any modifications to the status bar notification, including trivially changing the icon.
Unfortunately, using a RemoteView for notifications carries many unwanted implications with it related to custom manufacturer and/or platform-specific skins, including text colors and references to private resources (for instance #*android:drawable/notify_panel_notification_icon_bg_tile on Honeycomb/ICS).
There must be a simple way to swap the icon without using RemoteView. How?
I found that by overriding BasicPushNotificationBuilder, I can set the icon quite trivially:
BasicPushNotificationBuilder nb = new BasicPushNotificationBuilder() {
#Override
public Notification buildNotification(String alert,
Map<String, String> extras) {
Notification notification = super.buildNotification(alert,
extras);
// The icon displayed in the status bar
notification.icon = R.drawable.notification;
// The icon displayed within the notification content
notification.contentView.setImageViewResource(
android.R.id.icon, R.drawable.notification);
return notification;
}
};
// Set the custom notification builder
PushManager.shared().setNotificationBuilder(nb);
I know this is an old question, but UrbanAirship get's updated quite often, so I decided to help others who might reach this page. As of version 6.0.1 there's no BasicNotificationBuilder no more. In order to customize your notification with icon and color and whatnot, you need to extend the NotifcationFactory class, and override the createNotification method.
Like shown in the example below:
public class MyNotificationFactory extends NotificationFactory {
public MyNotificationFactory(Context context){
super(context);
}
#Override
public Notification createNotification(PushMessage pushMessage, int i) {
NotificationCompat.Builder builder = new NotificationCompat.Builder(getContext())
.setContentTitle(getContext().getResources().getString(R.string.app_name))
.setContentText(pushMessage.getAlert())
.setSmallIcon(R.drawable.your_icon_here)
.setColor(getContext().getResources().getColor(R.color.your_color_here))
.setAutoCancel(true);
return builder.build();
}
#Override
public int getNextId(PushMessage pushMessage) {
return NotificationIDGenerator.nextID();
}
}
At last you must set this as UrbanAirship's new notification factory in your application class or wherever you initialized UA:
UAirship.shared().getPushManager().setNotificationFactory(new MyNotificationFactory(getApplicationContext()));
We provide a lot of functionality in our default notification factory such as big styles (inbox, text, image), lollipop features (privacy, priority), and interactive notification buttons. If you are only trying to set the icon and maybe the accent color, I recommend the following:
UAirship.takeOff(this, new UAirship.OnReadyCallback() {
#Override
public void onAirshipReady(UAirship airship) {
// Perform any airship configurations here
// Create a customized default notification factory
DefaultNotificationFactory defaultNotificationFactory = new DefaultNotificationFactory(getApplicationContext());
defaultNotificationFactory.setSmallIconId(R.drawable.ic_notification);
defaultNotificationFactory.setColor(NotificationCompat.COLOR_DEFAULT);
// Set it
airship.getPushManager().setNotificationFactory(defaultNotificationFactory);
}
});
Full docs on the class can be found here - http://docs.urbanairship.com/reference/libraries/android/latest/reference/com/urbanairship/push/notifications/DefaultNotificationFactory.html