351 lines
13 KiB
Java
351 lines
13 KiB
Java
/*
|
|
* Copyright (C) 2015 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.settings.applications;
|
|
|
|
import android.app.usage.IUsageStatsManager;
|
|
import android.app.usage.UsageEvents;
|
|
import android.content.Context;
|
|
import android.os.RemoteException;
|
|
import android.os.UserHandle;
|
|
import android.os.UserManager;
|
|
import android.text.format.DateUtils;
|
|
import android.util.ArrayMap;
|
|
import android.util.Log;
|
|
import android.view.View;
|
|
import android.view.ViewGroup;
|
|
import android.widget.Switch;
|
|
|
|
import com.android.settings.R;
|
|
import com.android.settings.Utils;
|
|
import com.android.settings.notification.NotificationBackend;
|
|
import com.android.settingslib.applications.ApplicationsState;
|
|
import com.android.settingslib.applications.ApplicationsState.AppEntry;
|
|
import com.android.settingslib.applications.ApplicationsState.AppFilter;
|
|
import com.android.settingslib.utils.StringUtil;
|
|
|
|
import java.util.ArrayList;
|
|
import java.util.Comparator;
|
|
import java.util.List;
|
|
import java.util.Map;
|
|
|
|
/**
|
|
* Connects the info provided by ApplicationsState and UsageStatsManager.
|
|
* Also provides app filters that can use the notification data.
|
|
*/
|
|
public class AppStateNotificationBridge extends AppStateBaseBridge {
|
|
|
|
private final String TAG = "AppStateNotificationBridge";
|
|
private final boolean DEBUG = true;
|
|
private final Context mContext;
|
|
private IUsageStatsManager mUsageStatsManager;
|
|
protected List<Integer> mUserIds;
|
|
private NotificationBackend mBackend;
|
|
private static final int DAYS_TO_CHECK = 7;
|
|
|
|
public AppStateNotificationBridge(Context context, ApplicationsState appState,
|
|
Callback callback, IUsageStatsManager usageStatsManager,
|
|
UserManager userManager, NotificationBackend backend) {
|
|
super(appState, callback);
|
|
mContext = context;
|
|
mUsageStatsManager = usageStatsManager;
|
|
mBackend = backend;
|
|
mUserIds = new ArrayList<>();
|
|
mUserIds.add(mContext.getUserId());
|
|
int workUserId = Utils.getManagedProfileId(userManager, mContext.getUserId());
|
|
if (workUserId != UserHandle.USER_NULL) {
|
|
mUserIds.add(workUserId);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
protected void loadAllExtraInfo() {
|
|
ArrayList<AppEntry> apps = mAppSession.getAllApps();
|
|
if (apps == null) {
|
|
if (DEBUG) {
|
|
Log.d(TAG, "No apps. No extra info loaded");
|
|
}
|
|
return;
|
|
}
|
|
|
|
final Map<String, NotificationsSentState> map = getAggregatedUsageEvents();
|
|
for (AppEntry entry : apps) {
|
|
NotificationsSentState stats =
|
|
map.get(getKey(UserHandle.getUserId(entry.info.uid), entry.info.packageName));
|
|
calculateAvgSentCounts(stats);
|
|
addBlockStatus(entry, stats);
|
|
entry.extraInfo = stats;
|
|
}
|
|
}
|
|
|
|
@Override
|
|
protected void updateExtraInfo(AppEntry entry, String pkg, int uid) {
|
|
NotificationsSentState stats = getAggregatedUsageEvents(
|
|
UserHandle.getUserId(entry.info.uid), entry.info.packageName);
|
|
calculateAvgSentCounts(stats);
|
|
addBlockStatus(entry, stats);
|
|
entry.extraInfo = stats;
|
|
}
|
|
|
|
public static CharSequence getSummary(Context context, NotificationsSentState state,
|
|
int sortOrder) {
|
|
if (sortOrder == R.id.sort_order_recent_notification) {
|
|
if (state.lastSent == 0) {
|
|
return context.getString(R.string.notifications_sent_never);
|
|
}
|
|
return StringUtil.formatRelativeTime(
|
|
context, System.currentTimeMillis() - state.lastSent, true);
|
|
} else if (sortOrder == R.id.sort_order_frequent_notification) {
|
|
if (state.avgSentWeekly > 0) {
|
|
return context.getString(R.string.notifications_sent_weekly, state.avgSentWeekly);
|
|
}
|
|
return context.getString(R.string.notifications_sent_daily, state.avgSentDaily);
|
|
} else {
|
|
return "";
|
|
}
|
|
}
|
|
|
|
private void addBlockStatus(AppEntry entry, NotificationsSentState stats) {
|
|
if (stats != null) {
|
|
stats.blocked = mBackend.getNotificationsBanned(entry.info.packageName, entry.info.uid);
|
|
stats.systemApp = mBackend.isSystemApp(mContext, entry.info);
|
|
stats.blockable = !stats.systemApp || (stats.systemApp && stats.blocked);
|
|
}
|
|
}
|
|
|
|
private void calculateAvgSentCounts(NotificationsSentState stats) {
|
|
if (stats != null) {
|
|
stats.avgSentDaily = Math.round((float) stats.sentCount / DAYS_TO_CHECK);
|
|
if (stats.sentCount < DAYS_TO_CHECK) {
|
|
stats.avgSentWeekly = stats.sentCount;
|
|
}
|
|
}
|
|
}
|
|
|
|
protected Map<String, NotificationsSentState> getAggregatedUsageEvents() {
|
|
ArrayMap<String, NotificationsSentState> aggregatedStats = new ArrayMap<>();
|
|
|
|
long now = System.currentTimeMillis();
|
|
long startTime = now - (DateUtils.DAY_IN_MILLIS * DAYS_TO_CHECK);
|
|
for (int userId : mUserIds) {
|
|
UsageEvents events = null;
|
|
try {
|
|
events = mUsageStatsManager.queryEventsForUser(
|
|
startTime, now, userId, mContext.getPackageName());
|
|
} catch (RemoteException e) {
|
|
e.printStackTrace();
|
|
}
|
|
if (events != null) {
|
|
UsageEvents.Event event = new UsageEvents.Event();
|
|
while (events.hasNextEvent()) {
|
|
events.getNextEvent(event);
|
|
NotificationsSentState stats =
|
|
aggregatedStats.get(getKey(userId, event.getPackageName()));
|
|
if (stats == null) {
|
|
stats = new NotificationsSentState();
|
|
aggregatedStats.put(getKey(userId, event.getPackageName()), stats);
|
|
}
|
|
|
|
if (event.getEventType() == UsageEvents.Event.NOTIFICATION_INTERRUPTION) {
|
|
if (event.getTimeStamp() > stats.lastSent) {
|
|
stats.lastSent = event.getTimeStamp();
|
|
}
|
|
stats.sentCount++;
|
|
}
|
|
|
|
}
|
|
}
|
|
}
|
|
return aggregatedStats;
|
|
}
|
|
|
|
protected NotificationsSentState getAggregatedUsageEvents(int userId, String pkg) {
|
|
NotificationsSentState stats = null;
|
|
|
|
long now = System.currentTimeMillis();
|
|
long startTime = now - (DateUtils.DAY_IN_MILLIS * DAYS_TO_CHECK);
|
|
UsageEvents events = null;
|
|
try {
|
|
events = mUsageStatsManager.queryEventsForPackageForUser(
|
|
startTime, now, userId, pkg, mContext.getPackageName());
|
|
} catch (RemoteException e) {
|
|
e.printStackTrace();
|
|
}
|
|
if (events != null) {
|
|
UsageEvents.Event event = new UsageEvents.Event();
|
|
while (events.hasNextEvent()) {
|
|
events.getNextEvent(event);
|
|
|
|
if (event.getEventType() == UsageEvents.Event.NOTIFICATION_INTERRUPTION) {
|
|
if (stats == null) {
|
|
stats = new NotificationsSentState();
|
|
}
|
|
if (event.getTimeStamp() > stats.lastSent) {
|
|
stats.lastSent = event.getTimeStamp();
|
|
}
|
|
stats.sentCount++;
|
|
}
|
|
|
|
}
|
|
}
|
|
return stats;
|
|
}
|
|
|
|
private static NotificationsSentState getNotificationsSentState(AppEntry entry) {
|
|
if (entry == null || entry.extraInfo == null) {
|
|
return null;
|
|
}
|
|
if (entry.extraInfo instanceof NotificationsSentState) {
|
|
return (NotificationsSentState) entry.extraInfo;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
protected static String getKey(int userId, String pkg) {
|
|
return userId + "|" + pkg;
|
|
}
|
|
|
|
public View.OnClickListener getSwitchOnClickListener(final AppEntry entry) {
|
|
if (entry != null) {
|
|
return v -> {
|
|
ViewGroup view = (ViewGroup) v;
|
|
Switch toggle = view.findViewById(R.id.switchWidget);
|
|
if (toggle != null) {
|
|
if (!toggle.isEnabled()) {
|
|
return;
|
|
}
|
|
toggle.toggle();
|
|
mBackend.setNotificationsEnabledForPackage(
|
|
entry.info.packageName, entry.info.uid, toggle.isChecked());
|
|
NotificationsSentState stats = getNotificationsSentState(entry);
|
|
if (stats != null) {
|
|
stats.blocked = !toggle.isChecked();
|
|
}
|
|
}
|
|
};
|
|
}
|
|
return null;
|
|
}
|
|
|
|
public static final AppFilter FILTER_APP_NOTIFICATION_RECENCY = new AppFilter() {
|
|
@Override
|
|
public void init() {
|
|
}
|
|
|
|
@Override
|
|
public boolean filterApp(AppEntry info) {
|
|
NotificationsSentState state = getNotificationsSentState(info);
|
|
if (state != null) {
|
|
return state.lastSent != 0;
|
|
}
|
|
return false;
|
|
}
|
|
};
|
|
|
|
public static final AppFilter FILTER_APP_NOTIFICATION_FREQUENCY = new AppFilter() {
|
|
@Override
|
|
public void init() {
|
|
}
|
|
|
|
@Override
|
|
public boolean filterApp(AppEntry info) {
|
|
NotificationsSentState state = getNotificationsSentState(info);
|
|
if (state != null) {
|
|
return state.sentCount != 0;
|
|
}
|
|
return false;
|
|
}
|
|
};
|
|
|
|
public static final AppFilter FILTER_APP_NOTIFICATION_BLOCKED = new AppFilter() {
|
|
@Override
|
|
public void init() {
|
|
}
|
|
|
|
@Override
|
|
public boolean filterApp(AppEntry info) {
|
|
NotificationsSentState state = getNotificationsSentState(info);
|
|
if (state != null) {
|
|
return state.blocked;
|
|
}
|
|
return false;
|
|
}
|
|
};
|
|
|
|
public static final Comparator<AppEntry> RECENT_NOTIFICATION_COMPARATOR
|
|
= new Comparator<AppEntry>() {
|
|
@Override
|
|
public int compare(AppEntry object1, AppEntry object2) {
|
|
NotificationsSentState state1 = getNotificationsSentState(object1);
|
|
NotificationsSentState state2 = getNotificationsSentState(object2);
|
|
if (state1 == null && state2 != null) return -1;
|
|
if (state1 != null && state2 == null) return 1;
|
|
if (state1 != null && state2 != null) {
|
|
if (state1.lastSent < state2.lastSent) return 1;
|
|
if (state1.lastSent > state2.lastSent) return -1;
|
|
}
|
|
return ApplicationsState.ALPHA_COMPARATOR.compare(object1, object2);
|
|
}
|
|
};
|
|
|
|
public static final Comparator<AppEntry> FREQUENCY_NOTIFICATION_COMPARATOR
|
|
= new Comparator<AppEntry>() {
|
|
@Override
|
|
public int compare(AppEntry object1, AppEntry object2) {
|
|
NotificationsSentState state1 = getNotificationsSentState(object1);
|
|
NotificationsSentState state2 = getNotificationsSentState(object2);
|
|
if (state1 == null && state2 != null) return -1;
|
|
if (state1 != null && state2 == null) return 1;
|
|
if (state1 != null && state2 != null) {
|
|
if (state1.sentCount < state2.sentCount) return 1;
|
|
if (state1.sentCount > state2.sentCount) return -1;
|
|
}
|
|
return ApplicationsState.ALPHA_COMPARATOR.compare(object1, object2);
|
|
}
|
|
};
|
|
|
|
public static final boolean enableSwitch(AppEntry entry) {
|
|
NotificationsSentState stats = getNotificationsSentState(entry);
|
|
if (stats == null) {
|
|
return false;
|
|
}
|
|
|
|
return stats.blockable;
|
|
}
|
|
|
|
public static final boolean checkSwitch(AppEntry entry) {
|
|
NotificationsSentState stats = getNotificationsSentState(entry);
|
|
if (stats == null) {
|
|
return false;
|
|
}
|
|
|
|
return !stats.blocked;
|
|
}
|
|
|
|
/**
|
|
* NotificationsSentState contains how often an app sends notifications and how recently it sent
|
|
* one.
|
|
*/
|
|
public static class NotificationsSentState {
|
|
public int avgSentDaily = 0;
|
|
public int avgSentWeekly = 0;
|
|
public long lastSent = 0;
|
|
public int sentCount = 0;
|
|
public boolean blockable;
|
|
public boolean blocked;
|
|
public boolean systemApp;
|
|
}
|
|
}
|