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.adservices.ui.notifications; 18 19 import static com.android.adservices.ui.notifications.ConsentNotificationFragment.IS_EU_DEVICE_ARGUMENT_KEY; 20 21 import android.app.Notification; 22 import android.app.NotificationChannel; 23 import android.app.NotificationManager; 24 import android.app.PendingIntent; 25 import android.content.Context; 26 import android.content.Intent; 27 import android.os.Build; 28 29 import androidx.annotation.NonNull; 30 import androidx.annotation.RequiresApi; 31 import androidx.core.app.NotificationCompat; 32 import androidx.core.app.NotificationManagerCompat; 33 34 import com.android.adservices.api.R; 35 import com.android.adservices.service.FlagsFactory; 36 import com.android.adservices.service.consent.AdServicesApiType; 37 import com.android.adservices.service.consent.ConsentManager; 38 import com.android.adservices.service.stats.UiStatsLogger; 39 import com.android.adservices.ui.OTAResourcesManager; 40 41 /** Provides methods which can be used to display Privacy Sandbox consent notification. */ 42 // TODO(b/269798827): Enable for R. 43 @RequiresApi(Build.VERSION_CODES.S) 44 public class ConsentNotificationTrigger { 45 /* Random integer for NotificationCompat purposes. */ 46 public static final int NOTIFICATION_ID = 67920; 47 private static final String CHANNEL_ID = "PRIVACY_SANDBOX_CHANNEL"; 48 private static final int NOTIFICATION_PRIORITY = NotificationCompat.PRIORITY_MAX; 49 50 /** 51 * Shows consent notification as the highest priority notification to the user. 52 * 53 * @param context Context which is used to display {@link NotificationCompat} 54 */ showConsentNotification(@onNull Context context, boolean isEuDevice)55 public static void showConsentNotification(@NonNull Context context, boolean isEuDevice) { 56 UiStatsLogger.logRequestedNotification(context); 57 58 boolean gaUxFeatureEnabled = FlagsFactory.getFlags().getGaUxFeatureEnabled(); 59 60 NotificationManagerCompat notificationManager = NotificationManagerCompat.from(context); 61 ConsentManager consentManager = ConsentManager.getInstance(context); 62 if (!notificationManager.areNotificationsEnabled()) { 63 recordNotificationDisplayed(gaUxFeatureEnabled, consentManager); 64 UiStatsLogger.logNotificationDisabled(context); 65 return; 66 } 67 68 // Set OTA resources if it exists. 69 if (FlagsFactory.getFlags().getUiOtaStringsFeatureEnabled()) { 70 OTAResourcesManager.applyOTAResources(context.getApplicationContext(), true); 71 } 72 73 setupConsents(context, isEuDevice, gaUxFeatureEnabled, consentManager); 74 75 createNotificationChannel(context); 76 Notification notification = getNotification(context, isEuDevice, gaUxFeatureEnabled); 77 notificationManager.notify(NOTIFICATION_ID, notification); 78 79 UiStatsLogger.logNotificationDisplayed(context); 80 recordNotificationDisplayed(gaUxFeatureEnabled, consentManager); 81 } 82 recordNotificationDisplayed( boolean gaUxFeatureEnabled, ConsentManager consentManager)83 private static void recordNotificationDisplayed( 84 boolean gaUxFeatureEnabled, ConsentManager consentManager) { 85 if (FlagsFactory.getFlags().getRecordManualInteractionEnabled() 86 && consentManager.getUserManualInteractionWithConsent() 87 != ConsentManager.MANUAL_INTERACTIONS_RECORDED) { 88 consentManager.recordUserManualInteractionWithConsent( 89 ConsentManager.NO_MANUAL_INTERACTIONS_RECORDED); 90 } 91 if (gaUxFeatureEnabled) { 92 consentManager.recordGaUxNotificationDisplayed(); 93 } 94 consentManager.recordNotificationDisplayed(); 95 } 96 97 @NonNull getNotification( @onNull Context context, boolean isEuDevice, boolean gaUxFeatureEnabled)98 private static Notification getNotification( 99 @NonNull Context context, boolean isEuDevice, boolean gaUxFeatureEnabled) { 100 Notification notification; 101 if (gaUxFeatureEnabled) { 102 if (FlagsFactory.getFlags().getEuNotifFlowChangeEnabled()) { 103 notification = getGaV2ConsentNotification(context, isEuDevice); 104 } else { 105 notification = getGaConsentNotification(context, isEuDevice); 106 } 107 } else { 108 notification = getConsentNotification(context, isEuDevice); 109 } 110 111 // make notification sticky (non-dismissible) for EuDevices when the GA UX feature is on 112 if (gaUxFeatureEnabled && isEuDevice) { 113 notification.flags |= Notification.FLAG_ONGOING_EVENT | Notification.FLAG_NO_CLEAR; 114 } 115 return notification; 116 } 117 118 // setup default consents based on information whether the device is EU or non-EU device and 119 // GA UX feature flag is enabled. setupConsents( @onNull Context context, boolean isEuDevice, boolean gaUxFeatureEnabled, ConsentManager consentManager)120 private static void setupConsents( 121 @NonNull Context context, 122 boolean isEuDevice, 123 boolean gaUxFeatureEnabled, 124 ConsentManager consentManager) { 125 // Keep the feature flag at the upper level to make it easier to cleanup the code once 126 // the beta functionality is fully deprecated and abandoned. 127 if (gaUxFeatureEnabled) { 128 // EU: all APIs are by default disabled 129 // ROW: all APIs are by default enabled 130 // TODO(b/260266623): change consent state to UNDEFINED 131 if (isEuDevice) { 132 consentManager.recordTopicsDefaultConsent(false); 133 consentManager.recordFledgeDefaultConsent(false); 134 consentManager.recordMeasurementDefaultConsent(false); 135 136 consentManager.disable(context, AdServicesApiType.TOPICS); 137 consentManager.disable(context, AdServicesApiType.FLEDGE); 138 consentManager.disable(context, AdServicesApiType.MEASUREMENTS); 139 } else { 140 consentManager.recordTopicsDefaultConsent(true); 141 consentManager.recordFledgeDefaultConsent(true); 142 consentManager.recordMeasurementDefaultConsent(true); 143 144 consentManager.enable(context, AdServicesApiType.TOPICS); 145 consentManager.enable(context, AdServicesApiType.FLEDGE); 146 consentManager.enable(context, AdServicesApiType.MEASUREMENTS); 147 } 148 } else { 149 // For the ROW devices, set the consent to GIVEN (enabled). 150 // For the EU devices, set the consent to REVOKED (disabled) 151 if (!isEuDevice) { 152 consentManager.enable(context); 153 } else { 154 consentManager.disable(context); 155 } 156 } 157 } 158 getGaV2ConsentNotification( @onNull Context context, boolean isEuDevice)159 private static Notification getGaV2ConsentNotification( 160 @NonNull Context context, boolean isEuDevice) { 161 Intent intent = new Intent(context, ConsentNotificationActivity.class); 162 intent.putExtra(IS_EU_DEVICE_ARGUMENT_KEY, isEuDevice); 163 PendingIntent pendingIntent = 164 PendingIntent.getActivity(context, 1, intent, PendingIntent.FLAG_IMMUTABLE); 165 NotificationCompat.BigTextStyle textStyle = 166 new NotificationCompat.BigTextStyle() 167 .bigText( 168 isEuDevice 169 ? context.getString( 170 R.string.notificationUI_notification_ga_content_eu_v2) 171 : context.getString( 172 R.string.notificationUI_notification_ga_content_v2)); 173 NotificationCompat.Builder notification = 174 new NotificationCompat.Builder(context, CHANNEL_ID) 175 .setSmallIcon(R.drawable.ic_info_icon) 176 .setContentTitle( 177 context.getString( 178 isEuDevice 179 ? R.string.notificationUI_notification_ga_title_eu_v2 180 : R.string.notificationUI_notification_ga_title_v2)) 181 .setContentText( 182 context.getString( 183 isEuDevice 184 ? R.string.notificationUI_notification_ga_content_eu_v2 185 : R.string.notificationUI_notification_ga_content_v2)) 186 .setStyle(textStyle) 187 .setPriority(NOTIFICATION_PRIORITY) 188 .setAutoCancel(true) 189 .setContentIntent(pendingIntent); 190 return notification.build(); 191 } 192 193 /** 194 * Returns a {@link NotificationCompat.Builder} which can be used to display consent 195 * notification to the user when GaUxFeature flag is enabled. 196 * 197 * @param context {@link Context} which is used to prepare a {@link NotificationCompat}. 198 */ getGaConsentNotification( @onNull Context context, boolean isEuDevice)199 private static Notification getGaConsentNotification( 200 @NonNull Context context, boolean isEuDevice) { 201 Intent intent = new Intent(context, ConsentNotificationActivity.class); 202 intent.putExtra(IS_EU_DEVICE_ARGUMENT_KEY, isEuDevice); 203 PendingIntent pendingIntent = 204 PendingIntent.getActivity(context, 1, intent, PendingIntent.FLAG_IMMUTABLE); 205 NotificationCompat.BigTextStyle textStyle = 206 new NotificationCompat.BigTextStyle() 207 .bigText( 208 isEuDevice 209 ? context.getString( 210 R.string.notificationUI_notification_ga_content_eu) 211 : context.getString( 212 R.string.notificationUI_notification_ga_content)); 213 NotificationCompat.Builder notification = 214 new NotificationCompat.Builder(context, CHANNEL_ID) 215 .setSmallIcon(R.drawable.ic_info_icon) 216 .setContentTitle( 217 context.getString( 218 isEuDevice 219 ? R.string.notificationUI_notification_ga_title_eu 220 : R.string.notificationUI_notification_ga_title)) 221 .setContentText( 222 context.getString( 223 isEuDevice 224 ? R.string.notificationUI_notification_ga_content_eu 225 : R.string.notificationUI_notification_ga_content)) 226 .setStyle(textStyle) 227 .setPriority(NOTIFICATION_PRIORITY) 228 .setAutoCancel(true) 229 .setContentIntent(pendingIntent); 230 231 if (isEuDevice && !FlagsFactory.getFlags().getNotificationDismissedOnClick()) { 232 notification.setAutoCancel(false); 233 } 234 235 return notification.build(); 236 } 237 238 /** 239 * Returns a {@link NotificationCompat.Builder} which can be used to display consent 240 * notification to the user. 241 * 242 * @param context {@link Context} which is used to prepare a {@link NotificationCompat}. 243 */ getConsentNotification( @onNull Context context, boolean isEuDevice)244 private static Notification getConsentNotification( 245 @NonNull Context context, boolean isEuDevice) { 246 Intent intent = new Intent(context, ConsentNotificationActivity.class); 247 intent.putExtra(IS_EU_DEVICE_ARGUMENT_KEY, isEuDevice); 248 PendingIntent pendingIntent = 249 PendingIntent.getActivity( 250 context, 1, intent, PendingIntent.FLAG_IMMUTABLE); 251 NotificationCompat.BigTextStyle textStyle = 252 new NotificationCompat.BigTextStyle() 253 .bigText( 254 isEuDevice 255 ? context.getString( 256 R.string.notificationUI_notification_content_eu) 257 : context.getString( 258 R.string.notificationUI_notification_content)); 259 return new NotificationCompat.Builder(context, CHANNEL_ID) 260 .setSmallIcon(R.drawable.ic_info_icon) 261 .setContentTitle( 262 context.getString( 263 isEuDevice 264 ? R.string.notificationUI_notification_title_eu 265 : R.string.notificationUI_notification_title)) 266 .setContentText( 267 context.getString( 268 isEuDevice 269 ? R.string.notificationUI_notification_content_eu 270 : R.string.notificationUI_notification_content)) 271 .setStyle(textStyle) 272 .setPriority(NOTIFICATION_PRIORITY) 273 .setAutoCancel(true) 274 .setContentIntent(pendingIntent) 275 .build(); 276 } 277 createNotificationChannel(@onNull Context context)278 private static void createNotificationChannel(@NonNull Context context) { 279 // TODO (b/230372892): styling -> adjust channels to use Android System labels. 280 int importance = NotificationManager.IMPORTANCE_HIGH; 281 NotificationChannel channel = 282 new NotificationChannel( 283 CHANNEL_ID, 284 context.getString(R.string.settingsUI_main_view_title), 285 importance); 286 channel.setDescription(context.getString(R.string.settingsUI_main_view_title)); 287 NotificationManager notificationManager = 288 context.getSystemService(NotificationManager.class); 289 notificationManager.createNotificationChannel(channel); 290 } 291 } 292