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 bindBriefcaseIcon(boolean show)110 public void bindBriefcaseIcon(boolean show) {} 111 112 @Override onKey(View v, int keyCode, KeyEvent event)113 public boolean onKey(View v, int keyCode, KeyEvent event) { 114 assert(mKeyEventListener != null); 115 DocumentItemDetails details = getItemDetails(); 116 return (details == null) 117 ? false 118 : mKeyEventListener.onKey(details, keyCode, event); 119 } 120 121 /** 122 * Installs a delegate to receive keyboard input events. This arrangement is necessitated 123 * by the fact that a single listener cannot listen to all keyboard events 124 * on RecyclerView (our parent view). Not sure why this is, but have been 125 * assured it is the case. 126 * 127 * <p>Ideally we'd not involve DocumentHolder in propagation of events like this. 128 */ addKeyEventListener(KeyboardEventListener<DocumentItemDetails> listener)129 public void addKeyEventListener(KeyboardEventListener<DocumentItemDetails> listener) { 130 assert(mKeyEventListener == null); 131 mKeyEventListener = listener; 132 } 133 inDragRegion(MotionEvent event)134 public boolean inDragRegion(MotionEvent event) { 135 return false; 136 } 137 inSelectRegion(MotionEvent event)138 public boolean inSelectRegion(MotionEvent event) { 139 return false; 140 } 141 inPreviewIconRegion(MotionEvent event)142 public boolean inPreviewIconRegion(MotionEvent event) { 143 return false; 144 } 145 getItemDetails()146 public DocumentItemDetails getItemDetails() { 147 return mDetails; 148 } 149 setEnabledRecursive(View itemView, boolean enabled)150 static void setEnabledRecursive(View itemView, boolean enabled) { 151 if (itemView == null || itemView.isEnabled() == enabled) { 152 return; 153 } 154 itemView.setEnabled(enabled); 155 156 if (itemView instanceof ViewGroup) { 157 final ViewGroup vg = (ViewGroup) itemView; 158 for (int i = vg.getChildCount() - 1; i >= 0; i--) { 159 setEnabledRecursive(vg.getChildAt(i), enabled); 160 } 161 } 162 } 163 164 @SuppressWarnings("TypeParameterUnusedInFormals") inflateLayout(Context context, ViewGroup parent, int layout)165 private static <V extends View> V inflateLayout(Context context, ViewGroup parent, int layout) { 166 final LayoutInflater inflater = LayoutInflater.from(context); 167 return (V) inflater.inflate(layout, parent, false); 168 } 169 fade(ImageView view, float alpha)170 static ViewPropertyAnimator fade(ImageView view, float alpha) { 171 return view.animate().setDuration(Shared.CHECK_ANIMATION_DURATION).alpha(alpha); 172 } 173 174 protected static class PreviewAccessibilityDelegate extends View.AccessibilityDelegate { 175 private Function<View, Boolean> mCallback; 176 PreviewAccessibilityDelegate(Function<View, Boolean> clickCallback)177 public PreviewAccessibilityDelegate(Function<View, Boolean> clickCallback) { 178 super(); 179 mCallback = clickCallback; 180 } 181 182 @Override performAccessibilityAction(View host, int action, Bundle args)183 public boolean performAccessibilityAction(View host, int action, Bundle args) { 184 if (action == AccessibilityNodeInfoCompat.ACTION_CLICK) { 185 return mCallback.apply(host); 186 } 187 return super.performAccessibilityAction(host, action, args); 188 } 189 } 190 } 191