aboutsummaryrefslogtreecommitdiff
path: root/NotificationListenerSample
diff options
context:
space:
mode:
Diffstat (limited to 'NotificationListenerSample')
-rw-r--r--NotificationListenerSample/Android.mk33
-rw-r--r--NotificationListenerSample/AndroidManifest.xml37
-rw-r--r--NotificationListenerSample/res/drawable-hdpi/dialog_ic_dismiss.pngbin0 -> 347 bytes
-rw-r--r--NotificationListenerSample/res/drawable-hdpi/icon.pngbin0 -> 7828 bytes
-rw-r--r--NotificationListenerSample/res/drawable-mdpi/dialog_ic_dismiss.pngbin0 -> 313 bytes
-rw-r--r--NotificationListenerSample/res/drawable-mdpi/icon.pngbin0 -> 4287 bytes
-rw-r--r--NotificationListenerSample/res/drawable-xhdpi/dialog_ic_dismiss.pngbin0 -> 398 bytes
-rw-r--r--NotificationListenerSample/res/drawable-xhdpi/icon.pngbin0 -> 12264 bytes
-rw-r--r--NotificationListenerSample/res/drawable-xxhdpi/dialog_ic_dismiss.pngbin0 -> 321 bytes
-rw-r--r--NotificationListenerSample/res/drawable-xxhdpi/icon.pngbin0 -> 22796 bytes
-rw-r--r--NotificationListenerSample/res/layout/item.xml32
-rw-r--r--NotificationListenerSample/res/layout/main.xml50
-rw-r--r--NotificationListenerSample/res/values/strings.xml31
-rw-r--r--NotificationListenerSample/src/com/android/example/notificationlistener/Listener.java251
-rw-r--r--NotificationListenerSample/src/com/android/example/notificationlistener/NotificationListenerActivity.java260
15 files changed, 694 insertions, 0 deletions
diff --git a/NotificationListenerSample/Android.mk b/NotificationListenerSample/Android.mk
new file mode 100644
index 0000000..5075a96
--- /dev/null
+++ b/NotificationListenerSample/Android.mk
@@ -0,0 +1,33 @@
+#
+# Copyright (C) 2014 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_AAPT_FLAGS += -c mdpi,hdpi,xhdpi
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_STATIC_JAVA_LIBRARIES := android-support-v4
+
+LOCAL_PACKAGE_NAME := NotificationListenerSample
+LOCAL_CERTIFICATE := platform
+include $(BUILD_PACKAGE)
+
+include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/NotificationListenerSample/AndroidManifest.xml b/NotificationListenerSample/AndroidManifest.xml
new file mode 100644
index 0000000..e36d422
--- /dev/null
+++ b/NotificationListenerSample/AndroidManifest.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2014 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.example.notificationlistener"
+ android:versionCode="1"
+ android:versionName="1.0">
+
+ <application android:icon="@drawable/icon" android:label="@string/app_name">
+ <activity android:name=".NotificationListenerActivity"
+ android:label="@string/app_name">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ <service android:name="com.android.example.notificationlistener.Listener"
+ android:label="@string/service_name"
+ android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE">
+ <intent-filter>
+ <action android:name="android.service.notification.NotificationListenerService" />
+ </intent-filter>
+ </service>
+ </application>
+</manifest>
diff --git a/NotificationListenerSample/res/drawable-hdpi/dialog_ic_dismiss.png b/NotificationListenerSample/res/drawable-hdpi/dialog_ic_dismiss.png
new file mode 100644
index 0000000..1aca4b7
--- /dev/null
+++ b/NotificationListenerSample/res/drawable-hdpi/dialog_ic_dismiss.png
Binary files differ
diff --git a/NotificationListenerSample/res/drawable-hdpi/icon.png b/NotificationListenerSample/res/drawable-hdpi/icon.png
new file mode 100644
index 0000000..bc976fa
--- /dev/null
+++ b/NotificationListenerSample/res/drawable-hdpi/icon.png
Binary files differ
diff --git a/NotificationListenerSample/res/drawable-mdpi/dialog_ic_dismiss.png b/NotificationListenerSample/res/drawable-mdpi/dialog_ic_dismiss.png
new file mode 100644
index 0000000..6a2fb5e
--- /dev/null
+++ b/NotificationListenerSample/res/drawable-mdpi/dialog_ic_dismiss.png
Binary files differ
diff --git a/NotificationListenerSample/res/drawable-mdpi/icon.png b/NotificationListenerSample/res/drawable-mdpi/icon.png
new file mode 100644
index 0000000..e8f7538
--- /dev/null
+++ b/NotificationListenerSample/res/drawable-mdpi/icon.png
Binary files differ
diff --git a/NotificationListenerSample/res/drawable-xhdpi/dialog_ic_dismiss.png b/NotificationListenerSample/res/drawable-xhdpi/dialog_ic_dismiss.png
new file mode 100644
index 0000000..e11f2e3
--- /dev/null
+++ b/NotificationListenerSample/res/drawable-xhdpi/dialog_ic_dismiss.png
Binary files differ
diff --git a/NotificationListenerSample/res/drawable-xhdpi/icon.png b/NotificationListenerSample/res/drawable-xhdpi/icon.png
new file mode 100644
index 0000000..002515c
--- /dev/null
+++ b/NotificationListenerSample/res/drawable-xhdpi/icon.png
Binary files differ
diff --git a/NotificationListenerSample/res/drawable-xxhdpi/dialog_ic_dismiss.png b/NotificationListenerSample/res/drawable-xxhdpi/dialog_ic_dismiss.png
new file mode 100644
index 0000000..8d185f4
--- /dev/null
+++ b/NotificationListenerSample/res/drawable-xxhdpi/dialog_ic_dismiss.png
Binary files differ
diff --git a/NotificationListenerSample/res/drawable-xxhdpi/icon.png b/NotificationListenerSample/res/drawable-xxhdpi/icon.png
new file mode 100644
index 0000000..e78554f
--- /dev/null
+++ b/NotificationListenerSample/res/drawable-xxhdpi/icon.png
Binary files differ
diff --git a/NotificationListenerSample/res/layout/item.xml b/NotificationListenerSample/res/layout/item.xml
new file mode 100644
index 0000000..2d9a5ed
--- /dev/null
+++ b/NotificationListenerSample/res/layout/item.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2014 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_height="match_parent"
+ android:layout_width="wrap_content"
+ android:padding="4dp">
+ <FrameLayout android:id="@+id/remote_view"
+ android:layout_height="match_parent"
+ android:layout_width="wrap_content"
+ android:onClick="launch"
+ />
+ <ImageButton android:id="@+id/dismiss"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="top|end"
+ android:src="@drawable/dialog_ic_dismiss"
+ android:onClick="dismiss"
+ />
+</FrameLayout> \ No newline at end of file
diff --git a/NotificationListenerSample/res/layout/main.xml b/NotificationListenerSample/res/layout/main.xml
new file mode 100644
index 0000000..44c9da4
--- /dev/null
+++ b/NotificationListenerSample/res/layout/main.xml
@@ -0,0 +1,50 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2014 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical"
+ android:padding="12dp"
+ >
+ <ListView
+ android:id="@android:id/list"
+ android:layout_width="match_parent"
+ android:layout_height="0dp"
+ android:padding="4dp"
+ android:layout_marginBottom="16dp"
+ android:text="@string/working_as_intended"
+ android:layout_weight="1"
+ android:choiceMode="singleChoice"
+ />
+
+ <TextView android:id="@android:id/empty"
+ android:layout_width="match_parent"
+ android:layout_height="0dp"
+ android:padding="4dp"
+ android:layout_marginBottom="16dp"
+ android:text="@string/working_as_intended"
+ android:layout_weight="1"
+ />
+ <Button
+ android:id="@+id/launch_settings"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/launch_to_disable"
+ android:onClick="launchSettings"
+ android:layout_weight="0"
+ />
+</LinearLayout>
+
diff --git a/NotificationListenerSample/res/values/strings.xml b/NotificationListenerSample/res/values/strings.xml
new file mode 100644
index 0000000..5c74fda
--- /dev/null
+++ b/NotificationListenerSample/res/values/strings.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2014 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<resources>
+ <string name="app_name">NotificationListener</string>
+ <string name="long_app_name">Notification Listener Sample</string>
+ <string name="service_name">Sample Notification Listener</string>
+ <string name="working_as_intended">Service is running. Disable it in Security Settings.</string>
+ <string name="waiting_for_content">Service is running. Waiting for notifications to arrive.</string>
+ <string name="nothing_to_see">Service is disabled. You must enabled it to see notifications.</string>
+ <string name="launch_to_disable">Press to disable service</string>
+ <string name="launch_to_enable">Press to enable service</string>
+ <string name="explanation">This app demonstrates use of the Notification Listener Service API.
+ You must enable the listener service in Security Settings to see it work.
+ </string>
+ <string name="disabled">Service is not running.</string>
+ <string name="enable_it">Enable</string>
+ <string name="cancel">Cancel</string>
+</resources>
diff --git a/NotificationListenerSample/src/com/android/example/notificationlistener/Listener.java b/NotificationListenerSample/src/com/android/example/notificationlistener/Listener.java
new file mode 100644
index 0000000..466b084
--- /dev/null
+++ b/NotificationListenerSample/src/com/android/example/notificationlistener/Listener.java
@@ -0,0 +1,251 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.example.notificationlistener;
+
+
+import android.app.Notification;
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Handler;
+import android.os.Message;
+import android.service.notification.NotificationListenerService;
+import android.service.notification.NotificationListenerService.Ranking;
+import android.service.notification.NotificationListenerService.RankingMap;
+import android.service.notification.StatusBarNotification;
+import android.support.v4.content.LocalBroadcastManager;
+import android.text.TextUtils;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+
+public class Listener extends NotificationListenerService {
+ private static final String TAG = "SampleListener";
+
+ // Message tags
+ private static final int MSG_NOTIFY = 1;
+ private static final int MSG_CANCEL = 2;
+ private static final int MSG_STARTUP = 3;
+ private static final int MSG_ORDER = 4;
+ private static final int MSG_DISMISS = 5;
+ private static final int MSG_LAUNCH = 6;
+ private static final int PAGE = 10;
+
+ static final String ACTION_DISMISS = "com.android.example.notificationlistener.DISMISS";
+ static final String ACTION_LAUNCH = "com.android.example.notificationlistener.LAUNCH";
+ static final String ACTION_REFRESH = "com.android.example.notificationlistener.REFRESH";
+ static final String EXTRA_KEY = "key";
+
+ private static ArrayList<StatusBarNotification> sNotifications;
+
+ public static List<StatusBarNotification> getNotifications() {
+ return sNotifications;
+ }
+
+ private final Ranking mTmpRanking = new Ranking();
+
+ private class Delta {
+ final StatusBarNotification mSbn;
+ final RankingMap mRankingMap;
+
+ public Delta(StatusBarNotification sbn, RankingMap rankingMap) {
+ mSbn = sbn;
+ mRankingMap = rankingMap;
+ }
+ }
+
+ private final Comparator<StatusBarNotification> mRankingComparator =
+ new Comparator<StatusBarNotification>() {
+
+ private final Ranking mLhsRanking = new Ranking();
+ private final Ranking mRhsRanking = new Ranking();
+
+ @Override
+ public int compare(StatusBarNotification lhs, StatusBarNotification rhs) {
+ mRankingMap.getRanking(lhs.getKey(), mLhsRanking);
+ mRankingMap.getRanking(rhs.getKey(), mRhsRanking);
+ return Integer.compare(mLhsRanking.getRank(), mRhsRanking.getRank());
+ }
+ };
+
+ private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ String key = intent.getStringExtra(EXTRA_KEY);
+ int what = MSG_DISMISS;
+ if (ACTION_LAUNCH.equals(intent.getAction())) {
+ what = MSG_LAUNCH;
+ }
+ Log.d(TAG, "received an action broadcast " + intent.getAction());
+ if (!TextUtils.isEmpty(key)) {
+ Log.d(TAG, " on " + key);
+ Message.obtain(mHandler, what, key).sendToTarget();
+ }
+ }
+ };
+
+ private final Handler mHandler = new Handler() {
+ @Override
+ public void handleMessage(Message msg) {
+ Delta delta = null;
+ if (msg.obj instanceof Delta) {
+ delta = (Delta) msg.obj;
+ }
+
+ switch (msg.what) {
+ case MSG_NOTIFY:
+ Log.i(TAG, "notify: " + delta.mSbn.getKey());
+ synchronized (sNotifications) {
+ boolean exists = mRankingMap.getRanking(delta.mSbn.getKey(), mTmpRanking);
+ if (!exists) {
+ sNotifications.add(delta.mSbn);
+ } else {
+ int position = mTmpRanking.getRank();
+ sNotifications.set(position, delta.mSbn);
+ }
+ mRankingMap = delta.mRankingMap;
+ Collections.sort(sNotifications, mRankingComparator);
+ Log.i(TAG, "finish with: " + sNotifications.size());
+ }
+ LocalBroadcastManager.getInstance(Listener.this)
+ .sendBroadcast(new Intent(ACTION_REFRESH)
+ .putExtra(EXTRA_KEY, delta.mSbn.getKey()));
+ break;
+
+ case MSG_CANCEL:
+ Log.i(TAG, "remove: " + delta.mSbn.getKey());
+ synchronized (sNotifications) {
+ boolean exists = mRankingMap.getRanking(delta.mSbn.getKey(), mTmpRanking);
+ if (exists) {
+ sNotifications.remove(mTmpRanking.getRank());
+ }
+ mRankingMap = delta.mRankingMap;
+ Collections.sort(sNotifications, mRankingComparator);
+ }
+ LocalBroadcastManager.getInstance(Listener.this)
+ .sendBroadcast(new Intent(ACTION_REFRESH));
+ break;
+
+ case MSG_ORDER:
+ Log.i(TAG, "reorder");
+ synchronized (sNotifications) {
+ mRankingMap = delta.mRankingMap;
+ Collections.sort(sNotifications, mRankingComparator);
+ }
+ LocalBroadcastManager.getInstance(Listener.this)
+ .sendBroadcast(new Intent(ACTION_REFRESH));
+ break;
+
+ case MSG_STARTUP:
+ fetchActive();
+ Log.i(TAG, "start with: " + sNotifications.size() + " notifications.");
+ LocalBroadcastManager.getInstance(Listener.this)
+ .sendBroadcast(new Intent(ACTION_REFRESH));
+ break;
+
+ case MSG_DISMISS:
+ if (msg.obj instanceof String) {
+ final String key = (String) msg.obj;
+ mRankingMap.getRanking(key, mTmpRanking);
+ StatusBarNotification sbn = sNotifications.get(mTmpRanking.getRank());
+ if ((sbn.getNotification().flags & Notification.FLAG_AUTO_CANCEL) != 0 &&
+ sbn.getNotification().contentIntent != null) {
+ try {
+ sbn.getNotification().contentIntent.send();
+ } catch (PendingIntent.CanceledException e) {
+ Log.d(TAG, "failed to send intent for " + sbn.getKey(), e);
+ }
+ }
+ cancelNotification(key);
+ }
+ break;
+
+ case MSG_LAUNCH:
+ if (msg.obj instanceof String) {
+ final String key = (String) msg.obj;
+ mRankingMap.getRanking(key, mTmpRanking);
+ StatusBarNotification sbn = sNotifications.get(mTmpRanking.getRank());
+ if (sbn.getNotification().contentIntent != null) {
+ try {
+ sbn.getNotification().contentIntent.send();
+ } catch (PendingIntent.CanceledException e) {
+ Log.d(TAG, "failed to send intent for " + sbn.getKey(), e);
+ }
+ }
+ if ((sbn.getNotification().flags & Notification.FLAG_AUTO_CANCEL) != 0) {
+ cancelNotification(key);
+ }
+ }
+ break;
+ }
+ }
+ };
+
+ private RankingMap mRankingMap;
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ Log.d(TAG, "registering broadcast listener");
+ final IntentFilter intentFilter = new IntentFilter();
+ intentFilter.addAction(ACTION_DISMISS);
+ intentFilter.addAction(ACTION_LAUNCH);
+ LocalBroadcastManager.getInstance(this).registerReceiver(mBroadcastReceiver, intentFilter);
+ }
+
+ @Override
+ public void onDestroy() {
+ LocalBroadcastManager.getInstance(this).unregisterReceiver(mBroadcastReceiver);
+ super.onDestroy();
+ }
+
+ @Override
+ public void onListenerConnected() {
+ Message.obtain(mHandler, MSG_STARTUP).sendToTarget();
+ }
+
+ @Override
+ public void onNotificationRankingUpdate(RankingMap rankingMap) {
+ Message.obtain(mHandler, MSG_ORDER,
+ new Delta(null, rankingMap)).sendToTarget();
+ }
+
+ @Override
+ public void onNotificationPosted(StatusBarNotification sbn, RankingMap rankingMap) {
+ Message.obtain(mHandler, MSG_NOTIFY,
+ new Delta(sbn, rankingMap)).sendToTarget();
+ }
+
+ @Override
+ public void onNotificationRemoved(StatusBarNotification sbn, RankingMap rankingMap) {
+ Message.obtain(mHandler, MSG_CANCEL,
+ new Delta(sbn, rankingMap)).sendToTarget();
+ }
+
+ private void fetchActive() {
+ mRankingMap = getCurrentRanking();
+ sNotifications = new ArrayList<StatusBarNotification>();
+ for (StatusBarNotification sbn : getActiveNotifications()) {
+ sNotifications.add(sbn);
+ }
+ Collections.sort(sNotifications, mRankingComparator);
+ }
+}
diff --git a/NotificationListenerSample/src/com/android/example/notificationlistener/NotificationListenerActivity.java b/NotificationListenerSample/src/com/android/example/notificationlistener/NotificationListenerActivity.java
new file mode 100644
index 0000000..7699d33
--- /dev/null
+++ b/NotificationListenerSample/src/com/android/example/notificationlistener/NotificationListenerActivity.java
@@ -0,0 +1,260 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.example.notificationlistener;
+
+import android.app.AlertDialog;
+import android.app.ListActivity;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Bundle;
+import android.provider.Settings.Secure;
+import android.service.notification.StatusBarNotification;
+import android.support.v4.content.LocalBroadcastManager;
+import android.util.Log;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.BaseAdapter;
+import android.widget.Button;
+import android.widget.FrameLayout;
+import android.widget.TextView;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+
+public class NotificationListenerActivity extends ListActivity {
+ private static final String LISTENER_PATH = "com.android.example.notificationlistener/" +
+ "com.android.example.notificationlistener.Listener";
+ private static final String TAG = "NotificationListenerActivity";
+
+ private Button mLaunchButton;
+ private TextView mEmptyText;
+ private StatusAdaptor mStatusAdaptor;
+ private final BroadcastReceiver mRefreshListener = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ Log.i(TAG, "update tickle");
+ updateList(intent.getStringExtra(Listener.EXTRA_KEY));
+ }
+ };
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setTitle(R.string.long_app_name);
+ setContentView(R.layout.main);
+ mLaunchButton = (Button) findViewById(R.id.launch_settings);
+ mEmptyText = (TextView) findViewById(android.R.id.empty);
+ mStatusAdaptor = new StatusAdaptor(this);
+ setListAdapter(mStatusAdaptor);
+ }
+
+ @Override
+ protected void onStop() {
+ LocalBroadcastManager.getInstance(this).unregisterReceiver(mRefreshListener);
+ super.onStop();
+ }
+
+ @Override
+ public void onStart() {
+ super.onStart();
+ final IntentFilter intentFilter = new IntentFilter(Listener.ACTION_REFRESH);
+ LocalBroadcastManager.getInstance(this).registerReceiver(mRefreshListener, intentFilter);
+ updateList(null);
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ checkEnabled();
+ }
+
+ protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+ checkEnabled();
+ }
+
+ private void checkEnabled() {
+ String listeners = Secure.getString(getContentResolver(),
+ "enabled_notification_listeners");
+ if (listeners != null && listeners.contains(LISTENER_PATH)) {
+ mLaunchButton.setText(R.string.launch_to_disable);
+ mEmptyText.setText(R.string.waiting_for_content);
+ } else {
+ mLaunchButton.setText(R.string.launch_to_enable);
+ mEmptyText.setText(R.string.nothing_to_see);
+ AlertDialog.Builder builder = new AlertDialog.Builder(this);
+ builder.setMessage(R.string.explanation)
+ .setTitle(R.string.disabled);
+ builder.setPositiveButton(R.string.enable_it, new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int id) {
+ launchSettings(null);
+ }
+ });
+ builder.setNegativeButton(R.string.cancel, null);
+ builder.create().show();
+ }
+ }
+
+ public void launchSettings(View v) {
+ startActivityForResult(
+ new Intent("android.settings.ACTION_NOTIFICATION_LISTENER_SETTINGS"), 0);
+ }
+
+ public void dismiss(View v) {
+ Log.d(TAG, "clicked dismiss ");
+ Object tag = v.getTag();
+ if (tag instanceof StatusBarNotification) {
+ StatusBarNotification sbn = (StatusBarNotification) tag;
+ Log.d(TAG, " on " + sbn.getKey());
+ LocalBroadcastManager.getInstance(this).
+ sendBroadcast(new Intent(Listener.ACTION_DISMISS)
+ .putExtra(Listener.EXTRA_KEY, sbn.getKey()));
+ }
+ }
+
+ public void launch(View v) {
+ Log.d(TAG, "clicked launch");
+ Object tag = v.getTag();
+ if (tag instanceof StatusBarNotification) {
+ StatusBarNotification sbn = (StatusBarNotification) tag;
+ Log.d(TAG, " on " + sbn.getKey());
+ LocalBroadcastManager.getInstance(this).
+ sendBroadcast(new Intent(Listener.ACTION_LAUNCH)
+ .putExtra(Listener.EXTRA_KEY, sbn.getKey()));
+ }
+ }
+
+ private void updateList(String key) {
+ if (mStatusAdaptor.requiresInitialization()) {
+ final List<StatusBarNotification> notifications = Listener.getNotifications();
+ if (notifications != null) {
+ mStatusAdaptor.init(notifications);
+ }
+ }
+ mStatusAdaptor.update(key);
+ }
+
+ private class StatusAdaptor extends BaseAdapter {
+ private final Context mContext;
+ private List<StatusBarNotification> mNotifications;
+ private HashMap<String, Long> mKeyToId;
+ private HashSet<String> mKeys;
+ private long mNextId;
+ private HashMap<String, View> mRecycledViews;
+ private String mUpdateKey;
+
+ public StatusAdaptor(Context context) {
+ mContext = context;
+ mKeyToId = new HashMap<String, Long>();
+ mKeys = new HashSet<String>();
+ mNextId = 0;
+ mRecycledViews = new HashMap<String, View>();
+ }
+
+ @Override
+ public int getCount() {
+ return mNotifications == null ? 0 : mNotifications.size();
+ }
+
+ @Override
+ public Object getItem(int position) {
+ return mNotifications.get(position);
+ }
+
+ @Override
+ public boolean hasStableIds() {
+ return true;
+ }
+
+ @Override
+ public long getItemId(int position) {
+ final StatusBarNotification sbn = mNotifications.get(position);
+ final String key = sbn.getKey();
+ if (!mKeyToId.containsKey(key)) {
+ mKeyToId.put(key, mNextId);
+ mNextId ++;
+ }
+ return mKeyToId.get(key);
+ }
+
+ @Override
+ public View getView(int position, View view, ViewGroup list) {
+ if (view == null) {
+ view = View.inflate(mContext, R.layout.item, null);
+ }
+ FrameLayout container = (FrameLayout) view.findViewById(R.id.remote_view);
+ View dismiss = view.findViewById(R.id.dismiss);
+ StatusBarNotification sbn = mNotifications.get(position);
+ View child;
+ if (container.getTag() instanceof StatusBarNotification &&
+ container.getChildCount() > 0) {
+ // recycle the view
+ StatusBarNotification old = (StatusBarNotification) container.getTag();
+ if (sbn.getKey().equals(mUpdateKey)) {
+ //this view is out of date, discard it
+ mUpdateKey = null;
+ } else {
+ View content = container.getChildAt(0);
+ container.removeView(content);
+ mRecycledViews.put(old.getKey(), content);
+ }
+ }
+ child = mRecycledViews.get(sbn.getKey());
+ if (child == null) {
+ child = sbn.getNotification().contentView.apply(mContext, null);
+ }
+ container.setTag(sbn);
+ container.removeAllViews();
+ container.addView(child);
+ dismiss.setVisibility(sbn.isClearable() ? View.VISIBLE : View.GONE);
+ dismiss.setTag(sbn);
+ return view;
+ }
+
+ public void update(String key) {
+ if (mNotifications != null) {
+ synchronized (mNotifications) {
+ mKeys.clear();
+ for (int i = 0; i < mNotifications.size(); i++) {
+ mKeys.add(mNotifications.get(i).getKey());
+ }
+ mKeyToId.keySet().retainAll(mKeys);
+ }
+ if (key == null) {
+ mRecycledViews.clear();
+ } else {
+ mUpdateKey = key;
+ mRecycledViews.remove(key);
+ }
+ Log.d(TAG, "notifyDataSetChanged");
+ notifyDataSetChanged();
+ } else {
+ Log.d(TAG, "missed and update");
+ }
+ }
+
+ public boolean requiresInitialization() {
+ return mNotifications == null;
+ }
+
+ public void init(List<StatusBarNotification> notifications) {
+ mNotifications = notifications;
+ }
+ }
+}