1 /* 2 * Copyright 2016, 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.managedprovisioning.preprovisioning.terms.adapters; 17 18 import static android.view.View.TEXT_ALIGNMENT_TEXT_START; 19 20 import static java.util.Objects.requireNonNull; 21 22 import android.content.Context; 23 import android.content.Intent; 24 import android.view.LayoutInflater; 25 import android.view.View; 26 import android.view.View.AccessibilityDelegate; 27 import android.view.ViewGroup; 28 import android.view.accessibility.AccessibilityNodeInfo; 29 import android.widget.ImageView; 30 import android.widget.LinearLayout; 31 import android.widget.TextView; 32 33 import androidx.annotation.NonNull; 34 import androidx.recyclerview.widget.RecyclerView; 35 36 import com.android.managedprovisioning.R; 37 import com.android.managedprovisioning.common.AccessibilityContextMenuMaker; 38 import com.android.managedprovisioning.common.ClickableSpanFactory; 39 import com.android.managedprovisioning.common.StylerHelper; 40 import com.android.managedprovisioning.common.Utils; 41 import com.android.managedprovisioning.flags.Flags; 42 import com.android.managedprovisioning.preprovisioning.terms.TermsDocument; 43 44 import com.google.android.setupdesign.util.DescriptionStyler; 45 46 import java.util.List; 47 48 /** 49 * Displays {@link TermsDocument} objects in a {@link RecyclerView}. 50 */ 51 public class TermsListAdapter extends RecyclerView.Adapter<TermsListAdapter.TermsViewHolder> { 52 private static final int VIEW_TYPE_GENERAL_DISCLAIMER = 1; 53 private static final int VIEW_TYPE_REGULAR_DISCLAIMER = 2; 54 55 private final TermsDocument mGenericDisclaimer; 56 private final List<TermsDocument> mTermsDocuments; 57 private final LayoutInflater mInflater; 58 private final AccessibilityContextMenuMaker mContextMenuMaker; 59 private final Context mContext; 60 private final TermsBridge mTermsBridge; 61 private final Utils mUtils; 62 private final StylerHelper mStylerHelper; 63 TermsListAdapter(Context context, TermsDocument genericDisclaimer, List<TermsDocument> termsDocuments, LayoutInflater layoutInflater, AccessibilityContextMenuMaker contextMenuMaker, TermsBridge termsBridge, Utils utils, StylerHelper stylerHelper)64 public TermsListAdapter(Context context, TermsDocument genericDisclaimer, 65 List<TermsDocument> termsDocuments, LayoutInflater layoutInflater, 66 AccessibilityContextMenuMaker contextMenuMaker, TermsBridge termsBridge, Utils utils, 67 StylerHelper stylerHelper) { 68 mGenericDisclaimer = requireNonNull(genericDisclaimer); 69 mTermsDocuments = requireNonNull(termsDocuments); 70 mInflater = requireNonNull(layoutInflater); 71 mContextMenuMaker = requireNonNull(contextMenuMaker); 72 mContext = requireNonNull(context); 73 mTermsBridge = requireNonNull(termsBridge); 74 mUtils = requireNonNull(utils); 75 mStylerHelper = requireNonNull(stylerHelper); 76 } 77 78 @Override onCreateViewHolder(ViewGroup parent, int viewType)79 public TermsViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { 80 View rootView = null; 81 switch (viewType) { 82 case VIEW_TYPE_GENERAL_DISCLAIMER: 83 rootView = createGeneralDisclaimerView(parent); 84 break; 85 case VIEW_TYPE_REGULAR_DISCLAIMER: 86 rootView = createRegularDisclaimerView(parent); 87 break; 88 } 89 return new TermsViewHolder(rootView); 90 } 91 createRegularDisclaimerView(ViewGroup parent)92 private View createRegularDisclaimerView(ViewGroup parent) { 93 View rootView = mInflater.inflate( 94 R.layout.terms_disclaimer_header, parent, /* attachToRoot= */ false); 95 mStylerHelper.applyListItemStyling(rootView, 96 new LinearLayout.LayoutParams(rootView.getLayoutParams())); 97 return rootView; 98 } 99 createGeneralDisclaimerView(ViewGroup parent)100 private View createGeneralDisclaimerView(ViewGroup parent) { 101 View rootView = mInflater.inflate( 102 R.layout.terms_disclaimer_general, parent, /* attachToRoot= */ false); 103 TextView textView = rootView.findViewById(R.id.terms_disclaimer_general); 104 DescriptionStyler.applyPartnerCustomizationHeavyStyle(textView); 105 textView.setTextAlignment(TEXT_ALIGNMENT_TEXT_START); 106 return rootView; 107 } 108 109 @Override onBindViewHolder(TermsViewHolder holder, int position)110 public void onBindViewHolder(TermsViewHolder holder, int position) { 111 int itemViewType = getItemViewType(position); 112 switch (itemViewType) { 113 case VIEW_TYPE_GENERAL_DISCLAIMER: 114 onBindGeneralDisclaimerViewHolder(holder); 115 break; 116 case VIEW_TYPE_REGULAR_DISCLAIMER: 117 onBindDisclaimerViewHolder(holder, /* disclaimerIndex= */ position - 1); 118 break; 119 } 120 } 121 onBindGeneralDisclaimerViewHolder(TermsViewHolder holder)122 private void onBindGeneralDisclaimerViewHolder(TermsViewHolder holder) { 123 holder.mGeneralDisclaimer.setText(mGenericDisclaimer.getContent()); 124 } 125 onBindDisclaimerViewHolder(TermsViewHolder holder, int disclaimerIndex)126 private void onBindDisclaimerViewHolder(TermsViewHolder holder, int disclaimerIndex) { 127 TermsDocument disclaimer = getDisclaimer(disclaimerIndex); 128 String heading = disclaimer.getHeading(); 129 setupRootView(disclaimerIndex, heading, holder); 130 setupDisclaimerHeading(heading, holder); 131 setupDisclaimerContent(disclaimer, holder); 132 setExpanded(isExpanded(disclaimerIndex), disclaimerIndex, holder); 133 } 134 135 @Override getItemViewType(int position)136 public int getItemViewType(int position) { 137 if (position == 0) { 138 return VIEW_TYPE_GENERAL_DISCLAIMER; 139 } 140 return VIEW_TYPE_REGULAR_DISCLAIMER; 141 } 142 setupRootView(int position, String heading, TermsViewHolder viewHolder)143 private void setupRootView(int position, String heading, TermsViewHolder viewHolder) { 144 viewHolder.itemView 145 .setContentDescription(mContext.getString(R.string.section_heading, heading)); 146 viewHolder.itemView.setOnClickListener(view -> 147 setExpanded(!isExpanded(position), position, viewHolder)); 148 } 149 setupDisclaimerHeading(String heading, TermsViewHolder viewHolder)150 private void setupDisclaimerHeading(String heading, TermsViewHolder viewHolder) { 151 viewHolder.mDisclaimerHeader.setText(heading); 152 } 153 setupDisclaimerContent(TermsDocument disclaimer, TermsViewHolder viewHolder)154 private void setupDisclaimerContent(TermsDocument disclaimer, TermsViewHolder viewHolder) { 155 TextView disclaimerContent = viewHolder.mDisclaimerContent; 156 TermsAdapterUtils.populateContentTextView( 157 mContext, disclaimerContent, disclaimer, new ClickableSpanFactory( 158 mUtils.getAccentColor(mContext), 159 mTermsBridge::onLinkClicked)); 160 mContextMenuMaker.registerWithActivity(disclaimerContent); 161 } 162 updateViewsForExpandedState(boolean expanded, TermsViewHolder viewHolder)163 private void updateViewsForExpandedState(boolean expanded, TermsViewHolder viewHolder) { 164 if (Flags.termsViewAccessibilityEnabled()) { 165 updateViewAccessibility(expanded, viewHolder.itemView); 166 } 167 viewHolder.mDisclaimerContentContainer.setVisibility(expanded ? View.VISIBLE : View.GONE); 168 viewHolder.mChevron.setRotation(expanded ? 90 : -90); 169 } 170 updateViewAccessibility(boolean expanded, View view)171 private void updateViewAccessibility(boolean expanded, View view) { 172 view.setStateDescription( 173 mContext.getString(expanded ? R.string.expanded : R.string.collapsed)); 174 view.setAccessibilityDelegate( 175 new AccessibilityDelegate() { 176 @Override 177 public void onInitializeAccessibilityNodeInfo(@NonNull View host, 178 @NonNull AccessibilityNodeInfo info) { 179 super.onInitializeAccessibilityNodeInfo(host, info); 180 info.addAction( 181 new AccessibilityNodeInfo.AccessibilityAction( 182 AccessibilityNodeInfo.AccessibilityAction.ACTION_CLICK.getId(), 183 mContext 184 .getResources() 185 .getString(expanded ? R.string.collapse : R.string.expand))); 186 } 187 }); 188 } 189 @Override getItemCount()190 public int getItemCount() { 191 // First item is always the general disclaimer 192 return 1 + mTermsDocuments.size(); 193 } 194 getDisclaimer(int index)195 private TermsDocument getDisclaimer(int index) { 196 return mTermsDocuments.get(index); 197 } 198 isExpanded(int position)199 private Boolean isExpanded(int position) { 200 return mTermsBridge.isTermExpanded(position); 201 } 202 setExpanded(boolean expanded, int position, TermsViewHolder viewHolder)203 private void setExpanded(boolean expanded, int position, TermsViewHolder viewHolder) { 204 mTermsBridge.onTermExpanded(position, expanded); 205 updateViewsForExpandedState(expanded, viewHolder); 206 } 207 208 /** 209 * Used for communicating with the owner of the adapter. 210 */ 211 public interface TermsBridge { 212 /** 213 * Returns whether the disclaimer at position {@code position} is expanded. 214 */ isTermExpanded(int position)215 boolean isTermExpanded(int position); 216 217 /** 218 * Expands the disclaimer at position {@code position} if {@code expanded} is {@code true}, 219 * otherwise collapses it. 220 */ onTermExpanded(int position, boolean expanded)221 void onTermExpanded(int position, boolean expanded); 222 223 /** 224 * Callback invoked when a link is tapped from the terms content text. 225 * @param intent a web intent ready to be launched. 226 */ onLinkClicked(Intent intent)227 void onLinkClicked(Intent intent); 228 } 229 230 static class TermsViewHolder extends RecyclerView.ViewHolder { 231 final TextView mGeneralDisclaimer; 232 final TextView mDisclaimerHeader; 233 final TextView mDisclaimerContent; 234 final ViewGroup mDisclaimerContentContainer; 235 final ImageView mChevron; 236 TermsViewHolder(View itemView)237 TermsViewHolder(View itemView) { 238 super(itemView); 239 mGeneralDisclaimer = itemView.findViewById(R.id.terms_disclaimer_general); 240 mDisclaimerHeader = itemView.findViewById(R.id.sud_items_title); 241 mDisclaimerContent = itemView.findViewById(R.id.disclaimer_content); 242 mDisclaimerContentContainer = itemView.findViewById(R.id.disclaimer_content_container); 243 mChevron = itemView.findViewById(R.id.chevron); 244 } 245 } 246 } 247