• 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.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 
AppStateNotificationBridge(Context context, ApplicationsState appState, Callback callback, IUsageStatsManager usageStatsManager, UserManager userManager, NotificationBackend backend)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
loadAllExtraInfo()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
updateExtraInfo(AppEntry entry, String pkg, int uid)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 
getSummary(Context context, NotificationsSentState state, boolean sortByRecency)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 
addBlockStatus(AppEntry entry, NotificationsSentState stats)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 
calculateAvgSentCounts(NotificationsSentState stats)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 
getAggregatedUsageEvents()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 
getAggregatedUsageEvents(int userId, String pkg)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 
getNotificationsSentState(AppEntry entry)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 
getKey(int userId, String pkg)207     protected static String getKey(int userId, String pkg) {
208         return userId + "|" + pkg;
209     }
210 
getSwitchOnClickListener(final AppEntry entry)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 
enableSwitch(AppEntry entry)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 
checkSwitch(AppEntry entry)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