blob: 2d36189132a853f040c191bdc890b5bbed98123a [file] [log] [blame]
/*
* Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.localepicker;
import android.content.Context;
import android.os.SystemClock;
import android.os.SystemProperties;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
import java.util.Calendar;
import java.util.Set;
/**
* A controller that evaluates whether the notification can be triggered and update the
* SharedPreference.
*/
public class NotificationController {
private static final String TAG = NotificationController.class.getSimpleName();
private static final int DISMISS_COUNT_THRESHOLD = 2;
private static final int NOTIFICATION_COUNT_THRESHOLD = 2;
private static final int MULTIPLE_BASE = 2;
// seven days: 7 * 24 * 60
private static final int MIN_DURATION_BETWEEN_NOTIFICATIONS_MIN = 10080;
private static final String PROPERTY_MIN_DURATION =
"android.localenotification.duration.threshold";
private static NotificationController sInstance = null;
private final LocaleNotificationDataManager mDataManager;
/**
* Get {@link NotificationController} instance.
*
* @param context The context
* @return {@link NotificationController} instance
*/
public static synchronized NotificationController getInstance(@NonNull Context context) {
if (sInstance == null) {
sInstance = new NotificationController(context);
}
return sInstance;
}
private NotificationController(Context context) {
mDataManager = new LocaleNotificationDataManager(context);
}
@VisibleForTesting
LocaleNotificationDataManager getDataManager() {
return mDataManager;
}
/**
* Increment the dismissCount of the notification.
*
* @param locale A locale used to query the {@link NotificationInfo}
*/
public void incrementDismissCount(@NonNull String locale) {
NotificationInfo currentInfo = mDataManager.getNotificationInfo(locale);
NotificationInfo newInfo = new NotificationInfo(currentInfo.getUidCollection(),
currentInfo.getNotificationCount(),
currentInfo.getDismissCount() + 1,
currentInfo.getLastNotificationTimeMs(),
currentInfo.getNotificationId());
mDataManager.putNotificationInfo(locale, newInfo);
}
/**
* Whether the notification can be triggered or not.
*
* @param uid The application's uid.
* @param locale The application's locale which the user updated to.
* @return true if the notification needs to be triggered. Otherwise, false.
*/
public boolean shouldTriggerNotification(int uid, @NonNull String locale) {
if (LocaleUtils.isInSystemLocale(locale)) {
return false;
} else {
// Add the uid into the locale's uid list and update the notification count if the
// notification can be triggered.
return updateLocaleNotificationInfo(uid, locale);
}
}
/**
* Get the notification id
*
* @param locale The locale which the application sets to
* @return the notification id
*/
public int getNotificationId(@NonNull String locale) {
NotificationInfo info = mDataManager.getNotificationInfo(locale);
return (info != null) ? info.getNotificationId() : -1;
}
private boolean updateLocaleNotificationInfo(int uid, String locale) {
NotificationInfo info = mDataManager.getNotificationInfo(locale);
if (info == null) {
// Create an empty record with the uid and update the SharedPreference.
NotificationInfo emptyInfo = new NotificationInfo(Set.of(uid), 0, 0, 0, 0);
mDataManager.putNotificationInfo(locale, emptyInfo);
return false;
}
Set uidCollection = info.getUidCollection();
if (uidCollection.contains(uid)) {
return false;
}
NotificationInfo newInfo =
createNotificationInfoWithNewUidAndCount(uidCollection, uid, info);
mDataManager.putNotificationInfo(locale, newInfo);
return newInfo.getNotificationCount() > info.getNotificationCount();
}
private NotificationInfo createNotificationInfoWithNewUidAndCount(
Set<Integer> uidSet, int uid, NotificationInfo info) {
int dismissCount = info.getDismissCount();
int notificationCount = info.getNotificationCount();
long lastNotificationTime = info.getLastNotificationTimeMs();
int notificationId = info.getNotificationId();
// Add the uid into the locale's uid list
uidSet.add(uid);
if (dismissCount < DISMISS_COUNT_THRESHOLD
&& notificationCount < NOTIFICATION_COUNT_THRESHOLD
// Notification should fire on multiples of 2 apps using the locale.
&& uidSet.size() % MULTIPLE_BASE == 0
&& !isNotificationFrequent(lastNotificationTime)) {
// Increment the count because the notification can be triggered.
notificationCount = info.getNotificationCount() + 1;
lastNotificationTime = Calendar.getInstance().getTimeInMillis();
Log.i(TAG, "notificationCount:" + notificationCount);
if (notificationCount == 1) {
notificationId = (int) SystemClock.uptimeMillis();
}
}
return new NotificationInfo(uidSet, notificationCount, dismissCount, lastNotificationTime,
notificationId);
}
/**
* Evaluates if the notification is triggered frequently.
*
* @param lastNotificationTime The timestamp that the last notification was triggered.
* @return true if the duration of the two continuous notifications is smaller than the
* threshold.
* Otherwise, false.
*/
private boolean isNotificationFrequent(long lastNotificationTime) {
Calendar time = Calendar.getInstance();
int threshold = SystemProperties.getInt(PROPERTY_MIN_DURATION,
MIN_DURATION_BETWEEN_NOTIFICATIONS_MIN);
time.add(Calendar.MINUTE, threshold * -1);
return time.getTimeInMillis() < lastNotificationTime;
}
}