1/* 2 * Copyright (C) 2014 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16package com.android.example.notificationlistener; 17 18import android.app.AlertDialog; 19import android.app.ListActivity; 20import android.content.BroadcastReceiver; 21import android.content.Context; 22import android.content.DialogInterface; 23import android.content.Intent; 24import android.content.IntentFilter; 25import android.os.Bundle; 26import android.provider.Settings.Secure; 27import android.service.notification.StatusBarNotification; 28import android.support.v4.content.LocalBroadcastManager; 29import android.util.Log; 30import android.view.View; 31import android.view.ViewGroup; 32import android.widget.BaseAdapter; 33import android.widget.Button; 34import android.widget.FrameLayout; 35import android.widget.TextView; 36 37import java.util.HashMap; 38import java.util.HashSet; 39import java.util.List; 40 41public class NotificationListenerActivity extends ListActivity { 42 private static final String LISTENER_PATH = "com.android.example.notificationlistener/" + 43 "com.android.example.notificationlistener.Listener"; 44 private static final String TAG = "NotificationListenerActivity"; 45 46 private Button mLaunchButton; 47 private TextView mEmptyText; 48 private StatusAdaptor mStatusAdaptor; 49 private final BroadcastReceiver mRefreshListener = new BroadcastReceiver() { 50 @Override 51 public void onReceive(Context context, Intent intent) { 52 Log.i(TAG, "update tickle"); 53 updateList(intent.getStringExtra(Listener.EXTRA_KEY)); 54 } 55 }; 56 57 @Override 58 public void onCreate(Bundle savedInstanceState) { 59 super.onCreate(savedInstanceState); 60 setTitle(R.string.long_app_name); 61 setContentView(R.layout.main); 62 mLaunchButton = (Button) findViewById(R.id.launch_settings); 63 mEmptyText = (TextView) findViewById(android.R.id.empty); 64 mStatusAdaptor = new StatusAdaptor(this); 65 setListAdapter(mStatusAdaptor); 66 } 67 68 @Override 69 protected void onStop() { 70 LocalBroadcastManager.getInstance(this).unregisterReceiver(mRefreshListener); 71 super.onStop(); 72 } 73 74 @Override 75 public void onStart() { 76 super.onStart(); 77 final IntentFilter intentFilter = new IntentFilter(Listener.ACTION_REFRESH); 78 LocalBroadcastManager.getInstance(this).registerReceiver(mRefreshListener, intentFilter); 79 updateList(null); 80 } 81 82 @Override 83 public void onResume() { 84 super.onResume(); 85 checkEnabled(); 86 } 87 88 protected void onActivityResult(int requestCode, int resultCode, Intent data) { 89 checkEnabled(); 90 } 91 92 private void checkEnabled() { 93 String listeners = Secure.getString(getContentResolver(), 94 "enabled_notification_listeners"); 95 if (listeners != null && listeners.contains(LISTENER_PATH)) { 96 mLaunchButton.setText(R.string.launch_to_disable); 97 mEmptyText.setText(R.string.waiting_for_content); 98 } else { 99 mLaunchButton.setText(R.string.launch_to_enable); 100 mEmptyText.setText(R.string.nothing_to_see); 101 AlertDialog.Builder builder = new AlertDialog.Builder(this); 102 builder.setMessage(R.string.explanation) 103 .setTitle(R.string.disabled); 104 builder.setPositiveButton(R.string.enable_it, new DialogInterface.OnClickListener() { 105 public void onClick(DialogInterface dialog, int id) { 106 launchSettings(null); 107 } 108 }); 109 builder.setNegativeButton(R.string.cancel, null); 110 builder.create().show(); 111 } 112 } 113 114 public void launchSettings(View v) { 115 startActivityForResult( 116 new Intent("android.settings.ACTION_NOTIFICATION_LISTENER_SETTINGS"), 0); 117 } 118 119 public void dismiss(View v) { 120 Log.d(TAG, "clicked dismiss "); 121 Object tag = v.getTag(); 122 if (tag instanceof StatusBarNotification) { 123 StatusBarNotification sbn = (StatusBarNotification) tag; 124 Log.d(TAG, " on " + sbn.getKey()); 125 LocalBroadcastManager.getInstance(this). 126 sendBroadcast(new Intent(Listener.ACTION_DISMISS) 127 .putExtra(Listener.EXTRA_KEY, sbn.getKey())); 128 } 129 } 130 131 public void launch(View v) { 132 Log.d(TAG, "clicked launch"); 133 Object tag = v.getTag(); 134 if (tag instanceof StatusBarNotification) { 135 StatusBarNotification sbn = (StatusBarNotification) tag; 136 Log.d(TAG, " on " + sbn.getKey()); 137 LocalBroadcastManager.getInstance(this). 138 sendBroadcast(new Intent(Listener.ACTION_LAUNCH) 139 .putExtra(Listener.EXTRA_KEY, sbn.getKey())); 140 } 141 } 142 143 private void updateList(String key) { 144 if (mStatusAdaptor.requiresInitialization()) { 145 final List<StatusBarNotification> notifications = Listener.getNotifications(); 146 if (notifications != null) { 147 mStatusAdaptor.init(notifications); 148 } 149 } 150 mStatusAdaptor.update(key); 151 } 152 153 private class StatusAdaptor extends BaseAdapter { 154 private final Context mContext; 155 private List<StatusBarNotification> mNotifications; 156 private HashMap<String, Long> mKeyToId; 157 private HashSet<String> mKeys; 158 private long mNextId; 159 private HashMap<String, View> mRecycledViews; 160 private String mUpdateKey; 161 162 public StatusAdaptor(Context context) { 163 mContext = context; 164 mKeyToId = new HashMap<String, Long>(); 165 mKeys = new HashSet<String>(); 166 mNextId = 0; 167 mRecycledViews = new HashMap<String, View>(); 168 } 169 170 @Override 171 public int getCount() { 172 return mNotifications == null ? 0 : mNotifications.size(); 173 } 174 175 @Override 176 public Object getItem(int position) { 177 return mNotifications.get(position); 178 } 179 180 @Override 181 public boolean hasStableIds() { 182 return true; 183 } 184 185 @Override 186 public long getItemId(int position) { 187 final StatusBarNotification sbn = mNotifications.get(position); 188 final String key = sbn.getKey(); 189 if (!mKeyToId.containsKey(key)) { 190 mKeyToId.put(key, mNextId); 191 mNextId ++; 192 } 193 return mKeyToId.get(key); 194 } 195 196 @Override 197 public View getView(int position, View view, ViewGroup list) { 198 if (view == null) { 199 view = View.inflate(mContext, R.layout.item, null); 200 } 201 FrameLayout container = (FrameLayout) view.findViewById(R.id.remote_view); 202 View dismiss = view.findViewById(R.id.dismiss); 203 StatusBarNotification sbn = mNotifications.get(position); 204 View child; 205 if (container.getTag() instanceof StatusBarNotification && 206 container.getChildCount() > 0) { 207 // recycle the view 208 StatusBarNotification old = (StatusBarNotification) container.getTag(); 209 if (sbn.getKey().equals(mUpdateKey)) { 210 //this view is out of date, discard it 211 mUpdateKey = null; 212 } else { 213 View content = container.getChildAt(0); 214 container.removeView(content); 215 mRecycledViews.put(old.getKey(), content); 216 } 217 } 218 child = mRecycledViews.get(sbn.getKey()); 219 if (child == null) { 220 child = sbn.getNotification().contentView.apply(mContext, null); 221 } 222 container.setTag(sbn); 223 container.removeAllViews(); 224 container.addView(child); 225 dismiss.setVisibility(sbn.isClearable() ? View.VISIBLE : View.GONE); 226 dismiss.setTag(sbn); 227 return view; 228 } 229 230 public void update(String key) { 231 if (mNotifications != null) { 232 synchronized (mNotifications) { 233 mKeys.clear(); 234 for (int i = 0; i < mNotifications.size(); i++) { 235 mKeys.add(mNotifications.get(i).getKey()); 236 } 237 mKeyToId.keySet().retainAll(mKeys); 238 } 239 if (key == null) { 240 mRecycledViews.clear(); 241 } else { 242 mUpdateKey = key; 243 mRecycledViews.remove(key); 244 } 245 Log.d(TAG, "notifyDataSetChanged"); 246 notifyDataSetChanged(); 247 } else { 248 Log.d(TAG, "missed and update"); 249 } 250 } 251 252 public boolean requiresInitialization() { 253 return mNotifications == null; 254 } 255 256 public void init(List<StatusBarNotification> notifications) { 257 mNotifications = notifications; 258 } 259 } 260} 261