Home | History | Annotate | Download | only in applications
      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