blob: fc9e14f0af113f645432105eba05875e2fed21fa [file] [log] [blame]
/*
* 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;
}
}