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 package com.android.adservices.service.ui.data; 17 18 import static com.android.adservices.service.DebugFlagsConstants.KEY_CONSENT_NOTIFICATION_DEBUG_MODE; 19 import static com.android.adservices.service.FlagsConstants.KEY_EEA_PAS_UX_ENABLED; 20 import static com.android.adservices.service.FlagsConstants.KEY_PAS_UX_ENABLED; 21 import static com.android.adservices.service.ui.ux.collection.PrivacySandboxUxCollection.UNSUPPORTED_UX; 22 23 import android.adservices.common.AdServicesStates; 24 import android.annotation.NonNull; 25 import android.content.Context; 26 import android.content.SharedPreferences; 27 import android.os.Build; 28 29 import androidx.annotation.RequiresApi; 30 31 import com.android.adservices.LogUtil; 32 import com.android.adservices.service.Flags; 33 import com.android.adservices.service.FlagsConstants; 34 import com.android.adservices.service.FlagsFactory; 35 import com.android.adservices.service.consent.AdServicesApiType; 36 import com.android.adservices.service.consent.ConsentManager; 37 import com.android.adservices.service.consent.DeviceRegionProvider; 38 import com.android.adservices.service.ui.enrollment.collection.PrivacySandboxEnrollmentChannelCollection; 39 import com.android.adservices.service.ui.ux.collection.PrivacySandboxUxCollection; 40 import com.android.adservices.shared.common.ApplicationContextSingleton; 41 42 import java.util.Map; 43 44 /** 45 * Manager that deals with all UX related states. All other UX code should use this class to read ux 46 * component states. Specifically, this class: 47 * <li>Reads process stable UX flags from {@code Flags}, and provide these flags through the 48 * getFlags API. 49 * <li>Reads process stable consent manager bits such as UX and enrollment channel, so that these 50 * values are process stable. 51 */ 52 @RequiresApi(Build.VERSION_CODES.S) 53 public class UxStatesManager { 54 55 private static final Object LOCK = new Object(); 56 private static volatile UxStatesManager sUxStatesManager; 57 private final Map<String, Boolean> mUxFlags; 58 private final ConsentManager mConsentManager; 59 private final SharedPreferences mUxSharedPreferences; 60 private PrivacySandboxUxCollection mUx; 61 private PrivacySandboxEnrollmentChannelCollection mEnrollmentChannel; 62 private final boolean mIsEeaDevice; 63 UxStatesManager( @onNull Context context, @NonNull Flags flags, @NonNull ConsentManager consentManager)64 UxStatesManager( 65 @NonNull Context context, 66 @NonNull Flags flags, 67 @NonNull ConsentManager consentManager) { 68 LogUtil.d("Instantiating lazy UxStatesManager instance."); 69 70 mUxFlags = flags.getUxFlags(); 71 mConsentManager = consentManager; 72 mIsEeaDevice = DeviceRegionProvider.isEuDevice(context); 73 mUxSharedPreferences = 74 context.getSharedPreferences("UX_SHARED_PREFERENCES", Context.MODE_PRIVATE); 75 } 76 77 private static class UxStatesManagerLazyInstanceHolder { 78 static final UxStatesManager LAZY_INSTANCE = 79 new UxStatesManager( 80 ApplicationContextSingleton.get(), 81 FlagsFactory.getFlags(), 82 ConsentManager.getInstance()); 83 } 84 85 /** Returns an instance of the UxStatesManager. */ 86 @NonNull getInstance()87 public static UxStatesManager getInstance() { 88 return UxStatesManagerLazyInstanceHolder.LAZY_INSTANCE; 89 } 90 91 /** Saves the AdServices states into data stores. */ persistAdServicesStates(AdServicesStates adServicesStates)92 public void persistAdServicesStates(AdServicesStates adServicesStates) { 93 // Only a subset of states should be persisted. 94 mConsentManager.setAdIdEnabled(adServicesStates.isAdIdEnabled()); 95 // TO-DO (b/285005057): Remove the if statement when users can graduate. 96 if (mConsentManager.isU18Account() == null || !mConsentManager.isU18Account()) { 97 mConsentManager.setU18Account(adServicesStates.isU18Account()); 98 } 99 mConsentManager.setAdultAccount(adServicesStates.isAdultAccount()); 100 mConsentManager.setEntryPointEnabled(adServicesStates.isPrivacySandboxUiEnabled()); 101 } 102 103 /** Returns process stable UX flags. */ getFlag(String uxFlagKey)104 public boolean getFlag(String uxFlagKey) { 105 if (!mUxFlags.containsKey(uxFlagKey)) { 106 LogUtil.e("Key not found in cached UX flags: %s", uxFlagKey); 107 } 108 Boolean value = mUxFlags.get(uxFlagKey); 109 return value != null ? value : false; 110 } 111 112 /** Returns process stable UX. */ getUx()113 public PrivacySandboxUxCollection getUx() { 114 // Lazy read. 115 if (mUx == null) { 116 mUx = mConsentManager.getUx(); 117 } 118 return mUx != null ? mUx : UNSUPPORTED_UX; 119 } 120 121 /** Returns process stable enrollment channel. */ getEnrollmentChannel()122 public PrivacySandboxEnrollmentChannelCollection getEnrollmentChannel() { 123 // Lazy read. 124 if (mEnrollmentChannel == null) { 125 mEnrollmentChannel = mConsentManager.getEnrollmentChannel(mUx); 126 } 127 return mEnrollmentChannel; 128 } 129 130 /** Returns process stable device region. */ isEeaDevice()131 public boolean isEeaDevice() { 132 return mIsEeaDevice; 133 } 134 135 /** Returns a common shared preference for storing temporary UX states. */ getUxSharedPreferences()136 public SharedPreferences getUxSharedPreferences() { 137 return mUxSharedPreferences; 138 } 139 140 /** 141 * Returns whether the user is already enrolled for the current UX. or it is supervised account, 142 * we then set ux and default measurement consent. 143 */ isEnrolledUser(Context context)144 public boolean isEnrolledUser(Context context) { 145 boolean isNotificationDisplayed = 146 mConsentManager.wasGaUxNotificationDisplayed() 147 || mConsentManager.wasU18NotificationDisplayed() 148 || mConsentManager.wasNotificationDisplayed() 149 || (getFlag(KEY_PAS_UX_ENABLED) 150 && mConsentManager.wasPasNotificationDisplayed()); 151 // We follow the Chrome's capabilities practice here, when user is not in adult account and 152 // u18 account, (the u18 account is for teen and un-supervised account), we are consider 153 // them as supervised accounts for now, it actually also contains robot account, but we 154 // don't have a capability for that, we will update this when we have the new capability. 155 // TODO: when new capability is available, update with new capability. 156 boolean isSupervisedAccountEnabled = 157 getFlag(FlagsConstants.KEY_IS_U18_SUPERVISED_ACCOUNT_ENABLED); 158 boolean isSupervisedUser = 159 !mConsentManager.isU18Account() && !mConsentManager.isAdultAccount(); 160 // In case supervised account logging in second time and not able to set the ux to u18 161 if (isSupervisedAccountEnabled && isSupervisedUser) { 162 LogUtil.d("supervised user get"); 163 mConsentManager.setUx(PrivacySandboxUxCollection.U18_UX); 164 } 165 if (!isNotificationDisplayed) { 166 if (isSupervisedAccountEnabled && isSupervisedUser) { 167 // We initial the default consent and notification. 168 LogUtil.d("supervised user initial"); 169 mConsentManager.setU18NotificationDisplayed(true); 170 mConsentManager.enable(context, AdServicesApiType.MEASUREMENTS); 171 return true; 172 } 173 return false; 174 } 175 return true; 176 } 177 178 /** 179 * PAS could be enabled but user may not have received notification, so user would see GA UX 180 * instead of PAS UX. Before the notification card is shown the notification has not been 181 * displayed yet, so if the calling context is related to this then we should only look at the 182 * PAS UX flag. 183 * 184 * @param beforeNotificationShown True if the calling context is logic before PAS notification 185 * shown has been recorded. Or in the case of EEA, before notificationOpened. 186 * @return True if user will see PAS UX for settings/notification, otherwise false. 187 */ pasUxIsActive(boolean beforeNotificationShown)188 public boolean pasUxIsActive(boolean beforeNotificationShown) { 189 if (getFlag(KEY_EEA_PAS_UX_ENABLED)) { 190 if (isEeaDevice()) { 191 return wasPasNotificationOpened() || beforeNotificationShown; 192 } 193 } 194 return getFlag(KEY_PAS_UX_ENABLED) 195 && (wasPasNotificationDisplayed() || beforeNotificationShown); 196 } 197 198 /** Returns if PAS notification was displayed. */ wasPasNotificationDisplayed()199 private boolean wasPasNotificationDisplayed() { 200 if (getFlag(KEY_CONSENT_NOTIFICATION_DEBUG_MODE)) { 201 return getFlag(KEY_PAS_UX_ENABLED); 202 } 203 return ConsentManager.getInstance().wasPasNotificationDisplayed(); 204 } 205 206 /** Returns if PAS notification was opened. */ wasPasNotificationOpened()207 private boolean wasPasNotificationOpened() { 208 if (getFlag(KEY_CONSENT_NOTIFICATION_DEBUG_MODE)) { 209 return getFlag(KEY_EEA_PAS_UX_ENABLED); 210 } 211 return ConsentManager.getInstance().wasPasNotificationOpened(); 212 } 213 } 214