diff options
Diffstat (limited to 'NotificationListenerSample')
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 Binary files differnew file mode 100644 index 0000000..1aca4b7 --- /dev/null +++ b/NotificationListenerSample/res/drawable-hdpi/dialog_ic_dismiss.png diff --git a/NotificationListenerSample/res/drawable-hdpi/icon.png b/NotificationListenerSample/res/drawable-hdpi/icon.png Binary files differnew file mode 100644 index 0000000..bc976fa --- /dev/null +++ b/NotificationListenerSample/res/drawable-hdpi/icon.png diff --git a/NotificationListenerSample/res/drawable-mdpi/dialog_ic_dismiss.png b/NotificationListenerSample/res/drawable-mdpi/dialog_ic_dismiss.png Binary files differnew file mode 100644 index 0000000..6a2fb5e --- /dev/null +++ b/NotificationListenerSample/res/drawable-mdpi/dialog_ic_dismiss.png diff --git a/NotificationListenerSample/res/drawable-mdpi/icon.png b/NotificationListenerSample/res/drawable-mdpi/icon.png Binary files differnew file mode 100644 index 0000000..e8f7538 --- /dev/null +++ b/NotificationListenerSample/res/drawable-mdpi/icon.png diff --git a/NotificationListenerSample/res/drawable-xhdpi/dialog_ic_dismiss.png b/NotificationListenerSample/res/drawable-xhdpi/dialog_ic_dismiss.png Binary files differnew file mode 100644 index 0000000..e11f2e3 --- /dev/null +++ b/NotificationListenerSample/res/drawable-xhdpi/dialog_ic_dismiss.png diff --git a/NotificationListenerSample/res/drawable-xhdpi/icon.png b/NotificationListenerSample/res/drawable-xhdpi/icon.png Binary files differnew file mode 100644 index 0000000..002515c --- /dev/null +++ b/NotificationListenerSample/res/drawable-xhdpi/icon.png diff --git a/NotificationListenerSample/res/drawable-xxhdpi/dialog_ic_dismiss.png b/NotificationListenerSample/res/drawable-xxhdpi/dialog_ic_dismiss.png Binary files differnew file mode 100644 index 0000000..8d185f4 --- /dev/null +++ b/NotificationListenerSample/res/drawable-xxhdpi/dialog_ic_dismiss.png diff --git a/NotificationListenerSample/res/drawable-xxhdpi/icon.png b/NotificationListenerSample/res/drawable-xxhdpi/icon.png Binary files differnew file mode 100644 index 0000000..e78554f --- /dev/null +++ b/NotificationListenerSample/res/drawable-xxhdpi/icon.png 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; + } + } +} |