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 22 import android.app.settings.SettingsEnums; 23 import android.content.Context; 24 import android.net.Uri; 25 import android.view.LayoutInflater; 26 import android.view.View; 27 import android.view.ViewGroup; 28 import android.widget.LinearLayout; 29 30 import androidx.annotation.NonNull; 31 import androidx.annotation.VisibleForTesting; 32 import androidx.lifecycle.LiveData; 33 import androidx.recyclerview.widget.RecyclerView; 34 import androidx.slice.Slice; 35 import androidx.slice.SliceItem; 36 import androidx.slice.widget.SliceView; 37 38 import com.android.settings.R; 39 import com.android.settings.overlay.FeatureFactory; 40 41 import com.google.android.setupdesign.DividerItemDecoration; 42 43 import java.util.ArrayList; 44 import java.util.List; 45 import java.util.Map; 46 47 /** 48 * RecyclerView adapter for Slices in Settings Panels. 49 */ 50 public class PanelSlicesAdapter 51 extends RecyclerView.Adapter<PanelSlicesAdapter.SliceRowViewHolder> { 52 53 /** 54 * Maximum number of slices allowed on the panel view. 55 */ 56 @VisibleForTesting 57 static final int MAX_NUM_OF_SLICES = 6; 58 59 private final List<LiveData<Slice>> mSliceLiveData; 60 private final int mMetricsCategory; 61 private final PanelFragment mPanelFragment; 62 PanelSlicesAdapter( PanelFragment fragment, Map<Uri, LiveData<Slice>> sliceLiveData, int metricsCategory)63 public PanelSlicesAdapter( 64 PanelFragment fragment, Map<Uri, LiveData<Slice>> sliceLiveData, int metricsCategory) { 65 mPanelFragment = fragment; 66 mSliceLiveData = new ArrayList<>(sliceLiveData.values()); 67 mMetricsCategory = metricsCategory; 68 } 69 70 @NonNull 71 @Override onCreateViewHolder(@onNull ViewGroup viewGroup, int viewType)72 public SliceRowViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int viewType) { 73 final Context context = viewGroup.getContext(); 74 final LayoutInflater inflater = LayoutInflater.from(context); 75 View view; 76 if (viewType == PanelContent.VIEW_TYPE_SLIDER) { 77 view = inflater.inflate(R.layout.panel_slice_slider_row, viewGroup, false); 78 } else { 79 view = inflater.inflate(R.layout.panel_slice_row, viewGroup, false); 80 } 81 82 return new SliceRowViewHolder(view); 83 } 84 85 @Override onBindViewHolder(@onNull SliceRowViewHolder sliceRowViewHolder, int position)86 public void onBindViewHolder(@NonNull SliceRowViewHolder sliceRowViewHolder, int position) { 87 sliceRowViewHolder.onBind(mSliceLiveData.get(position), position); 88 } 89 90 /** 91 * Return the number of available items in the adapter with max number of slices enforced. 92 */ 93 @Override getItemCount()94 public int getItemCount() { 95 return Math.min(mSliceLiveData.size(), MAX_NUM_OF_SLICES); 96 } 97 98 @Override getItemViewType(int position)99 public int getItemViewType(int position) { 100 return mPanelFragment.getPanelViewType(); 101 } 102 103 /** 104 * Return the available data from the adapter. If the number of Slices over the max number 105 * allowed, the list will only have the first MAX_NUM_OF_SLICES of slices. 106 */ 107 @VisibleForTesting getData()108 List<LiveData<Slice>> getData() { 109 return mSliceLiveData.subList(0, getItemCount()); 110 } 111 112 /** 113 * ViewHolder for binding Slices to SliceViews. 114 */ 115 public class SliceRowViewHolder extends RecyclerView.ViewHolder 116 implements DividerItemDecoration.DividedViewHolder { 117 118 @VisibleForTesting 119 final SliceView sliceView; 120 @VisibleForTesting 121 final LinearLayout mSliceSliderLayout; 122 SliceRowViewHolder(View view)123 public SliceRowViewHolder(View view) { 124 super(view); 125 sliceView = view.findViewById(R.id.slice_view); 126 sliceView.setMode(SliceView.MODE_LARGE); 127 sliceView.setShowTitleItems(true); 128 sliceView.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO); 129 mSliceSliderLayout = view.findViewById(R.id.slice_slider_layout); 130 } 131 132 /** 133 * Called when the view is displayed. 134 */ onBind(LiveData<Slice> sliceLiveData, int position)135 public void onBind(LiveData<Slice> sliceLiveData, int position) { 136 sliceLiveData.observe(mPanelFragment.getViewLifecycleOwner(), sliceView); 137 138 // Do not show the divider above media devices switcher slice per request 139 final Slice slice = sliceLiveData.getValue(); 140 141 // Hides slice which reports with error hint or not contain any slice sub-item. 142 if (slice == null || !isValidSlice(slice)) { 143 sliceView.setVisibility(View.GONE); 144 } 145 146 // Log Panel interaction 147 sliceView.setOnSliceActionListener( 148 ((eventInfo, sliceItem) -> { 149 FeatureFactory.getFactory(sliceView.getContext()) 150 .getMetricsFeatureProvider() 151 .action(0 /* attribution */, 152 SettingsEnums.ACTION_PANEL_INTERACTION, 153 mMetricsCategory, 154 sliceLiveData.getValue().getUri().getLastPathSegment() 155 /* log key */, 156 eventInfo.actionType /* value */); 157 }) 158 ); 159 } 160 isValidSlice(Slice slice)161 private boolean isValidSlice(Slice slice) { 162 if (slice.getHints().contains(HINT_ERROR)) { 163 return false; 164 } 165 for (SliceItem item : slice.getItems()) { 166 if (item.getFormat().equals(FORMAT_SLICE)) { 167 return true; 168 } 169 } 170 return false; 171 } 172 173 @Override isDividerAllowedAbove()174 public boolean isDividerAllowedAbove() { 175 return false; 176 } 177 178 @Override isDividerAllowedBelow()179 public boolean isDividerAllowedBelow() { 180 return false; 181 } 182 } 183 } 184