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