1 /* 2 * Copyright (C) 2015 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.documentsui.dirlist; 18 19 import android.content.Context; 20 import android.database.Cursor; 21 import android.os.Bundle; 22 import android.view.KeyEvent; 23 import android.view.LayoutInflater; 24 import android.view.MotionEvent; 25 import android.view.View; 26 import android.view.ViewGroup; 27 import android.view.ViewPropertyAnimator; 28 import android.widget.ImageView; 29 30 import androidx.core.view.accessibility.AccessibilityNodeInfoCompat; 31 import androidx.recyclerview.widget.RecyclerView; 32 33 import com.android.documentsui.base.Shared; 34 import com.android.documentsui.base.State; 35 36 import java.util.function.Function; 37 38 import javax.annotation.Nullable; 39 40 /** 41 * ViewHolder of a document item within a RecyclerView. 42 */ 43 public abstract class DocumentHolder 44 extends RecyclerView.ViewHolder implements View.OnKeyListener { 45 46 static final float DISABLED_ALPHA = 0.3f; 47 48 protected final Context mContext; 49 50 protected @Nullable String mModelId; 51 52 protected @State.ActionType int mAction; 53 54 // See #addKeyEventListener for details on the need for this field. 55 private KeyboardEventListener<DocumentItemDetails> mKeyEventListener; 56 57 private final DocumentItemDetails mDetails; 58 DocumentHolder(Context context, ViewGroup parent, int layout)59 public DocumentHolder(Context context, ViewGroup parent, int layout) { 60 this(context, inflateLayout(context, parent, layout)); 61 } 62 DocumentHolder(Context context, View item)63 public DocumentHolder(Context context, View item) { 64 super(item); 65 66 itemView.setOnKeyListener(this); 67 68 mContext = context; 69 mDetails = new DocumentItemDetails(this); 70 } 71 72 /** 73 * Binds the view to the given item data. 74 * @param cursor 75 * @param modelId 76 * @param state 77 */ bind(Cursor cursor, String modelId)78 public abstract void bind(Cursor cursor, String modelId); 79 getModelId()80 public String getModelId() { 81 return mModelId; 82 } 83 84 /** 85 * Makes the associated item view appear selected. Note that this merely affects the appearance 86 * of the view, it doesn't actually select the item. 87 * TODO: Use the DirectoryItemAnimator instead of manually controlling animation using a boolean 88 * flag. 89 * 90 * @param selected 91 * @param animate Whether or not to animate the change. Only selection changes initiated by the 92 * selection manager should be animated. See 93 * {@link ModelBackedDocumentsAdapter#onBindViewHolder(DocumentHolder, int, java.util.List)} 94 */ setSelected(boolean selected, boolean animate)95 public void setSelected(boolean selected, boolean animate) { 96 itemView.setActivated(selected); 97 itemView.setSelected(selected); 98 } 99 setEnabled(boolean enabled)100 public void setEnabled(boolean enabled) { 101 setEnabledRecursive(itemView, enabled); 102 } 103 setAction(@tate.ActionType int action)104 public void setAction(@State.ActionType int action) { 105 mAction = action; 106 } 107 bindPreviewIcon(boolean show, Function<View, Boolean> clickCallback)108 public void bindPreviewIcon(boolean show, Function<View, Boolean> clickCallback) {} 109 110 @Override onKey(View v, int keyCode, KeyEvent event)111 public boolean onKey(View v, int keyCode, KeyEvent event) { 112 assert(mKeyEventListener != null); 113 DocumentItemDetails details = getItemDetails(); 114 return (details == null) 115 ? false 116 : mKeyEventListener.onKey(details, keyCode, event); 117 } 118 119 /** 120 * Installs a delegate to receive keyboard input events. This arrangement is necessitated 121 * by the fact that a single listener cannot listen to all keyboard events 122 * on RecyclerView (our parent view). Not sure why this is, but have been 123 * assured it is the case. 124 * 125 * <p>Ideally we'd not involve DocumentHolder in propagation of events like this. 126 */ addKeyEventListener(KeyboardEventListener<DocumentItemDetails> listener)127 public void addKeyEventListener(KeyboardEventListener<DocumentItemDetails> listener) { 128 assert(mKeyEventListener == null); 129 mKeyEventListener = listener; 130 } 131 inDragRegion(MotionEvent event)132 public boolean inDragRegion(MotionEvent event) { 133 return false; 134 } 135 inSelectRegion(MotionEvent event)136 public boolean inSelectRegion(MotionEvent event) { 137 return false; 138 } 139 inPreviewIconRegion(MotionEvent event)140 public boolean inPreviewIconRegion(MotionEvent event) { 141 return false; 142 } 143 getItemDetails()144 public DocumentItemDetails getItemDetails() { 145 return mDetails; 146 } 147 setEnabledRecursive(View itemView, boolean enabled)148 static void setEnabledRecursive(View itemView, boolean enabled) { 149 if (itemView == null || itemView.isEnabled() == enabled) { 150 return; 151 } 152 itemView.setEnabled(enabled); 153 154 if (itemView instanceof ViewGroup) { 155 final ViewGroup vg = (ViewGroup) itemView; 156 for (int i = vg.getChildCount() - 1; i >= 0; i--) { 157 setEnabledRecursive(vg.getChildAt(i), enabled); 158 } 159 } 160 } 161 162 @SuppressWarnings("TypeParameterUnusedInFormals") inflateLayout(Context context, ViewGroup parent, int layout)163 private static <V extends View> V inflateLayout(Context context, ViewGroup parent, int layout) { 164 final LayoutInflater inflater = LayoutInflater.from(context); 165 return (V) inflater.inflate(layout, parent, false); 166 } 167 fade(ImageView view, float alpha)168 static ViewPropertyAnimator fade(ImageView view, float alpha) { 169 return view.animate().setDuration(Shared.CHECK_ANIMATION_DURATION).alpha(alpha); 170 } 171 172 protected static class PreviewAccessibilityDelegate extends View.AccessibilityDelegate { 173 private Function<View, Boolean> mCallback; 174 PreviewAccessibilityDelegate(Function<View, Boolean> clickCallback)175 public PreviewAccessibilityDelegate(Function<View, Boolean> clickCallback) { 176 super(); 177 mCallback = clickCallback; 178 } 179 180 @Override performAccessibilityAction(View host, int action, Bundle args)181 public boolean performAccessibilityAction(View host, int action, Bundle args) { 182 if (action == AccessibilityNodeInfoCompat.ACTION_CLICK) { 183 return mCallback.apply(host); 184 } 185 return super.performAccessibilityAction(host, action, args); 186 } 187 } 188 } 189