1 /*
2  * Copyright (C) 2022 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.server.notification;
18 
19 import android.app.NotificationManager;
20 import android.content.BroadcastReceiver;
21 import android.content.Context;
22 import android.content.Intent;
23 import android.content.IntentFilter;
24 import android.provider.Settings;
25 import android.util.Log;
26 import android.util.Slog;
27 
28 import com.android.internal.annotations.VisibleForTesting;
29 import com.android.internal.messages.nano.SystemMessageProto;
30 
31 import java.time.LocalDateTime;
32 import java.time.ZoneId;
33 import java.time.temporal.ChronoUnit;
34 
35 /**
36  * Broadcast receiver for intents that come from the "review notification permissions" notification,
37  * shown to users who upgrade to T from an earlier OS to inform them of notification setup changes
38  * and invite them to review their notification permissions.
39  */
40 public class ReviewNotificationPermissionsReceiver extends BroadcastReceiver {
41     public static final String TAG = "ReviewNotifPermissions";
42     static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
43 
44     // 7 days in millis, as the amount of time to wait before re-sending the notification
45     private static final long JOB_RESCHEDULE_TIME = 1000 /* millis */ * 60 /* seconds */
46             * 60 /* minutes */ * 24 /* hours */ * 7 /* days */;
47 
getFilter()48     static IntentFilter getFilter() {
49         IntentFilter filter = new IntentFilter();
50         filter.addAction(NotificationManagerService.REVIEW_NOTIF_ACTION_REMIND);
51         filter.addAction(NotificationManagerService.REVIEW_NOTIF_ACTION_DISMISS);
52         filter.addAction(NotificationManagerService.REVIEW_NOTIF_ACTION_CANCELED);
53         return filter;
54     }
55 
56     // Cancels the "review notification permissions" notification.
57     @VisibleForTesting
cancelNotification(Context context)58     protected void cancelNotification(Context context) {
59         NotificationManager nm = context.getSystemService(NotificationManager.class);
60         if (nm != null) {
61             nm.cancel(NotificationManagerService.TAG,
62                     SystemMessageProto.SystemMessage.NOTE_REVIEW_NOTIFICATION_PERMISSIONS);
63         } else {
64             Slog.w(TAG, "could not cancel notification: NotificationManager not found");
65         }
66     }
67 
68     @VisibleForTesting
rescheduleNotification(Context context)69     protected void rescheduleNotification(Context context) {
70         ReviewNotificationPermissionsJobService.scheduleJob(context, JOB_RESCHEDULE_TIME);
71         // log if needed
72         if (DEBUG) {
73             Slog.d(TAG, "Scheduled review permissions notification for on or after: "
74                     + LocalDateTime.now(ZoneId.systemDefault())
75                             .plus(JOB_RESCHEDULE_TIME, ChronoUnit.MILLIS));
76         }
77     }
78 
79     @Override
onReceive(Context context, Intent intent)80     public void onReceive(Context context, Intent intent) {
81         String action = intent.getAction();
82         if (action.equals(NotificationManagerService.REVIEW_NOTIF_ACTION_REMIND)) {
83             // Reschedule the notification for 7 days in the future
84             rescheduleNotification(context);
85 
86             // note that the user has interacted; no longer needed to show the initial
87             // notification
88             Settings.Global.putInt(context.getContentResolver(),
89                     Settings.Global.REVIEW_PERMISSIONS_NOTIFICATION_STATE,
90                     NotificationManagerService.REVIEW_NOTIF_STATE_USER_INTERACTED);
91             cancelNotification(context);
92         } else if (action.equals(NotificationManagerService.REVIEW_NOTIF_ACTION_DISMISS)) {
93             // user dismissed; write to settings so we don't show ever again
94             Settings.Global.putInt(context.getContentResolver(),
95                     Settings.Global.REVIEW_PERMISSIONS_NOTIFICATION_STATE,
96                     NotificationManagerService.REVIEW_NOTIF_STATE_DISMISSED);
97             cancelNotification(context);
98         } else if (action.equals(NotificationManagerService.REVIEW_NOTIF_ACTION_CANCELED)) {
99             // we may get here from the user swiping away the notification,
100             // or from the notification being canceled in any other way.
101             // only in the case that the user hasn't interacted with it in
102             // any other way yet, reschedule
103             int notifState = Settings.Global.getInt(context.getContentResolver(),
104                     Settings.Global.REVIEW_PERMISSIONS_NOTIFICATION_STATE,
105                     /* default */ NotificationManagerService.REVIEW_NOTIF_STATE_UNKNOWN);
106             if (notifState == NotificationManagerService.REVIEW_NOTIF_STATE_SHOULD_SHOW) {
107                 // user hasn't interacted in the past, so reschedule once and then note that the
108                 // user *has* interacted now so we don't re-reschedule if they swipe again
109                 rescheduleNotification(context);
110                 Settings.Global.putInt(context.getContentResolver(),
111                         Settings.Global.REVIEW_PERMISSIONS_NOTIFICATION_STATE,
112                         NotificationManagerService.REVIEW_NOTIF_STATE_USER_INTERACTED);
113             } else if (notifState == NotificationManagerService.REVIEW_NOTIF_STATE_RESHOWN) {
114                 // swiping away on a rescheduled notification; mark as interacted and
115                 // don't reschedule again.
116                 Settings.Global.putInt(context.getContentResolver(),
117                         Settings.Global.REVIEW_PERMISSIONS_NOTIFICATION_STATE,
118                         NotificationManagerService.REVIEW_NOTIF_STATE_USER_INTERACTED);
119             }
120         }
121     }
122 }
123