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