RankingHelper.java revision 4a24fd3c7bab53e4e9c127214e7168db0af8800a
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.server.notification;
17
18import android.app.Notification;
19import android.content.Context;
20import android.net.Uri;
21import android.os.Handler;
22import android.os.Message;
23import android.os.UserHandle;
24import android.text.TextUtils;
25import android.util.ArrayMap;
26import android.util.Slog;
27import android.util.SparseIntArray;
28import org.xmlpull.v1.XmlPullParser;
29import org.xmlpull.v1.XmlPullParserException;
30import org.xmlpull.v1.XmlSerializer;
31
32import java.io.IOException;
33import java.io.PrintWriter;
34import java.util.ArrayList;
35import java.util.Collections;
36import java.util.concurrent.TimeUnit;
37
38public class RankingHelper implements RankingConfig {
39    private static final String TAG = "RankingHelper";
40    private static final boolean DEBUG = false;
41
42    private static final int XML_VERSION = 1;
43
44    private static final String TAG_RANKING = "ranking";
45    private static final String TAG_PACKAGE = "package";
46    private static final String ATT_VERSION = "version";
47
48    private static final String ATT_NAME = "name";
49    private static final String ATT_UID = "uid";
50    private static final String ATT_PRIORITY = "priority";
51
52    private final NotificationSignalExtractor[] mSignalExtractors;
53    private final NotificationComparator mPreliminaryComparator = new NotificationComparator();
54    private final NotificationComparator mFinalComparator = new GroupedNotificationComparator();
55
56    // Package name to uid, to priority. Would be better as Table<String, Int, Int>
57    private final ArrayMap<String, SparseIntArray> mPackagePriorities;
58    private final ArrayMap<String, NotificationRecord> mProxyByGroupTmp;
59
60    private final Context mContext;
61    private final Handler mRankingHandler;
62
63    public RankingHelper(Context context, Handler rankingHandler, String[] extractorNames) {
64        mContext = context;
65        mRankingHandler = rankingHandler;
66        mPackagePriorities = new ArrayMap<String, SparseIntArray>();
67
68        final int N = extractorNames.length;
69        mSignalExtractors = new NotificationSignalExtractor[N];
70        for (int i = 0; i < N; i++) {
71            try {
72                Class<?> extractorClass = mContext.getClassLoader().loadClass(extractorNames[i]);
73                NotificationSignalExtractor extractor =
74                        (NotificationSignalExtractor) extractorClass.newInstance();
75                extractor.initialize(mContext);
76                extractor.setConfig(this);
77                mSignalExtractors[i] = extractor;
78            } catch (ClassNotFoundException e) {
79                Slog.w(TAG, "Couldn't find extractor " + extractorNames[i] + ".", e);
80            } catch (InstantiationException e) {
81                Slog.w(TAG, "Couldn't instantiate extractor " + extractorNames[i] + ".", e);
82            } catch (IllegalAccessException e) {
83                Slog.w(TAG, "Problem accessing extractor " + extractorNames[i] + ".", e);
84            }
85        }
86        mProxyByGroupTmp = new ArrayMap<String, NotificationRecord>();
87    }
88
89    public void extractSignals(NotificationRecord r) {
90        final int N = mSignalExtractors.length;
91        for (int i = 0; i < N; i++) {
92            NotificationSignalExtractor extractor = mSignalExtractors[i];
93            try {
94                RankingReconsideration recon = extractor.process(r);
95                if (recon != null) {
96                    Message m = Message.obtain(mRankingHandler,
97                            NotificationManagerService.MESSAGE_RECONSIDER_RANKING, recon);
98                    long delay = recon.getDelay(TimeUnit.MILLISECONDS);
99                    mRankingHandler.sendMessageDelayed(m, delay);
100                }
101            } catch (Throwable t) {
102                Slog.w(TAG, "NotificationSignalExtractor failed.", t);
103            }
104        }
105    }
106
107    public void readXml(XmlPullParser parser) throws XmlPullParserException, IOException {
108        int type = parser.getEventType();
109        if (type != XmlPullParser.START_TAG) return;
110        String tag = parser.getName();
111        if (!TAG_RANKING.equals(tag)) return;
112        mPackagePriorities.clear();
113        final int version = safeInt(parser, ATT_VERSION, XML_VERSION);
114        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) {
115            tag = parser.getName();
116            if (type == XmlPullParser.END_TAG && TAG_RANKING.equals(tag)) {
117                return;
118            }
119            if (type == XmlPullParser.START_TAG) {
120                if (TAG_PACKAGE.equals(tag)) {
121                    int uid = safeInt(parser, ATT_UID, UserHandle.USER_ALL);
122                    int priority = safeInt(parser, ATT_PRIORITY, Notification.PRIORITY_DEFAULT);
123                    String name = parser.getAttributeValue(null, ATT_NAME);
124
125                    if (!TextUtils.isEmpty(name) && priority != Notification.PRIORITY_DEFAULT) {
126                        SparseIntArray priorityByUid = mPackagePriorities.get(name);
127                        if (priorityByUid == null) {
128                            priorityByUid = new SparseIntArray();
129                            mPackagePriorities.put(name, priorityByUid);
130                        }
131                        priorityByUid.put(uid, priority);
132                    }
133                }
134            }
135        }
136        throw new IllegalStateException("Failed to reach END_DOCUMENT");
137    }
138
139    public void writeXml(XmlSerializer out) throws IOException {
140        out.startTag(null, TAG_RANKING);
141        out.attribute(null, ATT_VERSION, Integer.toString(XML_VERSION));
142
143        final int N = mPackagePriorities.size();
144        for (int i = 0; i < N; i ++) {
145            String name = mPackagePriorities.keyAt(i);
146            SparseIntArray priorityByUid = mPackagePriorities.get(name);
147            final int M = priorityByUid.size();
148            for (int j = 0; j < M; j++) {
149                int uid = priorityByUid.keyAt(j);
150                int priority = priorityByUid.get(uid);
151                out.startTag(null, TAG_PACKAGE);
152                out.attribute(null, ATT_NAME, name);
153                out.attribute(null, ATT_UID, Integer.toString(uid));
154                out.attribute(null, ATT_PRIORITY, Integer.toString(priority));
155                out.endTag(null, TAG_PACKAGE);
156            }
157        }
158        out.endTag(null, TAG_RANKING);
159    }
160
161    private void updateConfig() {
162        final int N = mSignalExtractors.length;
163        for (int i = 0; i < N; i++) {
164            mSignalExtractors[i].setConfig(this);
165        }
166        mRankingHandler.sendEmptyMessage(NotificationManagerService.MESSAGE_RANKING_CONFIG_CHANGE);
167    }
168
169    public void sort(ArrayList<NotificationRecord> notificationList) {
170        final int N = notificationList.size();
171        // clear group proxies
172        for (int i = N - 1; i >= 0; i--) {
173            notificationList.get(i).setRankingProxy(null);
174        }
175
176        // rank each record individually
177        Collections.sort(notificationList, mPreliminaryComparator);
178
179        // record inidivdual ranking result and nominate proxies for each group
180        for (int i = N - 1; i >= 0; i--) {
181            final NotificationRecord record = notificationList.get(i);
182            record.setAuthoritativeRank(i);
183            final String groupKey = record.getGroupKey();
184            boolean isGroupSummary = record.getNotification().getGroup() != null
185                    && (record.getNotification().flags & Notification.FLAG_GROUP_SUMMARY) != 0;
186            if (isGroupSummary || mProxyByGroupTmp.get(groupKey) == null) {
187                mProxyByGroupTmp.put(groupKey, record);
188            }
189        }
190        // assign nominated proxies to each notification
191        for (int i = 0; i < N; i++) {
192            final NotificationRecord record = notificationList.get(i);
193            record.setRankingProxy(mProxyByGroupTmp.get(record.getGroupKey()));
194        }
195        // Do a second ranking pass, using group proxies
196        Collections.sort(notificationList, mFinalComparator);
197
198        mProxyByGroupTmp.clear();
199    }
200
201    public int indexOf(ArrayList<NotificationRecord> notificationList, NotificationRecord target) {
202        return Collections.binarySearch(notificationList, target, mFinalComparator);
203    }
204
205    private static int safeInt(XmlPullParser parser, String att, int defValue) {
206        final String val = parser.getAttributeValue(null, att);
207        return tryParseInt(val, defValue);
208    }
209
210    private static int tryParseInt(String value, int defValue) {
211        if (TextUtils.isEmpty(value)) return defValue;
212        try {
213            return Integer.valueOf(value);
214        } catch (NumberFormatException e) {
215            return defValue;
216        }
217    }
218
219    @Override
220    public int getPackagePriority(String packageName, int uid) {
221        int priority = Notification.PRIORITY_DEFAULT;
222        SparseIntArray priorityByUid = mPackagePriorities.get(packageName);
223        if (priorityByUid != null) {
224            priority = priorityByUid.get(uid, Notification.PRIORITY_DEFAULT);
225        }
226        return priority;
227    }
228
229    @Override
230    public void setPackagePriority(String packageName, int uid, int priority) {
231        if (priority == getPackagePriority(packageName, uid)) {
232            return;
233        }
234        SparseIntArray priorityByUid = mPackagePriorities.get(packageName);
235        if (priorityByUid == null) {
236            priorityByUid = new SparseIntArray();
237            mPackagePriorities.put(packageName, priorityByUid);
238        }
239        priorityByUid.put(uid, priority);
240        updateConfig();
241    }
242
243    public void dump(PrintWriter pw, String prefix, NotificationManagerService.DumpFilter filter) {
244        if (filter == null) {
245            final int N = mSignalExtractors.length;
246            pw.print(prefix);
247            pw.print("mSignalExtractors.length = ");
248            pw.println(N);
249            for (int i = 0; i < N; i++) {
250                pw.print(prefix);
251                pw.print("  ");
252                pw.println(mSignalExtractors[i]);
253            }
254        }
255        final int N = mPackagePriorities.size();
256        if (filter == null) {
257            pw.print(prefix);
258            pw.println("package priorities:");
259        }
260        for (int i = 0; i < N; i++) {
261            String name = mPackagePriorities.keyAt(i);
262            if (filter == null || filter.matches(name)) {
263                SparseIntArray priorityByUid = mPackagePriorities.get(name);
264                final int M = priorityByUid.size();
265                for (int j = 0; j < M; j++) {
266                    int uid = priorityByUid.keyAt(j);
267                    int priority = priorityByUid.get(uid);
268                    pw.print(prefix);
269                    pw.print("  ");
270                    pw.print(name);
271                    pw.print(" (");
272                    pw.print(uid);
273                    pw.print(") has priority: ");
274                    pw.println(priority);
275                }
276            }
277        }
278    }
279}
280