1 /* 2 * Copyright (C) 2015 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 package com.android.settings.applications; 17 18 import android.app.usage.IUsageStatsManager; 19 import android.app.usage.UsageEvents; 20 import android.app.usage.UsageStatsManager; 21 import android.content.Context; 22 import android.os.RemoteException; 23 import android.os.UserHandle; 24 import android.os.UserManager; 25 import android.text.format.DateUtils; 26 import android.util.ArrayMap; 27 import android.view.View; 28 import android.view.ViewGroup; 29 import android.widget.Switch; 30 31 import com.android.settings.R; 32 import com.android.settings.Utils; 33 import com.android.settings.notification.NotificationBackend; 34 import com.android.settingslib.applications.ApplicationsState; 35 import com.android.settingslib.applications.ApplicationsState.AppEntry; 36 import com.android.settingslib.applications.ApplicationsState.AppFilter; 37 import com.android.settingslib.utils.StringUtil; 38 39 import java.util.ArrayList; 40 import java.util.Comparator; 41 import java.util.List; 42 import java.util.Map; 43 44 /** 45 * Connects the info provided by ApplicationsState and UsageStatsManager. 46 * Also provides app filters that can use the notification data. 47 */ 48 public class AppStateNotificationBridge extends AppStateBaseBridge { 49 50 private final Context mContext; 51 private IUsageStatsManager mUsageStatsManager; 52 protected List<Integer> mUserIds; 53 private NotificationBackend mBackend; 54 private static final int DAYS_TO_CHECK = 7; 55 56 public AppStateNotificationBridge(Context context, ApplicationsState appState, 57 Callback callback, IUsageStatsManager usageStatsManager, 58 UserManager userManager, NotificationBackend backend) { 59 super(appState, callback); 60 mContext = context; 61 mUsageStatsManager = usageStatsManager; 62 mBackend = backend; 63 mUserIds = new ArrayList<>(); 64 mUserIds.add(mContext.getUserId()); 65 int workUserId = Utils.getManagedProfileId(userManager, mContext.getUserId()); 66 if (workUserId != UserHandle.USER_NULL) { 67 mUserIds.add(workUserId); 68 } 69 } 70 71 @Override 72 protected void loadAllExtraInfo() { 73 ArrayList<AppEntry> apps = mAppSession.getAllApps(); 74 if (apps == null) return; 75 76 final Map<String, NotificationsSentState> map = getAggregatedUsageEvents(); 77 for (AppEntry entry : apps) { 78 NotificationsSentState stats = 79 map.get(getKey(UserHandle.getUserId(entry.info.uid), entry.info.packageName)); 80 calculateAvgSentCounts(stats); 81 addBlockStatus(entry, stats); 82 entry.extraInfo = stats; 83 } 84 } 85 86 @Override 87 protected void updateExtraInfo(AppEntry entry, String pkg, int uid) { 88 NotificationsSentState stats = getAggregatedUsageEvents( 89 UserHandle.getUserId(entry.info.uid), entry.info.packageName); 90 calculateAvgSentCounts(stats); 91 addBlockStatus(entry, stats); 92 entry.extraInfo = stats; 93 } 94 95 public static CharSequence getSummary(Context context, NotificationsSentState state, 96 boolean sortByRecency) { 97 if (sortByRecency) { 98 if (state.lastSent == 0) { 99 return context.getString(R.string.notifications_sent_never); 100 } 101 return StringUtil.formatRelativeTime( 102 context, System.currentTimeMillis() - state.lastSent, true); 103 } else { 104 if (state.avgSentWeekly > 0) { 105 return context.getString(R.string.notifications_sent_weekly, state.avgSentWeekly); 106 } 107 return context.getString(R.string.notifications_sent_daily, state.avgSentDaily); 108 } 109 } 110 111 private void addBlockStatus(AppEntry entry, NotificationsSentState stats) { 112 if (stats != null) { 113 stats.blocked = mBackend.getNotificationsBanned(entry.info.packageName, entry.info.uid); 114 stats.systemApp = mBackend.isSystemApp(mContext, entry.info); 115 stats.blockable = !stats.systemApp || (stats.systemApp && stats.blocked); 116 } 117 } 118 119 private void calculateAvgSentCounts(NotificationsSentState stats) { 120 if (stats != null) { 121 stats.avgSentDaily = Math.round((float) stats.sentCount / DAYS_TO_CHECK); 122 if (stats.sentCount < DAYS_TO_CHECK) { 123 stats.avgSentWeekly = stats.sentCount; 124 } 125 } 126 } 127 128 protected Map<String, NotificationsSentState> getAggregatedUsageEvents() { 129 ArrayMap<String, NotificationsSentState> aggregatedStats = new ArrayMap<>(); 130 131 long now = System.currentTimeMillis(); 132 long startTime = now - (DateUtils.DAY_IN_MILLIS * DAYS_TO_CHECK); 133 for (int userId : mUserIds) { 134 UsageEvents events = null; 135 try { 136 events = mUsageStatsManager.queryEventsForUser( 137 startTime, now, userId, mContext.getPackageName()); 138 } catch (RemoteException e) { 139 e.printStackTrace(); 140 } 141 if (events != null) { 142 UsageEvents.Event event = new UsageEvents.Event(); 143 while (events.hasNextEvent()) { 144 events.getNextEvent(event); 145 NotificationsSentState stats = 146 aggregatedStats.get(getKey(userId, event.getPackageName())); 147 if (stats == null) { 148 stats = new NotificationsSentState(); 149 aggregatedStats.put(getKey(userId, event.getPackageName()), stats); 150 } 151 152 if (event.getEventType() == UsageEvents.Event.NOTIFICATION_INTERRUPTION) { 153 if (event.getTimeStamp() > stats.lastSent) { 154 stats.lastSent = event.getTimeStamp(); 155 } 156 stats.sentCount++; 157 } 158 159 } 160 } 161 } 162 return aggregatedStats; 163 } 164 165 protected NotificationsSentState getAggregatedUsageEvents(int userId, String pkg) { 166 NotificationsSentState stats = null; 167 168 long now = System.currentTimeMillis(); 169 long startTime = now - (DateUtils.DAY_IN_MILLIS * DAYS_TO_CHECK); 170 UsageEvents events = null; 171 try { 172 events = mUsageStatsManager.queryEventsForPackageForUser( 173 startTime, now, userId, pkg, mContext.getPackageName()); 174 } catch (RemoteException e) { 175 e.printStackTrace(); 176 } 177 if (events != null) { 178 UsageEvents.Event event = new UsageEvents.Event(); 179 while (events.hasNextEvent()) { 180 events.getNextEvent(event); 181 182 if (event.getEventType() == UsageEvents.Event.NOTIFICATION_INTERRUPTION) { 183 if (stats == null) { 184 stats = new NotificationsSentState(); 185 } 186 if (event.getTimeStamp() > stats.lastSent) { 187 stats.lastSent = event.getTimeStamp(); 188 } 189 stats.sentCount++; 190 } 191 192 } 193 } 194 return stats; 195 } 196 197 private static NotificationsSentState getNotificationsSentState(AppEntry entry) { 198 if (entry == null || entry.extraInfo == null) { 199 return null; 200 } 201 if (entry.extraInfo instanceof NotificationsSentState) { 202 return (NotificationsSentState) entry.extraInfo; 203 } 204 return null; 205 } 206 207 protected static String getKey(int userId, String pkg) { 208 return userId + "|" + pkg; 209 } 210 211 public View.OnClickListener getSwitchOnClickListener(final AppEntry entry) { 212 if (entry != null) { 213 return v -> { 214 ViewGroup view = (ViewGroup) v; 215 Switch toggle = view.findViewById(R.id.switchWidget); 216 if (toggle != null) { 217 if (!toggle.isEnabled()) { 218 return; 219 } 220 toggle.toggle(); 221 mBackend.setNotificationsEnabledForPackage( 222 entry.info.packageName, entry.info.uid, toggle.isChecked()); 223 NotificationsSentState stats = getNotificationsSentState(entry); 224 if (stats != null) { 225 stats.blocked = !toggle.isChecked(); 226 } 227 } 228 }; 229 } 230 return null; 231 } 232 233 public static final AppFilter FILTER_APP_NOTIFICATION_RECENCY = new AppFilter() { 234 @Override 235 public void init() { 236 } 237 238 @Override 239 public boolean filterApp(AppEntry info) { 240 NotificationsSentState state = getNotificationsSentState(info); 241 if (state != null) { 242 return state.lastSent != 0; 243 } 244 return false; 245 } 246 }; 247 248 public static final AppFilter FILTER_APP_NOTIFICATION_FREQUENCY = new AppFilter() { 249 @Override 250 public void init() { 251 } 252 253 @Override 254 public boolean filterApp(AppEntry info) { 255 NotificationsSentState state = getNotificationsSentState(info); 256 if (state != null) { 257 return state.sentCount != 0; 258 } 259 return false; 260 } 261 }; 262 263 public static final Comparator<AppEntry> RECENT_NOTIFICATION_COMPARATOR 264 = new Comparator<AppEntry>() { 265 @Override 266 public int compare(AppEntry object1, AppEntry object2) { 267 NotificationsSentState state1 = getNotificationsSentState(object1); 268 NotificationsSentState state2 = getNotificationsSentState(object2); 269 if (state1 == null && state2 != null) return -1; 270 if (state1 != null && state2 == null) return 1; 271 if (state1 != null && state2 != null) { 272 if (state1.lastSent < state2.lastSent) return 1; 273 if (state1.lastSent > state2.lastSent) return -1; 274 } 275 return ApplicationsState.ALPHA_COMPARATOR.compare(object1, object2); 276 } 277 }; 278 279 public static final Comparator<AppEntry> FREQUENCY_NOTIFICATION_COMPARATOR 280 = new Comparator<AppEntry>() { 281 @Override 282 public int compare(AppEntry object1, AppEntry object2) { 283 NotificationsSentState state1 = getNotificationsSentState(object1); 284 NotificationsSentState state2 = getNotificationsSentState(object2); 285 if (state1 == null && state2 != null) return -1; 286 if (state1 != null && state2 == null) return 1; 287 if (state1 != null && state2 != null) { 288 if (state1.sentCount < state2.sentCount) return 1; 289 if (state1.sentCount > state2.sentCount) return -1; 290 } 291 return ApplicationsState.ALPHA_COMPARATOR.compare(object1, object2); 292 } 293 }; 294 295 public static final boolean enableSwitch(AppEntry entry) { 296 NotificationsSentState stats = getNotificationsSentState(entry); 297 if (stats == null) { 298 return false; 299 } 300 301 return stats.blockable; 302 } 303 304 public static final boolean checkSwitch(AppEntry entry) { 305 NotificationsSentState stats = getNotificationsSentState(entry); 306 if (stats == null) { 307 return false; 308 } 309 310 return !stats.blocked; 311 } 312 313 /** 314 * NotificationsSentState contains how often an app sends notifications and how recently it sent 315 * one. 316 */ 317 public static class NotificationsSentState { 318 public int avgSentDaily = 0; 319 public int avgSentWeekly = 0; 320 public long lastSent = 0; 321 public int sentCount = 0; 322 public boolean blockable; 323 public boolean blocked; 324 public boolean systemApp; 325 } 326 } 327