1 /* 2 * Copyright (C) 2018 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 17 package com.android.settings.panel; 18 19 import static android.app.slice.Slice.HINT_ERROR; 20 import static android.app.slice.SliceItem.FORMAT_SLICE; 21 import static android.view.accessibility.AccessibilityNodeInfo.ACTION_CLICK; 22 23 import android.app.settings.SettingsEnums; 24 import android.content.Context; 25 import android.net.Uri; 26 import android.view.LayoutInflater; 27 import android.view.View; 28 import android.view.ViewGroup; 29 import android.view.accessibility.AccessibilityNodeInfo; 30 import android.widget.LinearLayout; 31 32 import androidx.annotation.NonNull; 33 import androidx.annotation.VisibleForTesting; 34 import androidx.lifecycle.LiveData; 35 import androidx.recyclerview.widget.RecyclerView; 36 import androidx.slice.Slice; 37 import androidx.slice.SliceItem; 38 import androidx.slice.widget.SliceView; 39 40 import com.android.settings.R; 41 import com.android.settings.overlay.FeatureFactory; 42 43 import com.google.android.setupdesign.DividerItemDecoration; 44 45 import java.util.ArrayList; 46 import java.util.List; 47 import java.util.Map; 48 49 /** 50 * RecyclerView adapter for Slices in Settings Panels. 51 * 52 * @deprecated this is no longer used after V and will be removed. 53 */ 54 @Deprecated(forRemoval = true) 55 public class PanelSlicesAdapter 56 extends RecyclerView.Adapter<PanelSlicesAdapter.SliceRowViewHolder> { 57 58 /** 59 * Maximum number of slices allowed on the panel view. 60 */ 61 @VisibleForTesting 62 static final int MAX_NUM_OF_SLICES = 9; 63 64 private final List<LiveData<Slice>> mSliceLiveData; 65 private final int mMetricsCategory; 66 private final PanelFragment mPanelFragment; 67 PanelSlicesAdapter( PanelFragment fragment, Map<Uri, LiveData<Slice>> sliceLiveData, int metricsCategory)68 public PanelSlicesAdapter( 69 PanelFragment fragment, Map<Uri, LiveData<Slice>> sliceLiveData, int metricsCategory) { 70 mPanelFragment = fragment; 71 mSliceLiveData = new ArrayList<>(sliceLiveData.values()); 72 mMetricsCategory = metricsCategory; 73 } 74 75 @NonNull 76 @Override onCreateViewHolder(@onNull ViewGroup viewGroup, int viewType)77 public SliceRowViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int viewType) { 78 final Context context = viewGroup.getContext(); 79 final LayoutInflater inflater = LayoutInflater.from(context); 80 final View view; 81 if (viewType == PanelContent.VIEW_TYPE_SLIDER) { 82 view = inflater.inflate(R.layout.panel_slice_slider_row, viewGroup, false); 83 } else { 84 view = inflater.inflate(R.layout.panel_slice_row, viewGroup, false); 85 } 86 return new SliceRowViewHolder(view); 87 } 88 89 @Override onBindViewHolder(@onNull SliceRowViewHolder sliceRowViewHolder, int position)90 public void onBindViewHolder(@NonNull SliceRowViewHolder sliceRowViewHolder, int position) { 91 sliceRowViewHolder.onBind(mSliceLiveData.get(position).getValue()); 92 } 93 94 /** 95 * Return the number of available items in the adapter with max number of slices enforced. 96 */ 97 @Override getItemCount()98 public int getItemCount() { 99 return Math.min(mSliceLiveData.size(), MAX_NUM_OF_SLICES); 100 } 101 102 @Override getItemViewType(int position)103 public int getItemViewType(int position) { 104 return mPanelFragment.getPanelViewType(); 105 } 106 107 /** 108 * Return the available data from the adapter. If the number of Slices over the max number 109 * allowed, the list will only have the first MAX_NUM_OF_SLICES of slices. 110 */ 111 @VisibleForTesting getData()112 List<LiveData<Slice>> getData() { 113 return mSliceLiveData.subList(0, getItemCount()); 114 } 115 116 /** 117 * ViewHolder for binding Slices to SliceViews. 118 * 119 * @deprecated this is no longer used after V and will be removed. 120 */ 121 @Deprecated(forRemoval = true) 122 public class SliceRowViewHolder extends RecyclerView.ViewHolder 123 implements DividerItemDecoration.DividedViewHolder { 124 125 private static final int ROW_VIEW_ID = androidx.slice.view.R.id.row_view; 126 private static final int ROW_VIEW_TAG = R.id.tag_row_view; 127 128 @VisibleForTesting 129 final SliceView sliceView; 130 @VisibleForTesting 131 final LinearLayout mSliceSliderLayout; 132 SliceRowViewHolder(View view)133 public SliceRowViewHolder(View view) { 134 super(view); 135 sliceView = view.findViewById(R.id.slice_view); 136 sliceView.setMode(SliceView.MODE_LARGE); 137 sliceView.setShowTitleItems(true); 138 sliceView.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO); 139 mSliceSliderLayout = view.findViewById(R.id.slice_slider_layout); 140 } 141 142 /** 143 * Called when the view is displayed. 144 */ onBind(Slice slice)145 public void onBind(Slice slice) { 146 // Hides slice which reports with error hint or not contain any slice sub-item. 147 if (slice == null || !isValidSlice(slice)) { 148 updateActionLabel(); 149 sliceView.setVisibility(View.GONE); 150 return; 151 } else { 152 sliceView.setSlice(slice); 153 sliceView.setVisibility(View.VISIBLE); 154 } 155 156 // Add divider for the end icon 157 sliceView.setShowActionDividers(true); 158 159 // Log Panel interaction 160 sliceView.setOnSliceActionListener( 161 ((eventInfo, sliceItem) -> { 162 FeatureFactory.getFeatureFactory() 163 .getMetricsFeatureProvider() 164 .action(0 /* attribution */, 165 SettingsEnums.ACTION_PANEL_INTERACTION, 166 mMetricsCategory, 167 slice.getUri().getLastPathSegment() 168 /* log key */, 169 eventInfo.actionType /* value */); 170 }) 171 ); 172 updateActionLabel(); 173 } 174 175 /** 176 * Either set the action label if the row view is inflated into Slice, or set a listener to 177 * do so later when the row is available. 178 */ updateActionLabel()179 @VisibleForTesting void updateActionLabel() { 180 if (sliceView == null) { 181 return; 182 } 183 184 final LinearLayout llRow = sliceView.findViewById(ROW_VIEW_ID); 185 if (llRow != null) { 186 // Just set the label for the row. if is already laid out, there is no need for 187 // listening to future changes. 188 setActionLabel(llRow); 189 } else { // set the accessibility delegate when row_view is laid out 190 Object alreadyAddedListener = sliceView.getTag(ROW_VIEW_TAG); 191 if (alreadyAddedListener != null) { 192 return; 193 } 194 sliceView.setTag(ROW_VIEW_TAG, new Object()); 195 sliceView.addOnLayoutChangeListener(new View.OnLayoutChangeListener() { 196 @Override 197 public void onLayoutChange(View v, int left, int top, int right, int bottom, 198 int oldLeft, int oldTop, int oldRight, int oldBottom) { 199 LinearLayout row = sliceView.findViewById(ROW_VIEW_ID); 200 if (row != null) { 201 setActionLabel(row); 202 sliceView.removeOnLayoutChangeListener(this); 203 } 204 } 205 }); 206 } 207 } 208 209 /** 210 * Update the action label for TalkBack to be more specific 211 * @param view the RowView within the Slice 212 */ setActionLabel(View view)213 @VisibleForTesting void setActionLabel(View view) { 214 view.setAccessibilityDelegate(new View.AccessibilityDelegate() { 215 @Override 216 public void onInitializeAccessibilityNodeInfo(View host, 217 AccessibilityNodeInfo info) { 218 super.onInitializeAccessibilityNodeInfo(host, info); 219 220 AccessibilityNodeInfo.AccessibilityAction customClick = 221 new AccessibilityNodeInfo.AccessibilityAction(ACTION_CLICK, host 222 .getResources() 223 .getString(R.string.accessibility_action_label_panel_slice)); 224 info.addAction(customClick); 225 } 226 }); 227 } 228 isValidSlice(Slice slice)229 private boolean isValidSlice(Slice slice) { 230 if (slice.getHints().contains(HINT_ERROR)) { 231 return false; 232 } 233 for (SliceItem item : slice.getItems()) { 234 if (item.getFormat().equals(FORMAT_SLICE)) { 235 return true; 236 } 237 } 238 return false; 239 } 240 241 @Override isDividerAllowedAbove()242 public boolean isDividerAllowedAbove() { 243 return false; 244 } 245 246 @Override isDividerAllowedBelow()247 public boolean isDividerAllowedBelow() { 248 return false; 249 } 250 } 251 } 252