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 package com.android.adservices.ui.settings.viewmodels;
17 
18 import android.app.Application;
19 import android.os.Build;
20 import android.util.Pair;
21 
22 import androidx.annotation.NonNull;
23 import androidx.annotation.RequiresApi;
24 import androidx.lifecycle.AndroidViewModel;
25 import androidx.lifecycle.LiveData;
26 import androidx.lifecycle.MutableLiveData;
27 
28 import com.android.adservices.data.topics.Topic;
29 import com.android.adservices.service.FlagsFactory;
30 import com.android.adservices.service.consent.AdServicesApiConsent;
31 import com.android.adservices.service.consent.AdServicesApiType;
32 import com.android.adservices.service.consent.ConsentManager;
33 import com.android.adservices.ui.settings.fragments.AdServicesSettingsTopicsFragment;
34 import com.android.internal.annotations.VisibleForTesting;
35 import com.android.settingslib.widget.MainSwitchBar;
36 
37 import com.google.common.collect.ImmutableList;
38 
39 /**
40  * View model for the topics view and blocked topics view of the AdServices Settings App. This view
41  * model is responsible for serving topics to the topics view and blocked topics view, and
42  * interacting with the {@link ConsentManager} that persists and changes the topics data in a
43  * storage.
44  */
45 @RequiresApi(Build.VERSION_CODES.S)
46 public class TopicsViewModel extends AndroidViewModel {
47 
48     private final MutableLiveData<Pair<TopicsViewModelUiEvent, Topic>> mEventTrigger =
49             new MutableLiveData<>();
50     private final MutableLiveData<ImmutableList<Topic>> mTopics;
51     private final MutableLiveData<ImmutableList<Topic>> mBlockedTopics;
52     private final ConsentManager mConsentManager;
53     private final MutableLiveData<Boolean> mTopicsConsent;
54 
55     /** UI event triggered by view model */
56     public enum TopicsViewModelUiEvent {
57         SWITCH_ON_TOPICS,
58         SWITCH_OFF_TOPICS,
59         BLOCK_TOPIC,
60         RESET_TOPICS,
61         DISPLAY_BLOCKED_TOPICS_FRAGMENT,
62     }
63 
TopicsViewModel(@onNull Application application)64     public TopicsViewModel(@NonNull Application application) {
65         super(application);
66         mConsentManager = ConsentManager.getInstance();
67         mTopics = new MutableLiveData<>(getTopicsFromConsentManager());
68         mBlockedTopics = new MutableLiveData<>(getBlockedTopicsFromConsentManager());
69         mTopicsConsent =
70                 FlagsFactory.getFlags().getGaUxFeatureEnabled()
71                         ? new MutableLiveData<>(getTopicsConsentFromConsentManager())
72                         : null;
73     }
74 
75     @VisibleForTesting
TopicsViewModel( @onNull Application application, ConsentManager consentManager, Boolean topicsConsent)76     public TopicsViewModel(
77             @NonNull Application application,
78             ConsentManager consentManager,
79             Boolean topicsConsent) {
80         super(application);
81         mConsentManager = consentManager;
82         mTopics = new MutableLiveData<>(getTopicsFromConsentManager());
83         mBlockedTopics = new MutableLiveData<>(getBlockedTopicsFromConsentManager());
84         mTopicsConsent = new MutableLiveData<>(topicsConsent);
85     }
86 
87     /**
88      * Provides the topics displayed in {@link AdServicesSettingsTopicsFragment}.
89      *
90      * @return A list of {@link Topic}s that represents the user's interests.
91      */
getTopics()92     public LiveData<ImmutableList<Topic>> getTopics() {
93         return mTopics;
94     }
95 
96     /**
97      * Provides the blocked topics list.
98      *
99      * @return a list of topics that represents the user's blocked interests.
100      */
getBlockedTopics()101     public LiveData<ImmutableList<Topic>> getBlockedTopics() {
102         return mBlockedTopics;
103     }
104 
105     /**
106      * Revoke the consent for the specified topic (i.e. block the topic).
107      *
108      * @param topic the topic to be blocked.
109      */
revokeTopicConsent(Topic topic)110     public void revokeTopicConsent(Topic topic) {
111         mConsentManager.revokeConsentForTopic(topic);
112         refresh();
113     }
114 
115     /**
116      * Reads all the data from {@link ConsentManager}.
117      *
118      * <p>TODO(b/238387560): To be moved to private when is fixed.
119      */
refresh()120     public void refresh() {
121         mTopics.postValue(getTopicsFromConsentManager());
122         mBlockedTopics.postValue(getBlockedTopicsFromConsentManager());
123     }
124 
125     /** Reset all information related to topics but blocked topics. */
resetTopics()126     public void resetTopics() {
127         mConsentManager.resetTopics();
128         mTopics.postValue(getTopicsFromConsentManager());
129     }
130 
131     /** Returns an observable but immutable event enum representing an view action on UI. */
getUiEvents()132     public LiveData<Pair<TopicsViewModelUiEvent, Topic>> getUiEvents() {
133         return mEventTrigger;
134     }
135 
136     /**
137      * Sets the UI Event as handled so the action will not be handled again if activity is
138      * recreated.
139      */
uiEventHandled()140     public void uiEventHandled() {
141         mEventTrigger.postValue(new Pair<>(null, null));
142     }
143 
144     /**
145      * Triggers the block of the specified topic in the list of topics in {@link
146      * AdServicesSettingsTopicsFragment}.
147      *
148      * @param topic the topic to be blocked.
149      */
revokeTopicConsentButtonClickHandler(Topic topic)150     public void revokeTopicConsentButtonClickHandler(Topic topic) {
151         mEventTrigger.postValue(new Pair<>(TopicsViewModelUiEvent.BLOCK_TOPIC, topic));
152     }
153 
154     /** Triggers a reset of all topics related data. */
resetTopicsButtonClickHandler()155     public void resetTopicsButtonClickHandler() {
156         mEventTrigger.postValue(new Pair<>(TopicsViewModelUiEvent.RESET_TOPICS, null));
157     }
158 
159     /** Triggers {@link AdServicesSettingsTopicsFragment}. */
blockedTopicsFragmentButtonClickHandler()160     public void blockedTopicsFragmentButtonClickHandler() {
161         mEventTrigger.postValue(
162                 new Pair<>(TopicsViewModelUiEvent.DISPLAY_BLOCKED_TOPICS_FRAGMENT, null));
163     }
164 
165     // ---------------------------------------------------------------------------------------------
166     // Private Methods
167     // ---------------------------------------------------------------------------------------------
168 
getTopicsFromConsentManager()169     private ImmutableList<Topic> getTopicsFromConsentManager() {
170         return mConsentManager.getKnownTopicsWithConsent();
171     }
172 
getBlockedTopicsFromConsentManager()173     private ImmutableList<Topic> getBlockedTopicsFromConsentManager() {
174         return mConsentManager.getTopicsWithRevokedConsent();
175     }
176 
177     /**
178      * Provides {@link AdServicesApiConsent} displayed in {@link AdServicesSettingsTopicsFragment}
179      * as a Switch value.
180      *
181      * @return mTopicsConsent indicates if user has consented to Topics Api usage.
182      */
getTopicsConsent()183     public MutableLiveData<Boolean> getTopicsConsent() {
184         return mTopicsConsent;
185     }
186 
187     /**
188      * Sets the user consent for PP APIs.
189      *
190      * @param newTopicsConsentValue the new value that user consent should be set to for Topics PP
191      *     APIs.
192      */
setTopicsConsent(Boolean newTopicsConsentValue)193     public void setTopicsConsent(Boolean newTopicsConsentValue) {
194         if (newTopicsConsentValue) {
195             mConsentManager.enable(getApplication(), AdServicesApiType.TOPICS);
196         } else {
197             mConsentManager.disable(getApplication(), AdServicesApiType.TOPICS);
198         }
199         mTopicsConsent.postValue(getTopicsConsentFromConsentManager());
200         if (FlagsFactory.getFlags().getRecordManualInteractionEnabled()) {
201             ConsentManager.getInstance()
202                     .recordUserManualInteractionWithConsent(
203                             ConsentManager.MANUAL_INTERACTIONS_RECORDED);
204         }
205     }
206     /**
207      * Triggers opt out process for Privacy Sandbox. Also reverts the switch state, since
208      * confirmation dialog will handle switch change.
209      */
consentSwitchClickHandler(MainSwitchBar topicsSwitchBar)210     public void consentSwitchClickHandler(MainSwitchBar topicsSwitchBar) {
211         if (topicsSwitchBar.isChecked()) {
212             topicsSwitchBar.setChecked(false);
213             mEventTrigger.postValue(new Pair<>(TopicsViewModelUiEvent.SWITCH_ON_TOPICS, null));
214         } else {
215             topicsSwitchBar.setChecked(true);
216             mEventTrigger.postValue(new Pair<>(TopicsViewModelUiEvent.SWITCH_OFF_TOPICS, null));
217         }
218     }
219 
getTopicsConsentFromConsentManager()220     private boolean getTopicsConsentFromConsentManager() {
221         return mConsentManager.getConsent(AdServicesApiType.TOPICS).isGiven();
222     }
223 }
224