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.u18notifications;
17 
18 import static com.android.adservices.service.consent.ConsentManager.MANUAL_INTERACTIONS_RECORDED;
19 import static com.android.adservices.ui.notifications.ConsentNotificationActivity.NotificationFragmentEnum.LANDING_PAGE_DISMISSED;
20 import static com.android.adservices.ui.notifications.ConsentNotificationActivity.NotificationFragmentEnum.LANDING_PAGE_DISPLAYED;
21 import static com.android.adservices.ui.notifications.ConsentNotificationActivity.NotificationFragmentEnum.LANDING_PAGE_GOT_IT_CLICKED;
22 import static com.android.adservices.ui.notifications.ConsentNotificationActivity.NotificationFragmentEnum.LANDING_PAGE_MORE_BUTTON_CLICKED;
23 import static com.android.adservices.ui.notifications.ConsentNotificationActivity.NotificationFragmentEnum.LANDING_PAGE_SCROLLED;
24 import static com.android.adservices.ui.notifications.ConsentNotificationActivity.NotificationFragmentEnum.LANDING_PAGE_SCROLLED_TO_BOTTOM;
25 import static com.android.adservices.ui.notifications.ConsentNotificationActivity.NotificationFragmentEnum.LANDING_PAGE_SETTINGS_BUTTON_CLICKED;
26 import static com.android.adservices.ui.settings.activities.AdServicesSettingsMainActivity.FROM_NOTIFICATION_KEY;
27 import static com.android.adservices.service.ui.ux.collection.PrivacySandboxUxCollection.RVC_UX;
28 
29 import android.content.Context;
30 import android.content.Intent;
31 import android.os.Build;
32 import android.os.Bundle;
33 import android.text.method.LinkMovementMethod;
34 import android.view.LayoutInflater;
35 import android.view.View;
36 import android.view.View.OnScrollChangeListener;
37 import android.view.ViewGroup;
38 import android.widget.Button;
39 import android.widget.ScrollView;
40 import android.widget.TextView;
41 
42 import androidx.annotation.NonNull;
43 import androidx.annotation.Nullable;
44 import androidx.annotation.RequiresApi;
45 import androidx.fragment.app.Fragment;
46 
47 import com.android.adservices.api.R;
48 import com.android.adservices.service.FlagsFactory;
49 import com.android.adservices.service.consent.AdServicesApiType;
50 import com.android.adservices.service.consent.ConsentManager;
51 import com.android.adservices.ui.notifications.ConsentNotificationActivity;
52 import com.android.adservices.ui.settings.activities.AdServicesSettingsMainActivity;
53 
54 /** Fragment for the Notification landing page for U18 users. */
55 @RequiresApi(Build.VERSION_CODES.S)
56 public class ConsentNotificationU18Fragment extends Fragment {
57     public static final String IS_TOPICS_INFO_VIEW_EXPANDED_KEY = "is_topics_info_view_expanded";
58 
59     private @Nullable ScrollToBottomController mScrollToBottomController;
60 
61     @Override
onCreateView( @onNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)62     public View onCreateView(
63             @NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
64         return setupActivity(inflater, container);
65     }
66 
67     @Override
onViewCreated(@onNull View view, Bundle savedInstanceState)68     public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {
69         setupListeners(savedInstanceState);
70         ConsentNotificationActivity.handleAction(LANDING_PAGE_DISPLAYED, getContext());
71         if (ConsentManager.getInstance().getUx() == RVC_UX
72                 && ConsentManager.getInstance().getUserManualInteractionWithConsent()
73                         != MANUAL_INTERACTIONS_RECORDED) {
74             ConsentManager.getInstance().enable(requireContext(), AdServicesApiType.MEASUREMENTS);
75         }
76     }
77 
78     @Override
onSaveInstanceState(@onNull Bundle savedInstanceState)79     public void onSaveInstanceState(@NonNull Bundle savedInstanceState) {
80         super.onSaveInstanceState(savedInstanceState);
81 
82         ConsentNotificationActivity.handleAction(LANDING_PAGE_DISMISSED, getContext());
83         if (mScrollToBottomController != null) {
84             mScrollToBottomController.saveInstanceState(savedInstanceState);
85         }
86     }
87 
setupActivity(LayoutInflater inflater, ViewGroup container)88     private View setupActivity(LayoutInflater inflater, ViewGroup container) {
89         return inflater.inflate(R.layout.consent_notification_u18_fragment, container, false);
90     }
91 
setupListeners(Bundle savedInstanceState)92     private void setupListeners(Bundle savedInstanceState) {
93         // set up privacy policy link movement
94         ((TextView) requireActivity().findViewById(R.id.learn_more_from_privacy_policy))
95                 .setMovementMethod(LinkMovementMethod.getInstance());
96 
97         // set up left control button and right control button
98         Button leftControlButton = requireActivity().findViewById(R.id.leftControlButton);
99         leftControlButton.setOnClickListener(
100                 view -> {
101                     ConsentNotificationActivity.handleAction(
102                             LANDING_PAGE_SETTINGS_BUTTON_CLICKED, getContext());
103 
104                     // go to settings activity
105                     Intent intent =
106                             new Intent(requireActivity(), AdServicesSettingsMainActivity.class);
107                     intent.putExtra(FROM_NOTIFICATION_KEY, true);
108                     startActivity(intent);
109                     requireActivity().finish();
110                 });
111 
112         Button rightControlButton = requireActivity().findViewById(R.id.rightControlButton);
113         ScrollView scrollView = requireView().findViewById(R.id.notification_fragment_scrollview);
114 
115         mScrollToBottomController =
116                 new ScrollToBottomController(
117                         scrollView, leftControlButton, rightControlButton, savedInstanceState);
118         mScrollToBottomController.bind();
119         // check whether it can scroll vertically and update buttons after layout can be measured
120         scrollView.post(() -> mScrollToBottomController.updateButtonsIfHasScrolledToBottom());
121     }
122 
123     /**
124      * Allows the positive, acceptance button to scroll the view.
125      *
126      * <p>When the positive button first appears it will show the text "More". When the user taps
127      * the button, the view will scroll to the bottom. Once the view has scrolled to the bottom, the
128      * button text will be replaced with the acceptance text. Once the text has changed, the button
129      * will trigger the positive action no matter where the view is scrolled.
130      */
131     private class ScrollToBottomController implements OnScrollChangeListener {
132         private static final String STATE_HAS_SCROLLED_TO_BOTTOM = "has_scrolled_to_bottom";
133         private static final int SCROLL_DIRECTION_DOWN = 1;
134         private static final double SCROLL_MULTIPLIER = 0.8;
135 
136         private final ScrollView mScrollContainer;
137         private final Button mLeftControlButton;
138         private final Button mRightControlButton;
139 
140         private boolean mHasScrolledToBottom;
141 
ScrollToBottomController( ScrollView scrollContainer, Button leftControlButton, Button rightControlButton, @Nullable Bundle savedInstanceState)142         ScrollToBottomController(
143                 ScrollView scrollContainer,
144                 Button leftControlButton,
145                 Button rightControlButton,
146                 @Nullable Bundle savedInstanceState) {
147             this.mScrollContainer = scrollContainer;
148             this.mLeftControlButton = leftControlButton;
149             this.mRightControlButton = rightControlButton;
150             mHasScrolledToBottom =
151                     savedInstanceState != null
152                             && savedInstanceState.containsKey(STATE_HAS_SCROLLED_TO_BOTTOM)
153                             && savedInstanceState.getBoolean(STATE_HAS_SCROLLED_TO_BOTTOM);
154         }
155 
bind()156         public void bind() {
157             mScrollContainer.setOnScrollChangeListener(this);
158             mRightControlButton.setOnClickListener(this::onMoreOrAcceptClicked);
159             updateControlButtons();
160         }
161 
saveInstanceState(Bundle bundle)162         public void saveInstanceState(Bundle bundle) {
163             if (mHasScrolledToBottom) {
164                 bundle.putBoolean(STATE_HAS_SCROLLED_TO_BOTTOM, true);
165             }
166         }
167 
updateControlButtons()168         private void updateControlButtons() {
169             if (mHasScrolledToBottom) {
170                 mLeftControlButton.setVisibility(View.VISIBLE);
171                 mRightControlButton.setText(R.string.notificationUI_right_control_button_text);
172             } else {
173                 mLeftControlButton.setVisibility(View.INVISIBLE);
174                 mRightControlButton.setText(R.string.notificationUI_more_button_text);
175             }
176         }
177 
onMoreOrAcceptClicked(View view)178         private void onMoreOrAcceptClicked(View view) {
179             Context context = getContext();
180             if (context == null) {
181                 return;
182             }
183 
184             if (mHasScrolledToBottom) {
185 
186                 ConsentNotificationActivity.handleAction(LANDING_PAGE_GOT_IT_CLICKED, getContext());
187 
188                 if (FlagsFactory.getFlags().getRecordManualInteractionEnabled()) {
189                     ConsentManager.getInstance()
190                             .recordUserManualInteractionWithConsent(
191                                     ConsentManager.MANUAL_INTERACTIONS_RECORDED);
192 
193                     // acknowledge and dismiss
194                     requireActivity().finish();
195                 }
196             } else {
197                 ConsentNotificationActivity.handleAction(
198                         LANDING_PAGE_MORE_BUTTON_CLICKED, getContext());
199 
200                 mScrollContainer.smoothScrollTo(
201                         0,
202                         mScrollContainer.getScrollY()
203                                 + (int) (mScrollContainer.getHeight() * SCROLL_MULTIPLIER));
204             }
205         }
206 
207         @Override
onScrollChange( View view, int scrollX, int scrollY, int oldScrollX, int oldScrollY)208         public void onScrollChange(
209                 View view, int scrollX, int scrollY, int oldScrollX, int oldScrollY) {
210             ConsentNotificationActivity.handleAction(LANDING_PAGE_SCROLLED, getContext());
211             updateButtonsIfHasScrolledToBottom();
212         }
213 
updateButtonsIfHasScrolledToBottom()214         void updateButtonsIfHasScrolledToBottom() {
215             if (!mScrollContainer.canScrollVertically(SCROLL_DIRECTION_DOWN)) {
216                 ConsentNotificationActivity.handleAction(
217                         LANDING_PAGE_SCROLLED_TO_BOTTOM, getContext());
218                 mHasScrolledToBottom = true;
219                 updateControlButtons();
220             }
221         }
222     }
223 }
224