/**
|
* 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.server.notification;
|
|
import android.annotation.NonNull;
|
import android.app.NotificationManager;
|
import android.content.Context;
|
import android.service.notification.RankingHelperProto;
|
import android.util.ArrayMap;
|
import android.util.Slog;
|
import android.util.proto.ProtoOutputStream;
|
|
import java.io.PrintWriter;
|
import java.util.ArrayList;
|
import java.util.Collections;
|
|
public class RankingHelper {
|
private static final String TAG = "RankingHelper";
|
|
private final NotificationSignalExtractor[] mSignalExtractors;
|
private final NotificationComparator mPreliminaryComparator;
|
private final GlobalSortKeyComparator mFinalComparator = new GlobalSortKeyComparator();
|
|
private final ArrayMap<String, NotificationRecord> mProxyByGroupTmp = new ArrayMap<>();
|
|
private final Context mContext;
|
private final RankingHandler mRankingHandler;
|
|
|
public RankingHelper(Context context, RankingHandler rankingHandler, RankingConfig config,
|
ZenModeHelper zenHelper, NotificationUsageStats usageStats, String[] extractorNames) {
|
mContext = context;
|
mRankingHandler = rankingHandler;
|
mPreliminaryComparator = new NotificationComparator(mContext);
|
|
final int N = extractorNames.length;
|
mSignalExtractors = new NotificationSignalExtractor[N];
|
for (int i = 0; i < N; i++) {
|
try {
|
Class<?> extractorClass = mContext.getClassLoader().loadClass(extractorNames[i]);
|
NotificationSignalExtractor extractor =
|
(NotificationSignalExtractor) extractorClass.newInstance();
|
extractor.initialize(mContext, usageStats);
|
extractor.setConfig(config);
|
extractor.setZenHelper(zenHelper);
|
mSignalExtractors[i] = extractor;
|
} catch (ClassNotFoundException e) {
|
Slog.w(TAG, "Couldn't find extractor " + extractorNames[i] + ".", e);
|
} catch (InstantiationException e) {
|
Slog.w(TAG, "Couldn't instantiate extractor " + extractorNames[i] + ".", e);
|
} catch (IllegalAccessException e) {
|
Slog.w(TAG, "Problem accessing extractor " + extractorNames[i] + ".", e);
|
}
|
}
|
}
|
|
@SuppressWarnings("unchecked")
|
public <T extends NotificationSignalExtractor> T findExtractor(Class<T> extractorClass) {
|
final int N = mSignalExtractors.length;
|
for (int i = 0; i < N; i++) {
|
final NotificationSignalExtractor extractor = mSignalExtractors[i];
|
if (extractorClass.equals(extractor.getClass())) {
|
return (T) extractor;
|
}
|
}
|
return null;
|
}
|
|
public void extractSignals(NotificationRecord r) {
|
final int N = mSignalExtractors.length;
|
for (int i = 0; i < N; i++) {
|
NotificationSignalExtractor extractor = mSignalExtractors[i];
|
try {
|
RankingReconsideration recon = extractor.process(r);
|
if (recon != null) {
|
mRankingHandler.requestReconsideration(recon);
|
}
|
} catch (Throwable t) {
|
Slog.w(TAG, "NotificationSignalExtractor failed.", t);
|
}
|
}
|
}
|
|
public void sort(ArrayList<NotificationRecord> notificationList) {
|
final int N = notificationList.size();
|
// clear global sort keys
|
for (int i = N - 1; i >= 0; i--) {
|
notificationList.get(i).setGlobalSortKey(null);
|
}
|
|
// rank each record individually
|
Collections.sort(notificationList, mPreliminaryComparator);
|
|
synchronized (mProxyByGroupTmp) {
|
// record individual ranking result and nominate proxies for each group
|
for (int i = 0; i < N; i++) {
|
final NotificationRecord record = notificationList.get(i);
|
record.setAuthoritativeRank(i);
|
final String groupKey = record.getGroupKey();
|
NotificationRecord existingProxy = mProxyByGroupTmp.get(groupKey);
|
if (existingProxy == null) {
|
mProxyByGroupTmp.put(groupKey, record);
|
}
|
}
|
// assign global sort key:
|
// is_recently_intrusive:group_rank:is_group_summary:group_sort_key:rank
|
for (int i = 0; i < N; i++) {
|
final NotificationRecord record = notificationList.get(i);
|
NotificationRecord groupProxy = mProxyByGroupTmp.get(record.getGroupKey());
|
String groupSortKey = record.getNotification().getSortKey();
|
|
// We need to make sure the developer provided group sort key (gsk) is handled
|
// correctly:
|
// gsk="" < gsk=non-null-string < gsk=null
|
//
|
// We enforce this by using different prefixes for these three cases.
|
String groupSortKeyPortion;
|
if (groupSortKey == null) {
|
groupSortKeyPortion = "nsk";
|
} else if (groupSortKey.equals("")) {
|
groupSortKeyPortion = "esk";
|
} else {
|
groupSortKeyPortion = "gsk=" + groupSortKey;
|
}
|
|
boolean isGroupSummary = record.getNotification().isGroupSummary();
|
record.setGlobalSortKey(
|
String.format("crtcl=0x%04x:intrsv=%c:grnk=0x%04x:gsmry=%c:%s:rnk=0x%04x",
|
record.getCriticality(),
|
record.isRecentlyIntrusive()
|
&& record.getImportance() > NotificationManager.IMPORTANCE_MIN
|
? '0' : '1',
|
groupProxy.getAuthoritativeRank(),
|
isGroupSummary ? '0' : '1',
|
groupSortKeyPortion,
|
record.getAuthoritativeRank()));
|
}
|
mProxyByGroupTmp.clear();
|
}
|
|
// Do a second ranking pass, using group proxies
|
Collections.sort(notificationList, mFinalComparator);
|
}
|
|
public int indexOf(ArrayList<NotificationRecord> notificationList, NotificationRecord target) {
|
return Collections.binarySearch(notificationList, target, mFinalComparator);
|
}
|
|
public void dump(PrintWriter pw, String prefix,
|
@NonNull NotificationManagerService.DumpFilter filter) {
|
final int N = mSignalExtractors.length;
|
pw.print(prefix);
|
pw.print("mSignalExtractors.length = ");
|
pw.println(N);
|
for (int i = 0; i < N; i++) {
|
pw.print(prefix);
|
pw.print(" ");
|
pw.println(mSignalExtractors[i].getClass().getSimpleName());
|
}
|
}
|
|
public void dump(ProtoOutputStream proto,
|
@NonNull NotificationManagerService.DumpFilter filter) {
|
final int N = mSignalExtractors.length;
|
for (int i = 0; i < N; i++) {
|
proto.write(RankingHelperProto.NOTIFICATION_SIGNAL_EXTRACTORS,
|
mSignalExtractors[i].getClass().getSimpleName());
|
}
|
}
|
}
|