NotificationStation.java revision 95860491fe19b84c853fa7640be6d3c02dff2975
1/*
2 * Copyright (C) 2012 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 */
16
17package com.android.settings;
18
19import android.app.Activity;
20import android.app.ActivityManager;
21import android.app.INotificationListener;
22import android.app.INotificationManager;
23import android.app.Notification;
24import android.content.BroadcastReceiver;
25import android.content.Context;
26import android.content.Intent;
27import android.content.IntentFilter;
28import android.content.pm.ApplicationInfo;
29import android.content.pm.PackageManager;
30import android.content.res.Resources;
31import android.graphics.drawable.Drawable;
32import android.net.Uri;
33import android.os.Bundle;
34import android.os.Handler;
35import android.os.RemoteException;
36import android.os.ServiceManager;
37import android.os.UserHandle;
38import android.util.Log;
39import android.view.LayoutInflater;
40import android.view.View;
41import android.view.View.OnClickListener;
42import android.view.ViewGroup;
43import android.widget.ArrayAdapter;
44import android.widget.DateTimeView;
45import android.widget.ImageView;
46import android.widget.ListView;
47import android.widget.TextView;
48import com.android.internal.statusbar.StatusBarNotification;
49
50import java.util.ArrayList;
51import java.util.Comparator;
52import java.util.List;
53
54public class NotificationStation extends SettingsPreferenceFragment {
55    private static final String TAG = NotificationStation.class.getSimpleName();
56    static final boolean DEBUG = true;
57    private static final String PACKAGE_SCHEME = "package";
58    private static final boolean SHOW_HISTORICAL_NOTIFICATIONS = true;
59
60    private final PackageReceiver mPackageReceiver = new PackageReceiver();
61    private PackageManager mPm;
62    private INotificationManager mNoMan;
63
64    private Runnable mRefreshListRunnable = new Runnable() {
65        @Override
66        public void run() {
67            refreshList();
68        }
69    };
70
71    private INotificationListener.Stub mListener = new INotificationListener.Stub() {
72        @Override
73        public void onNotificationPosted(StatusBarNotification notification) throws RemoteException {
74            Log.v(TAG, "onNotificationPosted: " + notification);
75            final Handler h = getListView().getHandler();
76            h.removeCallbacks(mRefreshListRunnable);
77            h.postDelayed(mRefreshListRunnable, 100);
78        }
79
80        @Override
81        public void onNotificationRemoved(StatusBarNotification notification) throws RemoteException {
82            final Handler h = getListView().getHandler();
83            h.removeCallbacks(mRefreshListRunnable);
84            h.postDelayed(mRefreshListRunnable, 100);
85        }
86    };
87
88    private NotificationHistoryAdapter mAdapter;
89    private Context mContext;
90
91    private final Comparator<HistoricalNotificationInfo> mNotificationSorter
92            = new Comparator<HistoricalNotificationInfo>() {
93                @Override
94                public int compare(HistoricalNotificationInfo lhs,
95                                   HistoricalNotificationInfo rhs) {
96                    return (int)(rhs.timestamp - lhs.timestamp);
97                }
98            };
99
100    @Override
101    public void onAttach(Activity activity) {
102        logd("onAttach(%s)", activity.getClass().getSimpleName());
103        super.onAttach(activity);
104        mContext = activity;
105        mPm = mContext.getPackageManager();
106        mNoMan = INotificationManager.Stub.asInterface(
107                ServiceManager.getService(Context.NOTIFICATION_SERVICE));
108        try {
109            mNoMan.registerListener(mListener,
110                    mContext.getPackageName(),
111                    ActivityManager.getCurrentUser());
112        } catch (RemoteException e) {
113            // well, that didn't work out
114        }
115    }
116
117    @Override
118    public void onCreate(Bundle icicle) {
119        logd("onCreate(%s)", icicle);
120        super.onCreate(icicle);
121        Activity activity = getActivity();
122    }
123
124    @Override
125    public void onDestroyView() {
126        super.onDestroyView();
127    }
128
129    @Override
130    public void onActivityCreated(Bundle savedInstanceState) {
131        logd("onActivityCreated(%s)", savedInstanceState);
132        super.onActivityCreated(savedInstanceState);
133
134        ListView listView = getListView();
135
136//        TextView emptyView = (TextView) getView().findViewById(android.R.id.empty);
137//        emptyView.setText(R.string.screensaver_settings_disabled_prompt);
138//        listView.setEmptyView(emptyView);
139
140        mAdapter = new NotificationHistoryAdapter(mContext);
141        listView.setAdapter(mAdapter);
142    }
143
144    @Override
145    public void onPause() {
146        logd("onPause()");
147        super.onPause();
148        mContext.unregisterReceiver(mPackageReceiver);
149    }
150
151    @Override
152    public void onResume() {
153        logd("onResume()");
154        super.onResume();
155        refreshList();
156
157        // listen for package changes
158        IntentFilter filter = new IntentFilter();
159        filter.addAction(Intent.ACTION_PACKAGE_ADDED);
160        filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
161        filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
162        filter.addAction(Intent.ACTION_PACKAGE_REPLACED);
163        filter.addDataScheme(PACKAGE_SCHEME);
164        mContext.registerReceiver(mPackageReceiver , filter);
165    }
166
167    private void refreshList() {
168        List<HistoricalNotificationInfo> infos = loadNotifications();
169        if (infos != null) {
170            logd("adding %d infos", infos.size());
171            mAdapter.clear();
172            mAdapter.addAll(infos);
173            mAdapter.sort(mNotificationSorter);
174        }
175    }
176
177    private static void logd(String msg, Object... args) {
178        if (DEBUG)
179            Log.d(TAG, args == null || args.length == 0 ? msg : String.format(msg, args));
180    }
181
182    private static class HistoricalNotificationInfo {
183        public String pkg;
184        public Drawable pkgicon;
185        public CharSequence pkgname;
186        public Drawable icon;
187        public CharSequence title;
188        public int priority;
189        public int user;
190        public long timestamp;
191        public boolean active;
192    }
193
194    private List<HistoricalNotificationInfo> loadNotifications() {
195        final int currentUserId = ActivityManager.getCurrentUser();
196        try {
197            StatusBarNotification[] active = mNoMan.getActiveNotifications(mContext.getPackageName());
198            StatusBarNotification[] dismissed = mNoMan.getHistoricalNotifications(mContext.getPackageName(), 50);
199
200            List<HistoricalNotificationInfo> list
201                    = new ArrayList<HistoricalNotificationInfo>(active.length + dismissed.length);
202
203            for (StatusBarNotification[] resultset
204                    : new StatusBarNotification[][] { active, dismissed }) {
205                for (StatusBarNotification sbn : resultset) {
206                    final HistoricalNotificationInfo info = new HistoricalNotificationInfo();
207                    info.pkg = sbn.pkg;
208                    info.user = sbn.getUserId();
209                    info.icon = loadIconDrawable(info.pkg, info.user, sbn.notification.icon);
210                    info.pkgicon = loadPackageIconDrawable(info.pkg, info.user);
211                    info.pkgname = loadPackageName(info.pkg);
212                    if (sbn.notification.extras != null) {
213                        info.title = sbn.notification.extras.getString(Notification.EXTRA_TITLE);
214                        if (info.title == null || "".equals(info.title)) {
215                            info.title = sbn.notification.extras.getString(Notification.EXTRA_TEXT);
216                        }
217                    }
218                    if (info.title == null || "".equals(info.title)) {
219                        info.title = sbn.notification.tickerText;
220                    }
221                    // still nothing? come on, give us something!
222                    if (info.title == null || "".equals(info.title)) {
223                        info.title = info.pkgname;
224                    }
225                    info.timestamp = sbn.postTime;
226                    info.priority = sbn.notification.priority;
227                    logd("   [%d] %s: %s", info.timestamp, info.pkg, info.title);
228
229                    info.active = (resultset == active);
230
231                    if (info.user == UserHandle.USER_ALL
232                            || info.user == currentUserId) {
233                        list.add(info);
234                    }
235                }
236            }
237
238            return list;
239        } catch (RemoteException e) {
240            e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
241        }
242        return null;
243    }
244
245    private Resources getResourcesForUserPackage(String pkg, int userId) {
246        Resources r = null;
247
248        if (pkg != null) {
249            try {
250                if (userId == UserHandle.USER_ALL) {
251                    userId = UserHandle.USER_OWNER;
252                }
253                r = mPm.getResourcesForApplicationAsUser(pkg, userId);
254            } catch (PackageManager.NameNotFoundException ex) {
255                Log.e(TAG, "Icon package not found: " + pkg);
256                return null;
257            }
258        } else {
259            r = mContext.getResources();
260        }
261        return r;
262    }
263
264    private Drawable loadPackageIconDrawable(String pkg, int userId) {
265        Drawable icon = null;
266        try {
267            icon = mPm.getApplicationIcon(pkg);
268        } catch (PackageManager.NameNotFoundException e) {
269        }
270
271        return icon;
272    }
273
274    private CharSequence loadPackageName(String pkg) {
275        try {
276            ApplicationInfo info = mPm.getApplicationInfo(pkg,
277                    PackageManager.GET_UNINSTALLED_PACKAGES);
278            if (info != null) return mPm.getApplicationLabel(info);
279        } catch (PackageManager.NameNotFoundException e) {
280        }
281        return pkg;
282    }
283
284    private Drawable loadIconDrawable(String pkg, int userId, int resId) {
285        Resources r = getResourcesForUserPackage(pkg, userId);
286
287        if (resId == 0) {
288            return null;
289        }
290
291        try {
292            return r.getDrawable(resId);
293        } catch (RuntimeException e) {
294            Log.w(TAG, "Icon not found in "
295                    + (pkg != null ? resId : "<system>")
296                    + ": " + Integer.toHexString(resId));
297        }
298
299        return null;
300    }
301
302    private class NotificationHistoryAdapter extends ArrayAdapter<HistoricalNotificationInfo> {
303        private final LayoutInflater mInflater;
304
305        public NotificationHistoryAdapter(Context context) {
306            super(context, 0);
307            mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
308        }
309
310        @Override
311        public View getView(int position, View convertView, ViewGroup parent) {
312            final HistoricalNotificationInfo info = getItem(position);
313            logd("getView(%s/%s)", info.pkg, info.title);
314            final View row = convertView != null ? convertView : createRow(parent);
315            row.setTag(info);
316
317            // bind icon
318            if (info.icon != null) {
319                ((ImageView) row.findViewById(android.R.id.icon)).setImageDrawable(info.icon);
320            }
321            if (info.pkgicon != null) {
322                ((ImageView) row.findViewById(R.id.pkgicon)).setImageDrawable(info.pkgicon);
323            }
324
325            ((DateTimeView) row.findViewById(R.id.timestamp)).setTime(info.timestamp);
326
327            // bind caption
328            ((TextView) row.findViewById(android.R.id.title)).setText(info.title);
329
330            // app name
331            ((TextView) row.findViewById(R.id.pkgname)).setText(info.pkgname);
332
333            // extra goodies -- not implemented yet
334//            ((TextView) row.findViewById(R.id.extra)).setText(
335//              ...
336//            );
337            row.findViewById(R.id.extra).setVisibility(View.GONE);
338
339            row.setAlpha(info.active ? 1.0f : 0.5f);
340
341            // set up click handler
342            row.setOnClickListener(new OnClickListener(){
343                @Override
344                public void onClick(View v) {
345                    v.setPressed(true);
346                    startApplicationDetailsActivity(info.pkg);
347                }});
348
349//            // bind radio button
350//            RadioButton radioButton = (RadioButton) row.findViewById(android.R.id.button1);
351//            radioButton.setChecked(dreamInfo.isActive);
352//            radioButton.setOnTouchListener(new OnTouchListener() {
353//                @Override
354//                public boolean onTouch(View v, MotionEvent event) {
355//                    row.onTouchEvent(event);
356//                    return false;
357//                }});
358
359            // bind settings button + divider
360//            boolean showSettings = info.
361//                    settingsComponentName != null;
362//            View settingsDivider = row.findViewById(R.id.divider);
363//            settingsDivider.setVisibility(false ? View.VISIBLE : View.INVISIBLE);
364//
365//            ImageView settingsButton = (ImageView) row.findViewById(android.R.id.button2);
366//            settingsButton.setVisibility(false ? View.VISIBLE : View.INVISIBLE);
367//            settingsButton.setAlpha(info.isActive ? 1f : Utils.DISABLED_ALPHA);
368//            settingsButton.setEnabled(info.isActive);
369//            settingsButton.setOnClickListener(new OnClickListener(){
370//                @Override
371//                public void onClick(View v) {
372//                    mBackend.launchSettings((DreamInfo) row.getTag());
373//                }});
374
375            return row;
376        }
377
378        private View createRow(ViewGroup parent) {
379            final View row =  mInflater.inflate(R.layout.notification_log_row, parent, false);
380            return row;
381        }
382
383    }
384
385    private void startApplicationDetailsActivity(String packageName) {
386        Intent intent = new Intent(android.provider.Settings.ACTION_APPLICATION_DETAILS_SETTINGS,
387                Uri.fromParts("package", packageName, null));
388        intent.setComponent(intent.resolveActivity(mPm));
389        startActivity(intent);
390    }
391
392    private class PackageReceiver extends BroadcastReceiver {
393        @Override
394        public void onReceive(Context context, Intent intent) {
395            logd("PackageReceiver.onReceive");
396            //refreshList();
397        }
398    }
399}
400