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