Merge "Show "review notification permissions" notification" into tm-dev am: a436e42912
Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/17716555 Change-Id: I9fd2b3774d0a6b49277eb8bffc2b634212eb7942 Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
This commit is contained in:
@@ -7056,6 +7056,10 @@
|
||||
android:permission="android.permission.BIND_JOB_SERVICE">
|
||||
</service>
|
||||
|
||||
<service android:name="com.android.server.notification.ReviewNotificationPermissionsJobService"
|
||||
android:permission="android.permission.BIND_JOB_SERVICE">
|
||||
</service>
|
||||
|
||||
<service android:name="com.android.server.pm.PackageManagerShellCommandDataLoader"
|
||||
android:exported="false">
|
||||
<intent-filter>
|
||||
|
||||
@@ -42,4 +42,7 @@ public interface NotificationManagerInternal {
|
||||
|
||||
/** Does the specified package/uid have permission to post notifications? */
|
||||
boolean areNotificationsEnabledForPackage(String pkg, int uid);
|
||||
|
||||
/** Send a notification to the user prompting them to review their notification permissions. */
|
||||
void sendReviewPermissionsNotification();
|
||||
}
|
||||
|
||||
@@ -274,6 +274,7 @@ import com.android.internal.logging.InstanceIdSequence;
|
||||
import com.android.internal.logging.MetricsLogger;
|
||||
import com.android.internal.logging.nano.MetricsProto;
|
||||
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
|
||||
import com.android.internal.messages.nano.SystemMessageProto;
|
||||
import com.android.internal.notification.SystemNotificationChannels;
|
||||
import com.android.internal.os.BackgroundThread;
|
||||
import com.android.internal.os.SomeArgs;
|
||||
@@ -442,6 +443,18 @@ public class NotificationManagerService extends SystemService {
|
||||
|
||||
private static final int NOTIFICATION_INSTANCE_ID_MAX = (1 << 13);
|
||||
|
||||
// States for the review permissions notification
|
||||
static final int REVIEW_NOTIF_STATE_UNKNOWN = -1;
|
||||
static final int REVIEW_NOTIF_STATE_SHOULD_SHOW = 0;
|
||||
static final int REVIEW_NOTIF_STATE_USER_INTERACTED = 1;
|
||||
static final int REVIEW_NOTIF_STATE_DISMISSED = 2;
|
||||
static final int REVIEW_NOTIF_STATE_RESHOWN = 3;
|
||||
|
||||
// Action strings for review permissions notification
|
||||
static final String REVIEW_NOTIF_ACTION_REMIND = "REVIEW_NOTIF_ACTION_REMIND";
|
||||
static final String REVIEW_NOTIF_ACTION_DISMISS = "REVIEW_NOTIF_ACTION_DISMISS";
|
||||
static final String REVIEW_NOTIF_ACTION_CANCELED = "REVIEW_NOTIF_ACTION_CANCELED";
|
||||
|
||||
/**
|
||||
* Apps that post custom toasts in the background will have those blocked. Apps can
|
||||
* still post toasts created with
|
||||
@@ -652,6 +665,9 @@ public class NotificationManagerService extends SystemService {
|
||||
private InstanceIdSequence mNotificationInstanceIdSequence;
|
||||
private Set<String> mMsgPkgsAllowedAsConvos = new HashSet();
|
||||
|
||||
// Broadcast intent receiver for notification permissions review-related intents
|
||||
private ReviewNotificationPermissionsReceiver mReviewNotificationPermissionsReceiver;
|
||||
|
||||
static class Archive {
|
||||
final SparseArray<Boolean> mEnabled;
|
||||
final int mBufferSize;
|
||||
@@ -2416,6 +2432,11 @@ public class NotificationManagerService extends SystemService {
|
||||
|
||||
IntentFilter localeChangedFilter = new IntentFilter(Intent.ACTION_LOCALE_CHANGED);
|
||||
getContext().registerReceiver(mLocaleChangeReceiver, localeChangedFilter);
|
||||
|
||||
mReviewNotificationPermissionsReceiver = new ReviewNotificationPermissionsReceiver();
|
||||
getContext().registerReceiver(mReviewNotificationPermissionsReceiver,
|
||||
ReviewNotificationPermissionsReceiver.getFilter(),
|
||||
Context.RECEIVER_NOT_EXPORTED);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -2709,6 +2730,7 @@ public class NotificationManagerService extends SystemService {
|
||||
mHistoryManager.onBootPhaseAppsCanStart();
|
||||
registerDeviceConfigChange();
|
||||
migrateDefaultNAS();
|
||||
maybeShowInitialReviewPermissionsNotification();
|
||||
} else if (phase == SystemService.PHASE_ACTIVITY_MANAGER_READY) {
|
||||
mSnoozeHelper.scheduleRepostsForPersistedNotifications(System.currentTimeMillis());
|
||||
}
|
||||
@@ -6336,6 +6358,21 @@ public class NotificationManagerService extends SystemService {
|
||||
public boolean areNotificationsEnabledForPackage(String pkg, int uid) {
|
||||
return areNotificationsEnabledForPackageInt(pkg, uid);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendReviewPermissionsNotification() {
|
||||
// This method is meant to be called from the JobService upon running the job for this
|
||||
// notification having been rescheduled; so without checking any other state, it will
|
||||
// send the notification.
|
||||
checkCallerIsSystem();
|
||||
NotificationManager nm = getContext().getSystemService(NotificationManager.class);
|
||||
nm.notify(TAG,
|
||||
SystemMessageProto.SystemMessage.NOTE_REVIEW_NOTIFICATION_PERMISSIONS,
|
||||
createReviewPermissionsNotification());
|
||||
Settings.Global.putInt(getContext().getContentResolver(),
|
||||
Settings.Global.REVIEW_PERMISSIONS_NOTIFICATION_STATE,
|
||||
NotificationManagerService.REVIEW_NOTIF_STATE_RESHOWN);
|
||||
}
|
||||
};
|
||||
|
||||
int getNumNotificationChannelsForPackage(String pkg, int uid, boolean includeDeleted) {
|
||||
@@ -11608,6 +11645,76 @@ public class NotificationManagerService extends SystemService {
|
||||
out.endTag(null, LOCKSCREEN_ALLOW_SECURE_NOTIFICATIONS_TAG);
|
||||
}
|
||||
|
||||
// Creates a notification that informs the user about changes due to the migration to
|
||||
// use permissions for notifications.
|
||||
protected Notification createReviewPermissionsNotification() {
|
||||
int title = R.string.review_notification_settings_title;
|
||||
int content = R.string.review_notification_settings_text;
|
||||
|
||||
// Tapping on the notification leads to the settings screen for managing app notifications,
|
||||
// using the intent reserved for system services to indicate it comes from this notification
|
||||
Intent tapIntent = new Intent(Settings.ACTION_ALL_APPS_NOTIFICATION_SETTINGS_FOR_REVIEW);
|
||||
Intent remindIntent = new Intent(REVIEW_NOTIF_ACTION_REMIND);
|
||||
Intent dismissIntent = new Intent(REVIEW_NOTIF_ACTION_DISMISS);
|
||||
Intent swipeIntent = new Intent(REVIEW_NOTIF_ACTION_CANCELED);
|
||||
|
||||
// Both "remind me" and "dismiss" actions will be actions received by the BroadcastReceiver
|
||||
final Notification.Action remindMe = new Notification.Action.Builder(null,
|
||||
getContext().getResources().getString(
|
||||
R.string.review_notification_settings_remind_me_action),
|
||||
PendingIntent.getBroadcast(
|
||||
getContext(), 0, remindIntent,
|
||||
PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE))
|
||||
.build();
|
||||
final Notification.Action dismiss = new Notification.Action.Builder(null,
|
||||
getContext().getResources().getString(
|
||||
R.string.review_notification_settings_dismiss),
|
||||
PendingIntent.getBroadcast(
|
||||
getContext(), 0, dismissIntent,
|
||||
PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE))
|
||||
.build();
|
||||
|
||||
return new Notification.Builder(getContext(), SystemNotificationChannels.SYSTEM_CHANGES)
|
||||
.setSmallIcon(R.drawable.stat_sys_adb)
|
||||
.setContentTitle(getContext().getResources().getString(title))
|
||||
.setContentText(getContext().getResources().getString(content))
|
||||
.setContentIntent(PendingIntent.getActivity(getContext(), 0, tapIntent,
|
||||
PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE))
|
||||
.setStyle(new Notification.BigTextStyle())
|
||||
.setFlag(Notification.FLAG_NO_CLEAR, true)
|
||||
.setAutoCancel(true)
|
||||
.addAction(remindMe)
|
||||
.addAction(dismiss)
|
||||
.setDeleteIntent(PendingIntent.getBroadcast(getContext(), 0, swipeIntent,
|
||||
PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE))
|
||||
.build();
|
||||
}
|
||||
|
||||
protected void maybeShowInitialReviewPermissionsNotification() {
|
||||
int currentState = Settings.Global.getInt(getContext().getContentResolver(),
|
||||
Settings.Global.REVIEW_PERMISSIONS_NOTIFICATION_STATE,
|
||||
REVIEW_NOTIF_STATE_UNKNOWN);
|
||||
|
||||
// now check the last known state of the notification -- this determination of whether the
|
||||
// user is in the correct target audience occurs elsewhere, and will have written the
|
||||
// REVIEW_NOTIF_STATE_SHOULD_SHOW to indicate it should be shown in the future.
|
||||
//
|
||||
// alternatively, if the user has rescheduled the notification (so it has been shown
|
||||
// again) but not yet interacted with the new notification, then show it again on boot,
|
||||
// as this state indicates that the user had the notification open before rebooting.
|
||||
//
|
||||
// sending the notification here does not record a new state for the notification;
|
||||
// that will be written by parts of the system further down the line if at any point
|
||||
// the user interacts with the notification.
|
||||
if (currentState == REVIEW_NOTIF_STATE_SHOULD_SHOW
|
||||
|| currentState == REVIEW_NOTIF_STATE_RESHOWN) {
|
||||
NotificationManager nm = getContext().getSystemService(NotificationManager.class);
|
||||
nm.notify(TAG,
|
||||
SystemMessageProto.SystemMessage.NOTE_REVIEW_NOTIFICATION_PERMISSIONS,
|
||||
createReviewPermissionsNotification());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows a warning on logcat. Shows the toast only once per package. This is to avoid being too
|
||||
* aggressive and annoying the user.
|
||||
|
||||
@@ -96,6 +96,10 @@ public class PreferencesHelper implements RankingConfig {
|
||||
private final int XML_VERSION;
|
||||
/** What version to check to do the upgrade for bubbles. */
|
||||
private static final int XML_VERSION_BUBBLES_UPGRADE = 1;
|
||||
/** The first xml version with notification permissions enabled. */
|
||||
private static final int XML_VERSION_NOTIF_PERMISSION = 3;
|
||||
/** The first xml version that notifies users to review their notification permissions */
|
||||
private static final int XML_VERSION_REVIEW_PERMISSIONS_NOTIFICATION = 4;
|
||||
@VisibleForTesting
|
||||
static final int UNKNOWN_UID = UserHandle.USER_NULL;
|
||||
private static final String NON_BLOCKABLE_CHANNEL_DELIM = ":";
|
||||
@@ -206,7 +210,7 @@ public class PreferencesHelper implements RankingConfig {
|
||||
mStatsEventBuilderFactory = statsEventBuilderFactory;
|
||||
|
||||
if (mPermissionHelper.isMigrationEnabled()) {
|
||||
XML_VERSION = 3;
|
||||
XML_VERSION = 4;
|
||||
} else {
|
||||
XML_VERSION = 2;
|
||||
}
|
||||
@@ -226,8 +230,16 @@ public class PreferencesHelper implements RankingConfig {
|
||||
|
||||
final int xmlVersion = parser.getAttributeInt(null, ATT_VERSION, -1);
|
||||
boolean upgradeForBubbles = xmlVersion == XML_VERSION_BUBBLES_UPGRADE;
|
||||
boolean migrateToPermission =
|
||||
(xmlVersion < XML_VERSION) && mPermissionHelper.isMigrationEnabled();
|
||||
boolean migrateToPermission = (xmlVersion < XML_VERSION_NOTIF_PERMISSION)
|
||||
&& mPermissionHelper.isMigrationEnabled();
|
||||
if (xmlVersion < XML_VERSION_REVIEW_PERMISSIONS_NOTIFICATION) {
|
||||
// make a note that we should show the notification at some point.
|
||||
// it shouldn't be possible for the user to already have seen it, as the XML version
|
||||
// would be newer then.
|
||||
Settings.Global.putInt(mContext.getContentResolver(),
|
||||
Settings.Global.REVIEW_PERMISSIONS_NOTIFICATION_STATE,
|
||||
NotificationManagerService.REVIEW_NOTIF_STATE_SHOULD_SHOW);
|
||||
}
|
||||
ArrayList<PermissionHelper.PackagePermission> pkgPerms = new ArrayList<>();
|
||||
synchronized (mPackagePreferences) {
|
||||
while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) {
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
/*
|
||||
* Copyright (C) 2022 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.server.notification;
|
||||
|
||||
import android.app.job.JobInfo;
|
||||
import android.app.job.JobParameters;
|
||||
import android.app.job.JobScheduler;
|
||||
import android.app.job.JobService;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
|
||||
import com.android.internal.annotations.VisibleForTesting;
|
||||
import com.android.server.LocalServices;
|
||||
|
||||
/**
|
||||
* JobService implementation for scheduling the notification informing users about
|
||||
* notification permissions updates and taking them to review their existing permissions.
|
||||
* @hide
|
||||
*/
|
||||
public class ReviewNotificationPermissionsJobService extends JobService {
|
||||
public static final String TAG = "ReviewNotificationPermissionsJobService";
|
||||
|
||||
@VisibleForTesting
|
||||
protected static final int JOB_ID = 225373531;
|
||||
|
||||
/**
|
||||
* Schedule a new job that will show a notification the specified amount of time in the future.
|
||||
*/
|
||||
public static void scheduleJob(Context context, long rescheduleTimeMillis) {
|
||||
JobScheduler jobScheduler = context.getSystemService(JobScheduler.class);
|
||||
// if the job already exists for some reason, cancel & reschedule
|
||||
if (jobScheduler.getPendingJob(JOB_ID) != null) {
|
||||
jobScheduler.cancel(JOB_ID);
|
||||
}
|
||||
ComponentName component = new ComponentName(
|
||||
context, ReviewNotificationPermissionsJobService.class);
|
||||
JobInfo newJob = new JobInfo.Builder(JOB_ID, component)
|
||||
.setPersisted(true) // make sure it'll still get rescheduled after reboot
|
||||
.setMinimumLatency(rescheduleTimeMillis) // run after specified amount of time
|
||||
.build();
|
||||
jobScheduler.schedule(newJob);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onStartJob(JobParameters params) {
|
||||
// While jobs typically should be run on different threads, this
|
||||
// job only posts a notification, which is not a long-running operation
|
||||
// as notification posting is asynchronous.
|
||||
NotificationManagerInternal nmi =
|
||||
LocalServices.getService(NotificationManagerInternal.class);
|
||||
nmi.sendReviewPermissionsNotification();
|
||||
|
||||
// once the notification is posted, the job is done, so no need to
|
||||
// keep it alive afterwards
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onStopJob(JobParameters params) {
|
||||
// If we're interrupted for some reason, try again (though this may not
|
||||
// ever happen due to onStartJob not leaving a job running after being
|
||||
// called)
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
/*
|
||||
* Copyright (C) 2022 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.server.notification;
|
||||
|
||||
import android.app.NotificationManager;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.provider.Settings;
|
||||
import android.util.Log;
|
||||
import android.util.Slog;
|
||||
|
||||
import com.android.internal.annotations.VisibleForTesting;
|
||||
import com.android.internal.messages.nano.SystemMessageProto;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.ZoneId;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
|
||||
/**
|
||||
* Broadcast receiver for intents that come from the "review notification permissions" notification,
|
||||
* shown to users who upgrade to T from an earlier OS to inform them of notification setup changes
|
||||
* and invite them to review their notification permissions.
|
||||
*/
|
||||
public class ReviewNotificationPermissionsReceiver extends BroadcastReceiver {
|
||||
public static final String TAG = "ReviewNotifPermissions";
|
||||
static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
|
||||
|
||||
// 7 days in millis, as the amount of time to wait before re-sending the notification
|
||||
private static final long JOB_RESCHEDULE_TIME = 1000 /* millis */ * 60 /* seconds */
|
||||
* 60 /* minutes */ * 24 /* hours */ * 7 /* days */;
|
||||
|
||||
static IntentFilter getFilter() {
|
||||
IntentFilter filter = new IntentFilter();
|
||||
filter.addAction(NotificationManagerService.REVIEW_NOTIF_ACTION_REMIND);
|
||||
filter.addAction(NotificationManagerService.REVIEW_NOTIF_ACTION_DISMISS);
|
||||
filter.addAction(NotificationManagerService.REVIEW_NOTIF_ACTION_CANCELED);
|
||||
return filter;
|
||||
}
|
||||
|
||||
// Cancels the "review notification permissions" notification.
|
||||
@VisibleForTesting
|
||||
protected void cancelNotification(Context context) {
|
||||
NotificationManager nm = context.getSystemService(NotificationManager.class);
|
||||
if (nm != null) {
|
||||
nm.cancel(NotificationManagerService.TAG,
|
||||
SystemMessageProto.SystemMessage.NOTE_REVIEW_NOTIFICATION_PERMISSIONS);
|
||||
} else {
|
||||
Slog.w(TAG, "could not cancel notification: NotificationManager not found");
|
||||
}
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
protected void rescheduleNotification(Context context) {
|
||||
ReviewNotificationPermissionsJobService.scheduleJob(context, JOB_RESCHEDULE_TIME);
|
||||
// log if needed
|
||||
if (DEBUG) {
|
||||
Slog.d(TAG, "Scheduled review permissions notification for on or after: "
|
||||
+ LocalDateTime.now(ZoneId.systemDefault())
|
||||
.plus(JOB_RESCHEDULE_TIME, ChronoUnit.MILLIS));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
String action = intent.getAction();
|
||||
if (action.equals(NotificationManagerService.REVIEW_NOTIF_ACTION_REMIND)) {
|
||||
// Reschedule the notification for 7 days in the future
|
||||
rescheduleNotification(context);
|
||||
|
||||
// note that the user has interacted; no longer needed to show the initial
|
||||
// notification
|
||||
Settings.Global.putInt(context.getContentResolver(),
|
||||
Settings.Global.REVIEW_PERMISSIONS_NOTIFICATION_STATE,
|
||||
NotificationManagerService.REVIEW_NOTIF_STATE_USER_INTERACTED);
|
||||
cancelNotification(context);
|
||||
} else if (action.equals(NotificationManagerService.REVIEW_NOTIF_ACTION_DISMISS)) {
|
||||
// user dismissed; write to settings so we don't show ever again
|
||||
Settings.Global.putInt(context.getContentResolver(),
|
||||
Settings.Global.REVIEW_PERMISSIONS_NOTIFICATION_STATE,
|
||||
NotificationManagerService.REVIEW_NOTIF_STATE_DISMISSED);
|
||||
cancelNotification(context);
|
||||
} else if (action.equals(NotificationManagerService.REVIEW_NOTIF_ACTION_CANCELED)) {
|
||||
// we may get here from the user swiping away the notification,
|
||||
// or from the notification being canceled in any other way.
|
||||
// only in the case that the user hasn't interacted with it in
|
||||
// any other way yet, reschedule
|
||||
int notifState = Settings.Global.getInt(context.getContentResolver(),
|
||||
Settings.Global.REVIEW_PERMISSIONS_NOTIFICATION_STATE,
|
||||
/* default */ NotificationManagerService.REVIEW_NOTIF_STATE_UNKNOWN);
|
||||
if (notifState == NotificationManagerService.REVIEW_NOTIF_STATE_SHOULD_SHOW) {
|
||||
// user hasn't interacted in the past, so reschedule once and then note that the
|
||||
// user *has* interacted now so we don't re-reschedule if they swipe again
|
||||
rescheduleNotification(context);
|
||||
Settings.Global.putInt(context.getContentResolver(),
|
||||
Settings.Global.REVIEW_PERMISSIONS_NOTIFICATION_STATE,
|
||||
NotificationManagerService.REVIEW_NOTIF_STATE_USER_INTERACTED);
|
||||
} else if (notifState == NotificationManagerService.REVIEW_NOTIF_STATE_RESHOWN) {
|
||||
// swiping away on a rescheduled notification; mark as interacted and
|
||||
// don't reschedule again.
|
||||
Settings.Global.putInt(context.getContentResolver(),
|
||||
Settings.Global.REVIEW_PERMISSIONS_NOTIFICATION_STATE,
|
||||
NotificationManagerService.REVIEW_NOTIF_STATE_USER_INTERACTED);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -198,6 +198,7 @@ import com.android.internal.app.IAppOpsService;
|
||||
import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
|
||||
import com.android.internal.logging.InstanceIdSequence;
|
||||
import com.android.internal.logging.InstanceIdSequenceFake;
|
||||
import com.android.internal.messages.nano.SystemMessageProto;
|
||||
import com.android.internal.statusbar.NotificationVisibility;
|
||||
import com.android.server.DeviceIdleInternal;
|
||||
import com.android.server.LocalServices;
|
||||
@@ -303,6 +304,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
|
||||
ActivityManagerInternal mAmi;
|
||||
@Mock
|
||||
private Looper mMainLooper;
|
||||
@Mock
|
||||
private NotificationManager mMockNm;
|
||||
|
||||
@Mock
|
||||
IIntentSender pi1;
|
||||
@@ -405,6 +408,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
|
||||
LocalServices.removeServiceForTest(PermissionPolicyInternal.class);
|
||||
LocalServices.addService(PermissionPolicyInternal.class, mPermissionPolicyInternal);
|
||||
mContext.addMockSystemService(Context.ALARM_SERVICE, mAlarmManager);
|
||||
mContext.addMockSystemService(NotificationManager.class, mMockNm);
|
||||
|
||||
doNothing().when(mContext).sendBroadcastAsUser(any(), any(), any());
|
||||
|
||||
@@ -9294,4 +9298,77 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
|
||||
// the notifyPostedLocked function is called twice.
|
||||
verify(mListeners, times(2)).notifyPostedLocked(any(), any());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMaybeShowReviewPermissionsNotification_unknown() {
|
||||
// Set up various possible states of the settings int and confirm whether or not the
|
||||
// notification is shown as expected
|
||||
|
||||
// Initial state: default/unknown setting, make sure nothing happens
|
||||
Settings.Global.putInt(mContext.getContentResolver(),
|
||||
Settings.Global.REVIEW_PERMISSIONS_NOTIFICATION_STATE,
|
||||
NotificationManagerService.REVIEW_NOTIF_STATE_UNKNOWN);
|
||||
mService.maybeShowInitialReviewPermissionsNotification();
|
||||
verify(mMockNm, never()).notify(anyString(), anyInt(), any(Notification.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMaybeShowReviewPermissionsNotification_shouldShow() {
|
||||
// If state is SHOULD_SHOW, it ... should show
|
||||
Settings.Global.putInt(mContext.getContentResolver(),
|
||||
Settings.Global.REVIEW_PERMISSIONS_NOTIFICATION_STATE,
|
||||
NotificationManagerService.REVIEW_NOTIF_STATE_SHOULD_SHOW);
|
||||
mService.maybeShowInitialReviewPermissionsNotification();
|
||||
verify(mMockNm, times(1)).notify(eq(NotificationManagerService.TAG),
|
||||
eq(SystemMessageProto.SystemMessage.NOTE_REVIEW_NOTIFICATION_PERMISSIONS),
|
||||
any(Notification.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMaybeShowReviewPermissionsNotification_alreadyShown() {
|
||||
// If state is either USER_INTERACTED or DISMISSED, we should not show this on boot
|
||||
Settings.Global.putInt(mContext.getContentResolver(),
|
||||
Settings.Global.REVIEW_PERMISSIONS_NOTIFICATION_STATE,
|
||||
NotificationManagerService.REVIEW_NOTIF_STATE_USER_INTERACTED);
|
||||
mService.maybeShowInitialReviewPermissionsNotification();
|
||||
|
||||
Settings.Global.putInt(mContext.getContentResolver(),
|
||||
Settings.Global.REVIEW_PERMISSIONS_NOTIFICATION_STATE,
|
||||
NotificationManagerService.REVIEW_NOTIF_STATE_DISMISSED);
|
||||
mService.maybeShowInitialReviewPermissionsNotification();
|
||||
|
||||
verify(mMockNm, never()).notify(anyString(), anyInt(), any(Notification.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMaybeShowReviewPermissionsNotification_reshown() {
|
||||
// If we have re-shown the notification and the user did not subsequently interacted with
|
||||
// it, then make sure we show when trying on boot
|
||||
Settings.Global.putInt(mContext.getContentResolver(),
|
||||
Settings.Global.REVIEW_PERMISSIONS_NOTIFICATION_STATE,
|
||||
NotificationManagerService.REVIEW_NOTIF_STATE_RESHOWN);
|
||||
mService.maybeShowInitialReviewPermissionsNotification();
|
||||
verify(mMockNm, times(1)).notify(eq(NotificationManagerService.TAG),
|
||||
eq(SystemMessageProto.SystemMessage.NOTE_REVIEW_NOTIFICATION_PERMISSIONS),
|
||||
any(Notification.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRescheduledReviewPermissionsNotification() {
|
||||
// when rescheduled, the notification goes through the NotificationManagerInternal service
|
||||
// this call doesn't need to know anything about previously scheduled state -- if called,
|
||||
// it should send the notification & write the appropriate int to Settings
|
||||
mInternalService.sendReviewPermissionsNotification();
|
||||
|
||||
// Notification should be sent
|
||||
verify(mMockNm, times(1)).notify(eq(NotificationManagerService.TAG),
|
||||
eq(SystemMessageProto.SystemMessage.NOTE_REVIEW_NOTIFICATION_PERMISSIONS),
|
||||
any(Notification.class));
|
||||
|
||||
// write STATE_RESHOWN to settings
|
||||
assertEquals(NotificationManagerService.REVIEW_NOTIF_STATE_RESHOWN,
|
||||
Settings.Global.getInt(mContext.getContentResolver(),
|
||||
Settings.Global.REVIEW_PERMISSIONS_NOTIFICATION_STATE,
|
||||
NotificationManagerService.REVIEW_NOTIF_STATE_UNKNOWN));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -289,6 +289,11 @@ public class PreferencesHelperTest extends UiServiceTestCase {
|
||||
.setUsage(AudioAttributes.USAGE_NOTIFICATION_RINGTONE)
|
||||
.setFlags(AudioAttributes.FLAG_AUDIBILITY_ENFORCED)
|
||||
.build();
|
||||
|
||||
// make sure that the settings for review notification permissions are unset to begin with
|
||||
Settings.Global.putInt(mContext.getContentResolver(),
|
||||
Settings.Global.REVIEW_PERMISSIONS_NOTIFICATION_STATE,
|
||||
NotificationManagerService.REVIEW_NOTIF_STATE_UNKNOWN);
|
||||
}
|
||||
|
||||
private ByteArrayOutputStream writeXmlAndPurge(
|
||||
@@ -656,6 +661,13 @@ public class PreferencesHelperTest extends UiServiceTestCase {
|
||||
verify(mPermissionHelper).setNotificationPermission(nMr1Expected);
|
||||
verify(mPermissionHelper).setNotificationPermission(oExpected);
|
||||
verify(mPermissionHelper).setNotificationPermission(pExpected);
|
||||
|
||||
// verify that we also write a state for review_permissions_notification to eventually
|
||||
// show a notification
|
||||
assertEquals(NotificationManagerService.REVIEW_NOTIF_STATE_SHOULD_SHOW,
|
||||
Settings.Global.getInt(mContext.getContentResolver(),
|
||||
Settings.Global.REVIEW_PERMISSIONS_NOTIFICATION_STATE,
|
||||
NotificationManagerService.REVIEW_NOTIF_STATE_UNKNOWN));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -738,7 +750,7 @@ public class PreferencesHelperTest extends UiServiceTestCase {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReadXml_newXml_noMigration() throws Exception {
|
||||
public void testReadXml_newXml_noMigration_showPermissionNotification() throws Exception {
|
||||
when(mPermissionHelper.isMigrationEnabled()).thenReturn(true);
|
||||
mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
|
||||
mPermissionHelper, mLogger, mAppOpsManager, mStatsEventBuilderFactory);
|
||||
@@ -786,6 +798,70 @@ public class PreferencesHelperTest extends UiServiceTestCase {
|
||||
compareChannels(idp, mHelper.getNotificationChannel(PKG_P, UID_P, idp.getId(), false));
|
||||
|
||||
verify(mPermissionHelper, never()).setNotificationPermission(any());
|
||||
|
||||
// verify that we do, however, write a state for review_permissions_notification to
|
||||
// eventually show a notification, since this XML version is older than the notification
|
||||
assertEquals(NotificationManagerService.REVIEW_NOTIF_STATE_SHOULD_SHOW,
|
||||
Settings.Global.getInt(mContext.getContentResolver(),
|
||||
Settings.Global.REVIEW_PERMISSIONS_NOTIFICATION_STATE,
|
||||
NotificationManagerService.REVIEW_NOTIF_STATE_UNKNOWN));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReadXml_newXml_noMigration_noPermissionNotification() throws Exception {
|
||||
when(mPermissionHelper.isMigrationEnabled()).thenReturn(true);
|
||||
mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
|
||||
mPermissionHelper, mLogger, mAppOpsManager, mStatsEventBuilderFactory);
|
||||
|
||||
String xml = "<ranking version=\"4\">\n"
|
||||
+ "<package name=\"" + PKG_N_MR1 + "\" show_badge=\"true\">\n"
|
||||
+ "<channel id=\"idn\" name=\"name\" importance=\"2\"/>\n"
|
||||
+ "<channel id=\"miscellaneous\" name=\"Uncategorized\" />\n"
|
||||
+ "</package>\n"
|
||||
+ "<package name=\"" + PKG_O + "\" >\n"
|
||||
+ "<channel id=\"ido\" name=\"name2\" importance=\"2\" show_badge=\"true\"/>\n"
|
||||
+ "</package>\n"
|
||||
+ "<package name=\"" + PKG_P + "\" >\n"
|
||||
+ "<channel id=\"idp\" name=\"name3\" importance=\"4\" locked=\"2\" />\n"
|
||||
+ "</package>\n"
|
||||
+ "</ranking>\n";
|
||||
NotificationChannel idn = new NotificationChannel("idn", "name", IMPORTANCE_LOW);
|
||||
idn.setSound(null, new AudioAttributes.Builder()
|
||||
.setUsage(USAGE_NOTIFICATION)
|
||||
.setContentType(CONTENT_TYPE_SONIFICATION)
|
||||
.setFlags(0)
|
||||
.build());
|
||||
idn.setShowBadge(false);
|
||||
NotificationChannel ido = new NotificationChannel("ido", "name2", IMPORTANCE_LOW);
|
||||
ido.setShowBadge(true);
|
||||
ido.setSound(null, new AudioAttributes.Builder()
|
||||
.setUsage(USAGE_NOTIFICATION)
|
||||
.setContentType(CONTENT_TYPE_SONIFICATION)
|
||||
.setFlags(0)
|
||||
.build());
|
||||
NotificationChannel idp = new NotificationChannel("idp", "name3", IMPORTANCE_HIGH);
|
||||
idp.lockFields(2);
|
||||
idp.setSound(null, new AudioAttributes.Builder()
|
||||
.setUsage(USAGE_NOTIFICATION)
|
||||
.setContentType(CONTENT_TYPE_SONIFICATION)
|
||||
.setFlags(0)
|
||||
.build());
|
||||
|
||||
loadByteArrayXml(xml.getBytes(), true, USER_SYSTEM);
|
||||
|
||||
assertTrue(mHelper.canShowBadge(PKG_N_MR1, UID_N_MR1));
|
||||
|
||||
assertEquals(idn, mHelper.getNotificationChannel(PKG_N_MR1, UID_N_MR1, idn.getId(), false));
|
||||
compareChannels(ido, mHelper.getNotificationChannel(PKG_O, UID_O, ido.getId(), false));
|
||||
compareChannels(idp, mHelper.getNotificationChannel(PKG_P, UID_P, idp.getId(), false));
|
||||
|
||||
verify(mPermissionHelper, never()).setNotificationPermission(any());
|
||||
|
||||
// this XML is new enough, we should not be attempting to show a notification or anything
|
||||
assertEquals(NotificationManagerService.REVIEW_NOTIF_STATE_UNKNOWN,
|
||||
Settings.Global.getInt(mContext.getContentResolver(),
|
||||
Settings.Global.REVIEW_PERMISSIONS_NOTIFICATION_STATE,
|
||||
NotificationManagerService.REVIEW_NOTIF_STATE_UNKNOWN));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -903,7 +979,7 @@ public class PreferencesHelperTest extends UiServiceTestCase {
|
||||
|
||||
ByteArrayOutputStream baos = writeXmlAndPurge(
|
||||
PKG_N_MR1, UID_N_MR1, false, USER_SYSTEM);
|
||||
String expected = "<ranking version=\"3\">\n"
|
||||
String expected = "<ranking version=\"4\">\n"
|
||||
+ "<package name=\"com.example.o\" show_badge=\"true\" "
|
||||
+ "app_user_locked_fields=\"0\" sent_invalid_msg=\"false\" "
|
||||
+ "sent_valid_msg=\"false\" user_demote_msg_app=\"false\" uid=\"1111\">\n"
|
||||
@@ -984,7 +1060,7 @@ public class PreferencesHelperTest extends UiServiceTestCase {
|
||||
|
||||
ByteArrayOutputStream baos = writeXmlAndPurge(
|
||||
PKG_N_MR1, UID_N_MR1, true, USER_SYSTEM);
|
||||
String expected = "<ranking version=\"3\">\n"
|
||||
String expected = "<ranking version=\"4\">\n"
|
||||
// Importance 0 because off in permissionhelper
|
||||
+ "<package name=\"com.example.o\" importance=\"0\" show_badge=\"true\" "
|
||||
+ "app_user_locked_fields=\"0\" sent_invalid_msg=\"false\" "
|
||||
@@ -1067,7 +1143,7 @@ public class PreferencesHelperTest extends UiServiceTestCase {
|
||||
|
||||
ByteArrayOutputStream baos = writeXmlAndPurge(
|
||||
PKG_N_MR1, UID_N_MR1, true, USER_SYSTEM);
|
||||
String expected = "<ranking version=\"3\">\n"
|
||||
String expected = "<ranking version=\"4\">\n"
|
||||
// Importance 0 because off in permissionhelper
|
||||
+ "<package name=\"com.example.o\" importance=\"0\" show_badge=\"true\" "
|
||||
+ "app_user_locked_fields=\"0\" sent_invalid_msg=\"false\" "
|
||||
@@ -1121,7 +1197,7 @@ public class PreferencesHelperTest extends UiServiceTestCase {
|
||||
|
||||
ByteArrayOutputStream baos = writeXmlAndPurge(
|
||||
PKG_N_MR1, UID_N_MR1, true, USER_SYSTEM);
|
||||
String expected = "<ranking version=\"3\">\n"
|
||||
String expected = "<ranking version=\"4\">\n"
|
||||
// Packages that exist solely in permissionhelper
|
||||
+ "<package name=\"" + PKG_P + "\" importance=\"3\" />\n"
|
||||
+ "<package name=\"" + PKG_O + "\" importance=\"0\" />\n"
|
||||
|
||||
@@ -0,0 +1,104 @@
|
||||
/*
|
||||
* Copyright (C) 2022 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.server.notification;
|
||||
|
||||
import static junit.framework.Assert.assertEquals;
|
||||
import static junit.framework.Assert.assertFalse;
|
||||
import static junit.framework.Assert.assertTrue;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.anyInt;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import android.app.job.JobInfo;
|
||||
import android.app.job.JobParameters;
|
||||
import android.app.job.JobScheduler;
|
||||
import android.testing.AndroidTestingRunner;
|
||||
|
||||
import androidx.test.rule.ServiceTestRule;
|
||||
|
||||
import com.android.server.LocalServices;
|
||||
import com.android.server.UiServiceTestCase;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.Captor;
|
||||
import org.mockito.Mock;
|
||||
|
||||
@RunWith(AndroidTestingRunner.class)
|
||||
public class ReviewNotificationPermissionsJobServiceTest extends UiServiceTestCase {
|
||||
private ReviewNotificationPermissionsJobService mJobService;
|
||||
private JobParameters mJobParams = new JobParameters(null,
|
||||
ReviewNotificationPermissionsJobService.JOB_ID, null, null, null,
|
||||
0, false, false, null, null, null);
|
||||
|
||||
@Captor
|
||||
ArgumentCaptor<JobInfo> mJobInfoCaptor;
|
||||
|
||||
@Mock
|
||||
private JobScheduler mMockJobScheduler;
|
||||
|
||||
@Mock
|
||||
private NotificationManagerInternal mMockNotificationManagerInternal;
|
||||
|
||||
@Rule
|
||||
public final ServiceTestRule mServiceRule = new ServiceTestRule();
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
mJobService = new ReviewNotificationPermissionsJobService();
|
||||
mContext.addMockSystemService(JobScheduler.class, mMockJobScheduler);
|
||||
|
||||
// add NotificationManagerInternal to LocalServices
|
||||
LocalServices.removeServiceForTest(NotificationManagerInternal.class);
|
||||
LocalServices.addService(NotificationManagerInternal.class,
|
||||
mMockNotificationManagerInternal);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testScheduleJob() {
|
||||
// if asked, the job doesn't currently exist yet
|
||||
when(mMockJobScheduler.getPendingJob(anyInt())).thenReturn(null);
|
||||
|
||||
final int rescheduleTimeMillis = 350; // arbitrary number
|
||||
|
||||
// attempt to schedule the job
|
||||
ReviewNotificationPermissionsJobService.scheduleJob(mContext, rescheduleTimeMillis);
|
||||
verify(mMockJobScheduler, times(1)).schedule(mJobInfoCaptor.capture());
|
||||
|
||||
// verify various properties of the job that is passed in to the job scheduler
|
||||
JobInfo jobInfo = mJobInfoCaptor.getValue();
|
||||
assertEquals(ReviewNotificationPermissionsJobService.JOB_ID, jobInfo.getId());
|
||||
assertEquals(rescheduleTimeMillis, jobInfo.getMinLatencyMillis());
|
||||
assertTrue(jobInfo.isPersisted()); // should continue after reboot
|
||||
assertFalse(jobInfo.isPeriodic()); // one time
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnStartJob() {
|
||||
// the job need not be persisted after it does its work, so it'll return
|
||||
// false
|
||||
assertFalse(mJobService.onStartJob(mJobParams));
|
||||
|
||||
// verify that starting the job causes the notification to be sent
|
||||
verify(mMockNotificationManagerInternal).sendReviewPermissionsNotification();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,206 @@
|
||||
/*
|
||||
* Copyright (C) 2022 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.server.notification;
|
||||
|
||||
import static junit.framework.Assert.assertEquals;
|
||||
import static junit.framework.Assert.assertFalse;
|
||||
import static junit.framework.Assert.assertTrue;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.provider.Settings;
|
||||
import android.testing.AndroidTestingRunner;
|
||||
|
||||
import androidx.test.filters.SmallTest;
|
||||
|
||||
import com.android.server.UiServiceTestCase;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
@RunWith(AndroidTestingRunner.class)
|
||||
@SmallTest
|
||||
public class ReviewNotificationPermissionsReceiverTest extends UiServiceTestCase {
|
||||
|
||||
// Simple mock class that just overrides the reschedule and cancel behavior so that it's easy
|
||||
// to tell whether the receiver has sent requests to either reschedule or cancel the
|
||||
// notification (or both).
|
||||
private class MockReviewNotificationPermissionsReceiver
|
||||
extends ReviewNotificationPermissionsReceiver {
|
||||
boolean mCanceled = false;
|
||||
boolean mRescheduled = false;
|
||||
|
||||
@Override
|
||||
protected void cancelNotification(Context context) {
|
||||
mCanceled = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void rescheduleNotification(Context context) {
|
||||
mRescheduled = true;
|
||||
}
|
||||
}
|
||||
|
||||
private MockReviewNotificationPermissionsReceiver mReceiver;
|
||||
private Intent mIntent;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
mReceiver = new MockReviewNotificationPermissionsReceiver();
|
||||
mIntent = new Intent(); // actions will be set in test cases
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReceive_remindMeLater_firstTime() {
|
||||
// Test what happens when we receive a "remind me later" intent coming from
|
||||
// a previously-not-interacted notification
|
||||
Settings.Global.putInt(mContext.getContentResolver(),
|
||||
Settings.Global.REVIEW_PERMISSIONS_NOTIFICATION_STATE,
|
||||
NotificationManagerService.REVIEW_NOTIF_STATE_SHOULD_SHOW);
|
||||
|
||||
// set up Intent action
|
||||
mIntent.setAction(NotificationManagerService.REVIEW_NOTIF_ACTION_REMIND);
|
||||
|
||||
// Upon receipt of the intent, the following things should happen:
|
||||
// - notification rescheduled
|
||||
// - notification explicitly canceled
|
||||
// - settings state updated to indicate user has interacted
|
||||
mReceiver.onReceive(mContext, mIntent);
|
||||
assertTrue(mReceiver.mRescheduled);
|
||||
assertTrue(mReceiver.mCanceled);
|
||||
assertEquals(NotificationManagerService.REVIEW_NOTIF_STATE_USER_INTERACTED,
|
||||
Settings.Global.getInt(mContext.getContentResolver(),
|
||||
Settings.Global.REVIEW_PERMISSIONS_NOTIFICATION_STATE,
|
||||
NotificationManagerService.REVIEW_NOTIF_STATE_UNKNOWN));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReceive_remindMeLater_laterTimes() {
|
||||
// Test what happens when we receive a "remind me later" intent coming from
|
||||
// a previously-interacted notification that has been rescheduled
|
||||
Settings.Global.putInt(mContext.getContentResolver(),
|
||||
Settings.Global.REVIEW_PERMISSIONS_NOTIFICATION_STATE,
|
||||
NotificationManagerService.REVIEW_NOTIF_STATE_RESHOWN);
|
||||
|
||||
// set up Intent action
|
||||
mIntent.setAction(NotificationManagerService.REVIEW_NOTIF_ACTION_REMIND);
|
||||
|
||||
// Upon receipt of the intent, the following things should still happen
|
||||
// regardless of the fact that the user has interacted before:
|
||||
// - notification rescheduled
|
||||
// - notification explicitly canceled
|
||||
// - settings state still indicate user has interacted
|
||||
mReceiver.onReceive(mContext, mIntent);
|
||||
assertTrue(mReceiver.mRescheduled);
|
||||
assertTrue(mReceiver.mCanceled);
|
||||
assertEquals(NotificationManagerService.REVIEW_NOTIF_STATE_USER_INTERACTED,
|
||||
Settings.Global.getInt(mContext.getContentResolver(),
|
||||
Settings.Global.REVIEW_PERMISSIONS_NOTIFICATION_STATE,
|
||||
NotificationManagerService.REVIEW_NOTIF_STATE_UNKNOWN));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReceive_dismiss() {
|
||||
// Test that dismissing the notification does *not* reschedule the notification,
|
||||
// does cancel it, and writes that it has been dismissed to settings
|
||||
Settings.Global.putInt(mContext.getContentResolver(),
|
||||
Settings.Global.REVIEW_PERMISSIONS_NOTIFICATION_STATE,
|
||||
NotificationManagerService.REVIEW_NOTIF_STATE_SHOULD_SHOW);
|
||||
|
||||
// set up Intent action
|
||||
mIntent.setAction(NotificationManagerService.REVIEW_NOTIF_ACTION_DISMISS);
|
||||
|
||||
// send intent, watch what happens
|
||||
mReceiver.onReceive(mContext, mIntent);
|
||||
assertFalse(mReceiver.mRescheduled);
|
||||
assertTrue(mReceiver.mCanceled);
|
||||
assertEquals(NotificationManagerService.REVIEW_NOTIF_STATE_DISMISSED,
|
||||
Settings.Global.getInt(mContext.getContentResolver(),
|
||||
Settings.Global.REVIEW_PERMISSIONS_NOTIFICATION_STATE,
|
||||
NotificationManagerService.REVIEW_NOTIF_STATE_UNKNOWN));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReceive_notificationCanceled_firstSwipe() {
|
||||
// Test the basic swipe away case: the first time the user swipes the notification
|
||||
// away, it will not have been interacted with yet, so make sure it's rescheduled
|
||||
Settings.Global.putInt(mContext.getContentResolver(),
|
||||
Settings.Global.REVIEW_PERMISSIONS_NOTIFICATION_STATE,
|
||||
NotificationManagerService.REVIEW_NOTIF_STATE_SHOULD_SHOW);
|
||||
|
||||
// set up Intent action, would be called from notification's delete intent
|
||||
mIntent.setAction(NotificationManagerService.REVIEW_NOTIF_ACTION_CANCELED);
|
||||
|
||||
// send intent, make sure it gets:
|
||||
// - rescheduled
|
||||
// - not explicitly canceled, the notification was already canceled
|
||||
// - noted that it's been interacted with
|
||||
mReceiver.onReceive(mContext, mIntent);
|
||||
assertTrue(mReceiver.mRescheduled);
|
||||
assertFalse(mReceiver.mCanceled);
|
||||
assertEquals(NotificationManagerService.REVIEW_NOTIF_STATE_USER_INTERACTED,
|
||||
Settings.Global.getInt(mContext.getContentResolver(),
|
||||
Settings.Global.REVIEW_PERMISSIONS_NOTIFICATION_STATE,
|
||||
NotificationManagerService.REVIEW_NOTIF_STATE_UNKNOWN));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReceive_notificationCanceled_secondSwipe() {
|
||||
// Test the swipe away case for a rescheduled notification: in this case
|
||||
// it should not be rescheduled anymore
|
||||
Settings.Global.putInt(mContext.getContentResolver(),
|
||||
Settings.Global.REVIEW_PERMISSIONS_NOTIFICATION_STATE,
|
||||
NotificationManagerService.REVIEW_NOTIF_STATE_RESHOWN);
|
||||
|
||||
// set up Intent action, would be called from notification's delete intent
|
||||
mIntent.setAction(NotificationManagerService.REVIEW_NOTIF_ACTION_CANCELED);
|
||||
|
||||
// send intent, make sure it gets:
|
||||
// - not rescheduled on the second+ swipe
|
||||
// - not explicitly canceled, the notification was already canceled
|
||||
// - mark as user interacted
|
||||
mReceiver.onReceive(mContext, mIntent);
|
||||
assertFalse(mReceiver.mRescheduled);
|
||||
assertFalse(mReceiver.mCanceled);
|
||||
assertEquals(NotificationManagerService.REVIEW_NOTIF_STATE_USER_INTERACTED,
|
||||
Settings.Global.getInt(mContext.getContentResolver(),
|
||||
Settings.Global.REVIEW_PERMISSIONS_NOTIFICATION_STATE,
|
||||
NotificationManagerService.REVIEW_NOTIF_STATE_UNKNOWN));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReceive_notificationCanceled_fromDismiss() {
|
||||
// Test that if the notification delete intent is called due to us canceling
|
||||
// the notification from the receiver, we don't do anything extra
|
||||
Settings.Global.putInt(mContext.getContentResolver(),
|
||||
Settings.Global.REVIEW_PERMISSIONS_NOTIFICATION_STATE,
|
||||
NotificationManagerService.REVIEW_NOTIF_STATE_DISMISSED);
|
||||
|
||||
// set up Intent action, would be called from notification's delete intent
|
||||
mIntent.setAction(NotificationManagerService.REVIEW_NOTIF_ACTION_CANCELED);
|
||||
|
||||
// nothing should happen, nothing at all
|
||||
mReceiver.onReceive(mContext, mIntent);
|
||||
assertFalse(mReceiver.mRescheduled);
|
||||
assertFalse(mReceiver.mCanceled);
|
||||
assertEquals(NotificationManagerService.REVIEW_NOTIF_STATE_DISMISSED,
|
||||
Settings.Global.getInt(mContext.getContentResolver(),
|
||||
Settings.Global.REVIEW_PERMISSIONS_NOTIFICATION_STATE,
|
||||
NotificationManagerService.REVIEW_NOTIF_STATE_UNKNOWN));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user