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