1d839149843e0b4b481da6406718a15b544c093b0Ben Kwa/*
2d839149843e0b4b481da6406718a15b544c093b0Ben Kwa * Copyright (C) 2015 The Android Open Source Project
3d839149843e0b4b481da6406718a15b544c093b0Ben Kwa *
4d839149843e0b4b481da6406718a15b544c093b0Ben Kwa * Licensed under the Apache License, Version 2.0 (the "License");
5d839149843e0b4b481da6406718a15b544c093b0Ben Kwa * you may not use this file except in compliance with the License.
6d839149843e0b4b481da6406718a15b544c093b0Ben Kwa * You may obtain a copy of the License at
7d839149843e0b4b481da6406718a15b544c093b0Ben Kwa *
8d839149843e0b4b481da6406718a15b544c093b0Ben Kwa *      http://www.apache.org/licenses/LICENSE-2.0
9d839149843e0b4b481da6406718a15b544c093b0Ben Kwa *
10d839149843e0b4b481da6406718a15b544c093b0Ben Kwa * Unless required by applicable law or agreed to in writing, software
11d839149843e0b4b481da6406718a15b544c093b0Ben Kwa * distributed under the License is distributed on an "AS IS" BASIS,
12d839149843e0b4b481da6406718a15b544c093b0Ben Kwa * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13d839149843e0b4b481da6406718a15b544c093b0Ben Kwa * See the License for the specific language governing permissions and
14d839149843e0b4b481da6406718a15b544c093b0Ben Kwa * limitations under the License.
15d839149843e0b4b481da6406718a15b544c093b0Ben Kwa */
16d839149843e0b4b481da6406718a15b544c093b0Ben Kwa
17d839149843e0b4b481da6406718a15b544c093b0Ben Kwapackage com.android.documentsui.dirlist;
18d839149843e0b4b481da6406718a15b544c093b0Ben Kwa
19df72a432182b175b559fe0907b09d3ad10bb6a7bSteve McKayimport android.annotation.ColorInt;
20d839149843e0b4b481da6406718a15b544c093b0Ben Kwaimport android.content.Context;
21d839149843e0b4b481da6406718a15b544c093b0Ben Kwaimport android.database.Cursor;
220436a757211bd5b485ba5e484ee37082c59e1c47Ben Kwaimport android.graphics.Rect;
23d839149843e0b4b481da6406718a15b544c093b0Ben Kwaimport android.support.annotation.Nullable;
24d839149843e0b4b481da6406718a15b544c093b0Ben Kwaimport android.support.v7.widget.RecyclerView;
25d839149843e0b4b481da6406718a15b544c093b0Ben Kwaimport android.view.KeyEvent;
26d839149843e0b4b481da6406718a15b544c093b0Ben Kwaimport android.view.LayoutInflater;
270436a757211bd5b485ba5e484ee37082c59e1c47Ben Kwaimport android.view.MotionEvent;
28d839149843e0b4b481da6406718a15b544c093b0Ben Kwaimport android.view.View;
29d839149843e0b4b481da6406718a15b544c093b0Ben Kwaimport android.view.ViewGroup;
30d839149843e0b4b481da6406718a15b544c093b0Ben Kwa
310436a757211bd5b485ba5e484ee37082c59e1c47Ben Kwaimport com.android.documentsui.Events;
32d839149843e0b4b481da6406718a15b544c093b0Ben Kwaimport com.android.documentsui.R;
33d839149843e0b4b481da6406718a15b544c093b0Ben Kwaimport com.android.documentsui.State;
34d839149843e0b4b481da6406718a15b544c093b0Ben Kwa
35d839149843e0b4b481da6406718a15b544c093b0Ben Kwapublic abstract class DocumentHolder
36d839149843e0b4b481da6406718a15b544c093b0Ben Kwa        extends RecyclerView.ViewHolder
37d839149843e0b4b481da6406718a15b544c093b0Ben Kwa        implements View.OnKeyListener {
38d839149843e0b4b481da6406718a15b544c093b0Ben Kwa
39df72a432182b175b559fe0907b09d3ad10bb6a7bSteve McKay    static final float DISABLED_ALPHA = 0.3f;
40df72a432182b175b559fe0907b09d3ad10bb6a7bSteve McKay
41d839149843e0b4b481da6406718a15b544c093b0Ben Kwa    public @Nullable String modelId;
42d839149843e0b4b481da6406718a15b544c093b0Ben Kwa
43d839149843e0b4b481da6406718a15b544c093b0Ben Kwa    final Context mContext;
44df72a432182b175b559fe0907b09d3ad10bb6a7bSteve McKay    final @ColorInt int mDefaultBgColor;
45df72a432182b175b559fe0907b09d3ad10bb6a7bSteve McKay    final @ColorInt int mSelectedBgColor;
46d839149843e0b4b481da6406718a15b544c093b0Ben Kwa
470436a757211bd5b485ba5e484ee37082c59e1c47Ben Kwa    DocumentHolder.EventListener mEventListener;
48d839149843e0b4b481da6406718a15b544c093b0Ben Kwa    private View.OnKeyListener mKeyListener;
490436a757211bd5b485ba5e484ee37082c59e1c47Ben Kwa    private View mSelectionHotspot;
50d839149843e0b4b481da6406718a15b544c093b0Ben Kwa
51df72a432182b175b559fe0907b09d3ad10bb6a7bSteve McKay
52f8a51f6fcbec4dbc61bab3c5a06cd62971a4df43Ben Kwa    public DocumentHolder(Context context, ViewGroup parent, int layout) {
53f8a51f6fcbec4dbc61bab3c5a06cd62971a4df43Ben Kwa        this(context, inflateLayout(context, parent, layout));
54d839149843e0b4b481da6406718a15b544c093b0Ben Kwa    }
55d839149843e0b4b481da6406718a15b544c093b0Ben Kwa
56f8a51f6fcbec4dbc61bab3c5a06cd62971a4df43Ben Kwa    public DocumentHolder(Context context, View item) {
57d839149843e0b4b481da6406718a15b544c093b0Ben Kwa        super(item);
58d839149843e0b4b481da6406718a15b544c093b0Ben Kwa
59d839149843e0b4b481da6406718a15b544c093b0Ben Kwa        itemView.setOnKeyListener(this);
60d839149843e0b4b481da6406718a15b544c093b0Ben Kwa
61d839149843e0b4b481da6406718a15b544c093b0Ben Kwa        mContext = context;
62d839149843e0b4b481da6406718a15b544c093b0Ben Kwa
63df72a432182b175b559fe0907b09d3ad10bb6a7bSteve McKay        mDefaultBgColor = context.getColor(R.color.item_doc_background);
64df72a432182b175b559fe0907b09d3ad10bb6a7bSteve McKay        mSelectedBgColor = context.getColor(R.color.item_doc_background_selected);
650436a757211bd5b485ba5e484ee37082c59e1c47Ben Kwa
660436a757211bd5b485ba5e484ee37082c59e1c47Ben Kwa        mSelectionHotspot = itemView.findViewById(R.id.icon_check);
67d839149843e0b4b481da6406718a15b544c093b0Ben Kwa    }
68d839149843e0b4b481da6406718a15b544c093b0Ben Kwa
69d839149843e0b4b481da6406718a15b544c093b0Ben Kwa    /**
70d839149843e0b4b481da6406718a15b544c093b0Ben Kwa     * Binds the view to the given item data.
71d839149843e0b4b481da6406718a15b544c093b0Ben Kwa     * @param cursor
72d839149843e0b4b481da6406718a15b544c093b0Ben Kwa     * @param modelId
73d839149843e0b4b481da6406718a15b544c093b0Ben Kwa     * @param state
74d839149843e0b4b481da6406718a15b544c093b0Ben Kwa     */
75d839149843e0b4b481da6406718a15b544c093b0Ben Kwa    public abstract void bind(Cursor cursor, String modelId, State state);
76d839149843e0b4b481da6406718a15b544c093b0Ben Kwa
77e3dfebf8464e8608ff790cca67609aff7d8c2820Ben Kwa    /**
78e3dfebf8464e8608ff790cca67609aff7d8c2820Ben Kwa     * Makes the associated item view appear selected. Note that this merely affects the appearance
79e3dfebf8464e8608ff790cca67609aff7d8c2820Ben Kwa     * of the view, it doesn't actually select the item.
80a3c09b4b9b26ca5eedaac65cafad3efba18502cbBen Kwa     * TODO: Use the DirectoryItemAnimator instead of manually controlling animation using a boolean
81a3c09b4b9b26ca5eedaac65cafad3efba18502cbBen Kwa     * flag.
82e3dfebf8464e8608ff790cca67609aff7d8c2820Ben Kwa     *
83e3dfebf8464e8608ff790cca67609aff7d8c2820Ben Kwa     * @param selected
84a3c09b4b9b26ca5eedaac65cafad3efba18502cbBen Kwa     * @param animate Whether or not to animate the change. Only selection changes initiated by the
85a3c09b4b9b26ca5eedaac65cafad3efba18502cbBen Kwa     *            selection manager should be animated. See
86a3c09b4b9b26ca5eedaac65cafad3efba18502cbBen Kwa     *            {@link ModelBackedDocumentsAdapter#onBindViewHolder(DocumentHolder, int, java.util.List)}
87e3dfebf8464e8608ff790cca67609aff7d8c2820Ben Kwa     */
88a3c09b4b9b26ca5eedaac65cafad3efba18502cbBen Kwa    public void setSelected(boolean selected, boolean animate) {
89a3c09b4b9b26ca5eedaac65cafad3efba18502cbBen Kwa        // Note: the animate param doesn't apply for this base implementation, because the
90a3c09b4b9b26ca5eedaac65cafad3efba18502cbBen Kwa        // DirectoryItemAnimator takes care of it. It's required by subclasses, which perform their
91a3c09b4b9b26ca5eedaac65cafad3efba18502cbBen Kwa        // own animation.
92d839149843e0b4b481da6406718a15b544c093b0Ben Kwa        itemView.setActivated(selected);
93df72a432182b175b559fe0907b09d3ad10bb6a7bSteve McKay        itemView.setBackgroundColor(selected ? mSelectedBgColor : mDefaultBgColor);
94d839149843e0b4b481da6406718a15b544c093b0Ben Kwa    }
95d839149843e0b4b481da6406718a15b544c093b0Ben Kwa
96e3dfebf8464e8608ff790cca67609aff7d8c2820Ben Kwa    /**
97e3dfebf8464e8608ff790cca67609aff7d8c2820Ben Kwa     * Highlights the associated item view.
98e3dfebf8464e8608ff790cca67609aff7d8c2820Ben Kwa     * @param highlighted
99e3dfebf8464e8608ff790cca67609aff7d8c2820Ben Kwa     */
100e3dfebf8464e8608ff790cca67609aff7d8c2820Ben Kwa    public void setHighlighted(boolean highlighted) {
101df72a432182b175b559fe0907b09d3ad10bb6a7bSteve McKay        itemView.setBackgroundColor(highlighted ? mSelectedBgColor : mDefaultBgColor);
102df72a432182b175b559fe0907b09d3ad10bb6a7bSteve McKay    }
103df72a432182b175b559fe0907b09d3ad10bb6a7bSteve McKay
104df72a432182b175b559fe0907b09d3ad10bb6a7bSteve McKay    public void setEnabled(boolean enabled) {
105df72a432182b175b559fe0907b09d3ad10bb6a7bSteve McKay        setEnabledRecursive(itemView, enabled);
106e3dfebf8464e8608ff790cca67609aff7d8c2820Ben Kwa    }
107e3dfebf8464e8608ff790cca67609aff7d8c2820Ben Kwa
108d839149843e0b4b481da6406718a15b544c093b0Ben Kwa    @Override
109d839149843e0b4b481da6406718a15b544c093b0Ben Kwa    public boolean onKey(View v, int keyCode, KeyEvent event) {
1100436a757211bd5b485ba5e484ee37082c59e1c47Ben Kwa        // Event listener should always be set.
111a1f7680f535a30aa816d129c072870031c8a2eb6Steve McKay        assert(mEventListener != null);
112a1f7680f535a30aa816d129c072870031c8a2eb6Steve McKay
1136792489dc490eb029469f73490a560c76e84dc98Ben Kwa        return mEventListener.onKey(this,  keyCode,  event);
114d839149843e0b4b481da6406718a15b544c093b0Ben Kwa    }
115d839149843e0b4b481da6406718a15b544c093b0Ben Kwa
1160436a757211bd5b485ba5e484ee37082c59e1c47Ben Kwa    public void addEventListener(DocumentHolder.EventListener listener) {
117d839149843e0b4b481da6406718a15b544c093b0Ben Kwa        // Just handle one for now; switch to a list if necessary.
118a1f7680f535a30aa816d129c072870031c8a2eb6Steve McKay        assert(mEventListener == null);
1190436a757211bd5b485ba5e484ee37082c59e1c47Ben Kwa        mEventListener = listener;
120d839149843e0b4b481da6406718a15b544c093b0Ben Kwa    }
121d839149843e0b4b481da6406718a15b544c093b0Ben Kwa
122d839149843e0b4b481da6406718a15b544c093b0Ben Kwa    public void addOnKeyListener(View.OnKeyListener listener) {
123d839149843e0b4b481da6406718a15b544c093b0Ben Kwa        // Just handle one for now; switch to a list if necessary.
124a1f7680f535a30aa816d129c072870031c8a2eb6Steve McKay        assert(mKeyListener == null);
125d839149843e0b4b481da6406718a15b544c093b0Ben Kwa        mKeyListener = listener;
126d839149843e0b4b481da6406718a15b544c093b0Ben Kwa    }
127d839149843e0b4b481da6406718a15b544c093b0Ben Kwa
1280436a757211bd5b485ba5e484ee37082c59e1c47Ben Kwa    public boolean onSingleTapUp(MotionEvent event) {
1290436a757211bd5b485ba5e484ee37082c59e1c47Ben Kwa        if (Events.isMouseEvent(event)) {
1300436a757211bd5b485ba5e484ee37082c59e1c47Ben Kwa            // Mouse clicks select.
1310436a757211bd5b485ba5e484ee37082c59e1c47Ben Kwa            // TODO:  && input.isPrimaryButtonPressed(), but it is returning false.
1320436a757211bd5b485ba5e484ee37082c59e1c47Ben Kwa            if (mEventListener != null) {
1330436a757211bd5b485ba5e484ee37082c59e1c47Ben Kwa                return mEventListener.onSelect(this);
1340436a757211bd5b485ba5e484ee37082c59e1c47Ben Kwa            }
1350436a757211bd5b485ba5e484ee37082c59e1c47Ben Kwa        } else if (Events.isTouchEvent(event)) {
1360436a757211bd5b485ba5e484ee37082c59e1c47Ben Kwa            // Touch events select if they occur in the selection hotspot, otherwise they activate.
1370436a757211bd5b485ba5e484ee37082c59e1c47Ben Kwa            if (mEventListener == null) {
1380436a757211bd5b485ba5e484ee37082c59e1c47Ben Kwa                return false;
1390436a757211bd5b485ba5e484ee37082c59e1c47Ben Kwa            }
1400436a757211bd5b485ba5e484ee37082c59e1c47Ben Kwa
1410436a757211bd5b485ba5e484ee37082c59e1c47Ben Kwa            // Do everything in global coordinates - it makes things simpler.
1423871fd16d026d7f37c617b999806636fa4b90e47Tomasz Mikolajewski            int[] coords = new int[2];
1433871fd16d026d7f37c617b999806636fa4b90e47Tomasz Mikolajewski            mSelectionHotspot.getLocationOnScreen(coords);
1443871fd16d026d7f37c617b999806636fa4b90e47Tomasz Mikolajewski            Rect rect = new Rect(coords[0], coords[1], coords[0] + mSelectionHotspot.getWidth(),
1453871fd16d026d7f37c617b999806636fa4b90e47Tomasz Mikolajewski                    coords[1] + mSelectionHotspot.getHeight());
1460436a757211bd5b485ba5e484ee37082c59e1c47Ben Kwa
1470436a757211bd5b485ba5e484ee37082c59e1c47Ben Kwa            // If the tap occurred within the icon rect, consider it a selection.
1483871fd16d026d7f37c617b999806636fa4b90e47Tomasz Mikolajewski            if (rect.contains((int) event.getRawX(), (int) event.getRawY())) {
1490436a757211bd5b485ba5e484ee37082c59e1c47Ben Kwa                return mEventListener.onSelect(this);
1500436a757211bd5b485ba5e484ee37082c59e1c47Ben Kwa            } else {
1510436a757211bd5b485ba5e484ee37082c59e1c47Ben Kwa                return mEventListener.onActivate(this);
1520436a757211bd5b485ba5e484ee37082c59e1c47Ben Kwa            }
1530436a757211bd5b485ba5e484ee37082c59e1c47Ben Kwa        }
1540436a757211bd5b485ba5e484ee37082c59e1c47Ben Kwa        return false;
1550436a757211bd5b485ba5e484ee37082c59e1c47Ben Kwa    }
1560436a757211bd5b485ba5e484ee37082c59e1c47Ben Kwa
157d839149843e0b4b481da6406718a15b544c093b0Ben Kwa    static void setEnabledRecursive(View itemView, boolean enabled) {
158d839149843e0b4b481da6406718a15b544c093b0Ben Kwa        if (itemView == null) return;
159d839149843e0b4b481da6406718a15b544c093b0Ben Kwa        if (itemView.isEnabled() == enabled) return;
160d839149843e0b4b481da6406718a15b544c093b0Ben Kwa        itemView.setEnabled(enabled);
161d839149843e0b4b481da6406718a15b544c093b0Ben Kwa
162d839149843e0b4b481da6406718a15b544c093b0Ben Kwa        if (itemView instanceof ViewGroup) {
163d839149843e0b4b481da6406718a15b544c093b0Ben Kwa            final ViewGroup vg = (ViewGroup) itemView;
164d839149843e0b4b481da6406718a15b544c093b0Ben Kwa            for (int i = vg.getChildCount() - 1; i >= 0; i--) {
165d839149843e0b4b481da6406718a15b544c093b0Ben Kwa                setEnabledRecursive(vg.getChildAt(i), enabled);
166d839149843e0b4b481da6406718a15b544c093b0Ben Kwa            }
167d839149843e0b4b481da6406718a15b544c093b0Ben Kwa        }
168d839149843e0b4b481da6406718a15b544c093b0Ben Kwa    }
169d839149843e0b4b481da6406718a15b544c093b0Ben Kwa
170d839149843e0b4b481da6406718a15b544c093b0Ben Kwa    private static View inflateLayout(Context context, ViewGroup parent, int layout) {
171d839149843e0b4b481da6406718a15b544c093b0Ben Kwa        final LayoutInflater inflater = LayoutInflater.from(context);
172d839149843e0b4b481da6406718a15b544c093b0Ben Kwa        return inflater.inflate(layout, parent, false);
173d839149843e0b4b481da6406718a15b544c093b0Ben Kwa    }
174d839149843e0b4b481da6406718a15b544c093b0Ben Kwa
1750436a757211bd5b485ba5e484ee37082c59e1c47Ben Kwa    /**
1760436a757211bd5b485ba5e484ee37082c59e1c47Ben Kwa     * Implement this in order to be able to respond to events coming from DocumentHolders.
1770436a757211bd5b485ba5e484ee37082c59e1c47Ben Kwa     */
1780436a757211bd5b485ba5e484ee37082c59e1c47Ben Kwa    interface EventListener {
1790436a757211bd5b485ba5e484ee37082c59e1c47Ben Kwa        /**
1806792489dc490eb029469f73490a560c76e84dc98Ben Kwa         * Handles activation events on the document holder.
1816792489dc490eb029469f73490a560c76e84dc98Ben Kwa         *
1820436a757211bd5b485ba5e484ee37082c59e1c47Ben Kwa         * @param doc The target DocumentHolder
1830436a757211bd5b485ba5e484ee37082c59e1c47Ben Kwa         * @return Whether the event was handled.
1840436a757211bd5b485ba5e484ee37082c59e1c47Ben Kwa         */
1850436a757211bd5b485ba5e484ee37082c59e1c47Ben Kwa        public boolean onActivate(DocumentHolder doc);
1860436a757211bd5b485ba5e484ee37082c59e1c47Ben Kwa
1870436a757211bd5b485ba5e484ee37082c59e1c47Ben Kwa        /**
1886792489dc490eb029469f73490a560c76e84dc98Ben Kwa         * Handles selection events on the document holder.
1896792489dc490eb029469f73490a560c76e84dc98Ben Kwa         *
1900436a757211bd5b485ba5e484ee37082c59e1c47Ben Kwa         * @param doc The target DocumentHolder
1910436a757211bd5b485ba5e484ee37082c59e1c47Ben Kwa         * @return Whether the event was handled.
1920436a757211bd5b485ba5e484ee37082c59e1c47Ben Kwa         */
1930436a757211bd5b485ba5e484ee37082c59e1c47Ben Kwa        public boolean onSelect(DocumentHolder doc);
1946792489dc490eb029469f73490a560c76e84dc98Ben Kwa
1956792489dc490eb029469f73490a560c76e84dc98Ben Kwa        /**
1966792489dc490eb029469f73490a560c76e84dc98Ben Kwa         * Handles key events on the document holder.
1976792489dc490eb029469f73490a560c76e84dc98Ben Kwa         *
1986792489dc490eb029469f73490a560c76e84dc98Ben Kwa         * @param doc The target DocumentHolder.
1996792489dc490eb029469f73490a560c76e84dc98Ben Kwa         * @param keyCode Key code for the event.
2006792489dc490eb029469f73490a560c76e84dc98Ben Kwa         * @param event KeyEvent for the event.
2016792489dc490eb029469f73490a560c76e84dc98Ben Kwa         * @return Whether the event was handled.
2026792489dc490eb029469f73490a560c76e84dc98Ben Kwa         */
2036792489dc490eb029469f73490a560c76e84dc98Ben Kwa        public boolean onKey(DocumentHolder doc, int keyCode, KeyEvent event);
204d839149843e0b4b481da6406718a15b544c093b0Ben Kwa    }
205d839149843e0b4b481da6406718a15b544c093b0Ben Kwa}
206