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