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