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.service.FlagsFactory;
29 import com.android.adservices.service.consent.AdServicesApiConsent;
30 import com.android.adservices.service.consent.AdServicesApiType;
31 import com.android.adservices.service.consent.App;
32 import com.android.adservices.service.consent.ConsentManager;
33 import com.android.adservices.ui.settings.fragments.AdServicesSettingsAppsFragment;
34 import com.android.internal.annotations.VisibleForTesting;
35 import com.android.settingslib.widget.MainSwitchBar;
36 
37 import com.google.common.collect.ImmutableList;
38 
39 import java.io.IOException;
40 
41 /**
42  * View model for the apps view and blocked apps view of the AdServices Settings App. This view
43  * model is responsible for serving apps to the apps view and blocked apps view, and interacting
44  * with the {@link ConsentManager} that persists and changes the apps data in a storage.
45  */
46 @RequiresApi(Build.VERSION_CODES.S)
47 public class AppsViewModel extends AndroidViewModel {
48 
49     private final MutableLiveData<Pair<AppsViewModelUiEvent, App>> mEventTrigger =
50             new MutableLiveData<>();
51     private final MutableLiveData<ImmutableList<App>> mApps;
52     private final MutableLiveData<ImmutableList<App>> mBlockedApps;
53     private final ConsentManager mConsentManager;
54     private final MutableLiveData<Boolean> mAppsConsent;
55 
56     /** UI event triggered by view model */
57     public enum AppsViewModelUiEvent {
58         SWITCH_ON_APPS,
59         SWITCH_OFF_APPS,
60         BLOCK_APP,
61         RESET_APPS,
62         DISPLAY_BLOCKED_APPS_FRAGMENT,
63     }
64 
AppsViewModel(@onNull Application application)65     public AppsViewModel(@NonNull Application application) {
66         super(application);
67 
68         mConsentManager = ConsentManager.getInstance();
69         mApps = new MutableLiveData<>(getAppsFromConsentManager());
70         mBlockedApps = new MutableLiveData<>(getBlockedAppsFromConsentManager());
71         mAppsConsent =
72                 FlagsFactory.getFlags().getGaUxFeatureEnabled()
73                         ? new MutableLiveData<>(getAppsConsentFromConsentManager())
74                         : null;
75     }
76 
77     @VisibleForTesting
AppsViewModel(@onNull Application application, ConsentManager consentManager)78     public AppsViewModel(@NonNull Application application, ConsentManager consentManager) {
79         super(application);
80 
81         mConsentManager = consentManager;
82         mApps = new MutableLiveData<>(getAppsFromConsentManager());
83         mBlockedApps = new MutableLiveData<>(getBlockedAppsFromConsentManager());
84         mAppsConsent = new MutableLiveData<>(true);
85     }
86 
87     /**
88      * Provides the apps displayed in {@link AdServicesSettingsAppsFragment}.
89      *
90      * @return A list of {@link App}s that represents apps that use FLEDGE.
91      */
getApps()92     public LiveData<ImmutableList<App>> getApps() {
93         return mApps;
94     }
95 
96     /**
97      * Provides the blocked apps list.
98      *
99      * @return a list of apps that represents the user's blocked interests.
100      */
getBlockedApps()101     public LiveData<ImmutableList<App>> getBlockedApps() {
102         return mBlockedApps;
103     }
104 
105     /**
106      * Revoke the consent for the specified app (i.e. block the app).
107      *
108      * @param app the app to be blocked.
109      */
revokeAppConsent(App app)110     public void revokeAppConsent(App app) throws IOException {
111         mConsentManager.revokeConsentForApp(app);
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         mApps.postValue(getAppsFromConsentManager());
122         mBlockedApps.postValue(getBlockedAppsFromConsentManager());
123     }
124 
125     /** Reset all information related to apps but blocked apps. */
resetApps()126     public void resetApps() throws IOException {
127         mConsentManager.resetApps();
128         mApps.postValue(getAppsFromConsentManager());
129         mConsentManager.setPaDataReset(true);
130     }
131 
132     /** Returns an observable but immutable event enum representing an view action on UI. */
getUiEvents()133     public LiveData<Pair<AppsViewModelUiEvent, App>> 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 app in the list of apps in {@link
147      * AdServicesSettingsAppsFragment}.
148      *
149      * @param app the app to be blocked.
150      */
revokeAppConsentButtonClickHandler(App app)151     public void revokeAppConsentButtonClickHandler(App app) {
152         mEventTrigger.postValue(new Pair<>(AppsViewModelUiEvent.BLOCK_APP, app));
153     }
154 
155     /** Triggers a reset of all apps related data. */
resetAppsButtonClickHandler()156     public void resetAppsButtonClickHandler() {
157         mEventTrigger.postValue(new Pair<>(AppsViewModelUiEvent.RESET_APPS, null));
158     }
159 
160     /** Triggers {@link AdServicesSettingsAppsFragment}. */
blockedAppsFragmentButtonClickHandler()161     public void blockedAppsFragmentButtonClickHandler() {
162         mEventTrigger.postValue(
163                 new Pair<>(AppsViewModelUiEvent.DISPLAY_BLOCKED_APPS_FRAGMENT, null));
164     }
165 
166     // ---------------------------------------------------------------------------------------------
167     // Private Methods
168     // ---------------------------------------------------------------------------------------------
169 
getAppsFromConsentManager()170     private ImmutableList<App> getAppsFromConsentManager() {
171         return mConsentManager.getKnownAppsWithConsent();
172     }
173 
getBlockedAppsFromConsentManager()174     private ImmutableList<App> getBlockedAppsFromConsentManager() {
175         return mConsentManager.getAppsWithRevokedConsent();
176     }
177 
178     /**
179      * Provides {@link AdServicesApiConsent} displayed in {@link AdServicesSettingsAppsFragment} as
180      * a Switch value.
181      *
182      * @return mAppsConsent indicates if user has consented to Apps Api usage.
183      */
getAppsConsent()184     public MutableLiveData<Boolean> getAppsConsent() {
185         return mAppsConsent;
186     }
187 
188     /**
189      * Sets the user consent for PP APIs.
190      *
191      * @param newAppsConsentValue the new value that user consent should be set to for Apps PP APIs.
192      */
setAppsConsent(Boolean newAppsConsentValue)193     public void setAppsConsent(Boolean newAppsConsentValue) {
194         if (newAppsConsentValue) {
195             mConsentManager.enable(getApplication(), AdServicesApiType.FLEDGE);
196         } else {
197             mConsentManager.disable(getApplication(), AdServicesApiType.FLEDGE);
198         }
199         mAppsConsent.postValue(getAppsConsentFromConsentManager());
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 appsSwitchBar)210     public void consentSwitchClickHandler(MainSwitchBar appsSwitchBar) {
211         if (appsSwitchBar.isChecked()) {
212             appsSwitchBar.setChecked(false);
213             mEventTrigger.postValue(new Pair<>(AppsViewModelUiEvent.SWITCH_ON_APPS, null));
214         } else {
215             appsSwitchBar.setChecked(true);
216             mEventTrigger.postValue(new Pair<>(AppsViewModelUiEvent.SWITCH_OFF_APPS, null));
217         }
218     }
219 
getAppsConsentFromConsentManager()220     private boolean getAppsConsentFromConsentManager() {
221         return mConsentManager.getConsent(AdServicesApiType.FLEDGE).isGiven();
222     }
223 }
224