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