• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2023 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 
17 package com.android.settings.localepicker;
18 
19 import android.content.Context;
20 import android.os.SystemClock;
21 import android.os.SystemProperties;
22 import android.util.Log;
23 
24 import androidx.annotation.NonNull;
25 import androidx.annotation.VisibleForTesting;
26 
27 import java.util.Calendar;
28 import java.util.Set;
29 
30 /**
31  * A controller that evaluates whether the notification can be triggered and update the
32  * SharedPreference.
33  */
34 public class NotificationController {
35     private static final String TAG = NotificationController.class.getSimpleName();
36     private static final int DISMISS_COUNT_THRESHOLD = 2;
37     private static final int NOTIFICATION_COUNT_THRESHOLD = 2;
38     private static final int MULTIPLE_BASE = 2;
39     // seven days: 7 * 24 * 60
40     private static final int MIN_DURATION_BETWEEN_NOTIFICATIONS_MIN = 10080;
41     private static final String PROPERTY_MIN_DURATION =
42             "android.localenotification.duration.threshold";
43 
44     private static NotificationController sInstance = null;
45 
46     private final LocaleNotificationDataManager mDataManager;
47 
48     /**
49      * Get {@link NotificationController} instance.
50      *
51      * @param context The context
52      * @return {@link NotificationController} instance
53      */
getInstance(@onNull Context context)54     public static synchronized NotificationController getInstance(@NonNull Context context) {
55         if (sInstance == null) {
56             sInstance = new NotificationController(context);
57         }
58         return sInstance;
59     }
60 
NotificationController(Context context)61     private NotificationController(Context context) {
62         mDataManager = new LocaleNotificationDataManager(context);
63     }
64 
65     @VisibleForTesting
getDataManager()66     LocaleNotificationDataManager getDataManager() {
67         return mDataManager;
68     }
69 
70     /**
71      * Increment the dismissCount of the notification.
72      *
73      * @param locale A locale used to query the {@link NotificationInfo}
74      */
incrementDismissCount(@onNull String locale)75     public void incrementDismissCount(@NonNull String locale) {
76         NotificationInfo currentInfo = mDataManager.getNotificationInfo(locale);
77         NotificationInfo newInfo = new NotificationInfo(currentInfo.getUidCollection(),
78                 currentInfo.getNotificationCount(),
79                 currentInfo.getDismissCount() + 1,
80                 currentInfo.getLastNotificationTimeMs(),
81                 currentInfo.getNotificationId());
82         mDataManager.putNotificationInfo(locale, newInfo);
83     }
84 
85     /**
86      * Whether the notification can be triggered or not.
87      *
88      * @param uid     The application's uid.
89      * @param locale  The application's locale which the user updated to.
90      * @return true if the notification needs to be triggered. Otherwise, false.
91      */
shouldTriggerNotification(int uid, @NonNull String locale)92     public boolean shouldTriggerNotification(int uid, @NonNull String locale) {
93         if (LocaleUtils.isInSystemLocale(locale)) {
94             return false;
95         } else {
96             // Add the uid into the locale's uid list and update the notification count if the
97             // notification can be triggered.
98             return updateLocaleNotificationInfo(uid, locale);
99         }
100     }
101 
102     /**
103      * Get the notification id
104      *
105      * @param locale The locale which the application sets to
106      * @return the notification id
107      */
getNotificationId(@onNull String locale)108     public int getNotificationId(@NonNull String locale) {
109         NotificationInfo info = mDataManager.getNotificationInfo(locale);
110         return (info != null) ? info.getNotificationId() : -1;
111     }
112 
113     /**
114      * Remove the {@link NotificationInfo} with the corresponding locale
115      *
116      * @param locale The locale which the application sets to
117      */
removeNotificationInfo(@onNull String locale)118     public void removeNotificationInfo(@NonNull String locale) {
119         mDataManager.removeNotificationInfo(locale);
120     }
121 
updateLocaleNotificationInfo(int uid, String locale)122     private boolean updateLocaleNotificationInfo(int uid, String locale) {
123         NotificationInfo info = mDataManager.getNotificationInfo(locale);
124         if (info == null) {
125             // Create an empty record with the uid and update the SharedPreference.
126             NotificationInfo emptyInfo = new NotificationInfo(Set.of(uid), 0, 0, 0, 0);
127             mDataManager.putNotificationInfo(locale, emptyInfo);
128             return false;
129         }
130         Set uidCollection = info.getUidCollection();
131         if (uidCollection.contains(uid)) {
132             return false;
133         }
134 
135         NotificationInfo newInfo =
136                 createNotificationInfoWithNewUidAndCount(uidCollection, uid, info);
137         mDataManager.putNotificationInfo(locale, newInfo);
138         return newInfo.getNotificationCount() > info.getNotificationCount();
139     }
140 
createNotificationInfoWithNewUidAndCount( Set<Integer> uidSet, int uid, NotificationInfo info)141     private NotificationInfo createNotificationInfoWithNewUidAndCount(
142             Set<Integer> uidSet, int uid, NotificationInfo info) {
143         int dismissCount = info.getDismissCount();
144         int notificationCount = info.getNotificationCount();
145         long lastNotificationTime = info.getLastNotificationTimeMs();
146         int notificationId = info.getNotificationId();
147         if (dismissCount < DISMISS_COUNT_THRESHOLD
148                 && notificationCount < NOTIFICATION_COUNT_THRESHOLD) {
149             // Add the uid into the locale's uid list
150             uidSet.add(uid);
151             // Notification should fire on multiples of 2 apps using the locale.
152             if (uidSet.size() % MULTIPLE_BASE == 0
153                     && !isNotificationFrequent(lastNotificationTime)) {
154                 // Increment the count because the notification can be triggered.
155                 notificationCount = info.getNotificationCount() + 1;
156                 lastNotificationTime = Calendar.getInstance().getTimeInMillis();
157                 Log.i(TAG, "notificationCount:" + notificationCount);
158                 if (notificationCount == 1) {
159                     notificationId = (int) SystemClock.uptimeMillis();
160                 }
161             }
162         }
163         return new NotificationInfo(uidSet, notificationCount, dismissCount, lastNotificationTime,
164                 notificationId);
165     }
166 
167     /**
168      * Evaluates if the notification is triggered frequently.
169      *
170      * @param lastNotificationTime The timestamp that the last notification was triggered.
171      * @return true if the duration of the two continuous notifications is smaller than the
172      * threshold.
173      * Otherwise, false.
174      */
isNotificationFrequent(long lastNotificationTime)175     private boolean isNotificationFrequent(long lastNotificationTime) {
176         Calendar time = Calendar.getInstance();
177         int threshold = SystemProperties.getInt(PROPERTY_MIN_DURATION,
178                 MIN_DURATION_BETWEEN_NOTIFICATIONS_MIN);
179         time.add(Calendar.MINUTE, threshold * -1);
180         return time.getTimeInMillis() < lastNotificationTime;
181     }
182 }
183