How to write a DashClock Extension: Whatsapp Extension example
After BatteryExtension and DialExtension, now I tried to write an unofficial Whatsapp Extension for DashClock.
It was not so easy...
WhatsApp does not expose an API or official ContentProvider and therefore the only idea I had was to listen for status bar notifications.
It requires you to enable a Accessibility Service to work.
I don't like this way, because it needs relatively deep/dangerous permission authorizations.
At present, I would't install an application that requires this type of authorization.
With these permissions, an app can listen for all notification from all apps...
You can specify which packages you want to listen to and only listen to those package, but users don't have visibility of this information.
It would be a good idea if in the next version of Android this information was available.
You can listen for status bar notifications by using
We are going to write our WhtNotificationService that extends
Here you can find official doc.
Here you can find a single event.
D/WhtNotificationService(9122): new Event=EventType: TYPE_NOTIFICATION_STATE_CHANGED;
EventTime: 152881074; PackageName: com.whatsapp; MovementGranularity: 0;
Action: 0 [ ClassName: android.app.Notification;
Text: [Message from Gabriele Mariotti]; ContentDescription: null; ItemCount: -1; CurrentItemIndex: -1; IsEnabled: false; IsPassword: false; IsChecked: false;
IsFullScreen: false; Scrollable: false; BeforeText: null; FromIndex: -1; ToIndex: -1; ScrollX: -1; ScrollY: -1;
MaxScrollX: -1; MaxScrollY: -1; AddedCount: -1; RemovedCount: -1;
ParcelableData: Notification(pri=0 contentView=com.whatsapp/0x102013f vibrate=null sound=null defaults=0x0 flags=0x0
kind=[null]) ]; recordCount: 0
We have to declare it in Manifest file:
Also android:description (that I didn't find in doc) is very important because it is the only way to explain users why you are going to use this service.
After you install the application you must activate accessibility service from Accessibility item in Android Settings
In the event there aren't much information. The only method useful in this case is
I used a MessageManager to connect accessibility service with DashClockExtension.
Below our dashclock extension:
The last issue was how to clear messages and message count when user is present after device wakes up.
The ideal would be a intent Broadcast from Notification Area, but I don't know about it.
I chose to have a broadcast receiver in DashClock Extension:
You can get code from GitHub:
It was not so easy...
WhatsApp does not expose an API or official ContentProvider and therefore the only idea I had was to listen for status bar notifications.
It requires you to enable a Accessibility Service to work.
I don't like this way, because it needs relatively deep/dangerous permission authorizations.
At present, I would't install an application that requires this type of authorization.
With these permissions, an app can listen for all notification from all apps...
You can specify which packages you want to listen to and only listen to those package, but users don't have visibility of this information.
It would be a good idea if in the next version of Android this information was available.
You can listen for status bar notifications by using
AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED
.We are going to write our WhtNotificationService that extends
AccessibilityService
.Here you can find official doc.
public class WhtNotificationService extends AccessibilityService { private final static String TAG="WhtNotificationService"; public static final String PACKAGE_NAME = "com.whatsapp"; @Override public void onAccessibilityEvent(AccessibilityEvent event) { Log.d(TAG,"new Event="+event.toString()); MessageManager manager = MessageManager.getInstance(); if (manager != null) { if (manager.getmReceiver()!=null && !manager.getmReceiver().isUserActive()){ MessageWht msg = new MessageWht(); msg.setText(event.getText()); manager.notifyListener(msg); } } } @Override public void onInterrupt() {} }This class listens for whatsapp notifications on status bar.
Here you can find a single event.
D/WhtNotificationService(9122): new Event=EventType: TYPE_NOTIFICATION_STATE_CHANGED;
EventTime: 152881074; PackageName: com.whatsapp; MovementGranularity: 0;
Action: 0 [ ClassName: android.app.Notification;
Text: [Message from Gabriele Mariotti]; ContentDescription: null; ItemCount: -1; CurrentItemIndex: -1; IsEnabled: false; IsPassword: false; IsChecked: false;
IsFullScreen: false; Scrollable: false; BeforeText: null; FromIndex: -1; ToIndex: -1; ScrollX: -1; ScrollY: -1;
MaxScrollX: -1; MaxScrollY: -1; AddedCount: -1; RemovedCount: -1;
ParcelableData: Notification(pri=0 contentView=com.whatsapp/0x102013f vibrate=null sound=null defaults=0x0 flags=0x0
kind=[null]) ]; recordCount: 0
We have to declare it in Manifest file:
In this case we chose to use a config xml file (we can configure our Accessibility Service with code).
In this file we configure events type and packageNames from which we will listen notifications.
Also android:description (that I didn't find in doc) is very important because it is the only way to explain users why you are going to use this service.
After you install the application you must activate accessibility service from Accessibility item in Android Settings
In the event there aren't much information. The only method useful in this case is
getText()
which returns a [Message from Gabriele Mariotti]. It not so easy to parse this text because it depends from language and from sender name.I used a MessageManager to connect accessibility service with DashClockExtension.
public class MessageManager { private final static String TAG="MessageManager"; private WhatsappExtension mWhtExtension; private static MessageManager sInstance; private SomethingOnScreenReceiver mReceiver; public static MessageManager getInstance(WhatsappExtension context) { if (sInstance == null) { sInstance = new MessageManager(context); } return sInstance; } public static MessageManager getInstance() { return sInstance; } private MessageManager(WhatsappExtension context) { mWhtExtension = context; mCount = 0; mMsgs = new ArrayListThis class stores information about messages (count and text).(); } private int mCount; private ArrayList mMsgs; /** * Notify for new Message * @param msg */ public void notifyListener(MessageWht msg) { Log.d(TAG,"new Message"); if (mWhtExtension != null){ mCount++; mMsgs.add(msg); mWhtExtension.changeMessage(); } } /** * Reset counter and clear old messages */ public void clearMessages(){ Log.d(TAG,"Clear Messages"); mCount=0; mMsgs=new ArrayList (); if (mWhtExtension!=null) mWhtExtension.changeMessage(); } public int getmCount() { return mCount; } public ArrayList getmMsgs() { return mMsgs; } public SomethingOnScreenReceiver getmReceiver() { return mReceiver; } public void setmReceiver(SomethingOnScreenReceiver mReceiver) { this.mReceiver = mReceiver; } }
Below our dashclock extension:
public class WhatsappExtension extends DashClockExtension { private static final String TAG = "WhatsappExtension"; private MessageManager mManager; private SomethingOnScreenReceiver mReceiver; @Override protected void onInitialize(boolean isReconnect) { super.onInitialize(isReconnect); if (!isReconnect) { mManager = MessageManager.getInstance(this); registerReceiver(); mManager.setmReceiver(mReceiver); } } public void changeMessage() { onUpdateData(UPDATE_REASON_CONTENT_CHANGED); } @Override protected void onUpdateData(int reason) { if (mManager!=null){ Log.d(TAG,"onUpdateData msgCount="+mManager.getmCount()); if (mManager.getmCount() > 0) { // publish publishUpdateExtensionData(); } else clearUpdateExtensionData(); } } /** * Clear DashClock */ private void clearUpdateExtensionData() { publishUpdate(null); } /** * publishUpdata */ private void publishUpdateExtensionData() { if (mManager!=null){ // Intent PackageManager pm = getPackageManager(); Intent intentWht=pm.getLaunchIntentForPackage(WhtNotificationService.PACKAGE_NAME); int mCount=mManager.getmCount(); ExtensionData data= new ExtensionData().visible(true) .icon(R.drawable.ic_extension_wht) .status(""+mCount) .expandedTitle(getResources().getQuantityString( R.plurals.messagecount, mCount, mCount)); if (intentWht!=null) data.clickIntent(intentWht); // Publish the extension data update. publishUpdate(data); } }With this code when a new message will be notified, the accessibility service will catch the event, and through the MessageManager, it will update dashclock extension.
The last issue was how to clear messages and message count when user is present after device wakes up.
The ideal would be a intent Broadcast from Notification Area, but I don't know about it.
I chose to have a broadcast receiver in DashClock Extension:
private void registerReceiver(){ IntentFilter localIntentFilter = new IntentFilter(); localIntentFilter.addAction(Intent.ACTION_SCREEN_OFF); localIntentFilter.addAction(Intent.ACTION_SCREEN_ON); localIntentFilter.addAction(Intent.ACTION_USER_PRESENT); MessageManager manager = MessageManager.getInstance(); if (manager!=null){ mReceiver = new SomethingOnScreenReceiver(manager); registerReceiver(mReceiver, localIntentFilter); } } @Override public void onDestroy() { // TODO Auto-generated method stub super.onDestroy(); if (mReceiver!=null) unregisterReceiver(mReceiver); }The Broadcast Receiver will reset counter and messages.
public class SomethingOnScreenReceiver extends BroadcastReceiver { private boolean userActive = false; private boolean onScreen = false; private MessageManager mManager; public SomethingOnScreenReceiver(MessageManager manager){ mManager=manager; } @Override public void onReceive(Context context, Intent intent) { if (intent.getAction().equals(Intent.ACTION_SCREEN_ON)) { onScreen = true; userActive=false; }else if (intent.getAction().equals(Intent.ACTION_SCREEN_OFF)){ onScreen=false; userActive=false; }else if (intent.getAction().equals(Intent.ACTION_USER_PRESENT)){ userActive=true; if (onScreen && mManager!=null){ mManager.clearMessages(); } } } public boolean isUserActive() { return userActive; } }
You can get code from GitHub:
Comments
Post a Comment