1/*
2 * Copyright (C) 2012 Google Inc.
3 * Licensed to The Android Open Source Project.
4 *
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
8 *
9 *      http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17
18package com.android.mail.browse;
19
20import android.content.Context;
21import android.view.Gravity;
22import android.view.LayoutInflater;
23import android.view.View;
24import android.view.ViewGroup;
25import android.widget.Adapter;
26import android.widget.CursorAdapter;
27
28import com.android.mail.browse.ConversationViewAdapter.ConversationViewType;
29import com.android.mail.ui.ConversationViewFragment;
30import com.android.mail.utils.LogUtils;
31
32public abstract class ConversationOverlayItem {
33    private int mHeight;  // in px
34    private int mTop;  // in px
35    private boolean mNeedsMeasure;
36
37    public static final String LOG_TAG = ConversationViewFragment.LAYOUT_TAG;
38
39    private int mPosition;
40
41    // The view to focus when this overlay item should be focused.
42    protected View mRootView;
43
44    /**
45     * @see Adapter#getItemViewType(int)
46     */
47    public abstract @ConversationViewType int getType();
48
49    /**
50     * Inflate and perform one-time initialization on a view for later binding.
51     */
52    public abstract View createView(Context context, LayoutInflater inflater,
53            ViewGroup parent);
54
55    /**
56     * @see CursorAdapter#bindView(View, Context, android.database.Cursor)
57     * @param v a view to bind to
58     * @param measureOnly true iff we are binding this view only to measure its height (so items
59     * know they can cut certain corners that do not affect a view's height)
60     */
61    public abstract void bindView(View v, boolean measureOnly);
62
63    /**
64     * Returns true if this overlay view is meant to be positioned right on top of the overlay
65     * below. This special positioning allows {@link ConversationContainer} to stack overlays
66     * together even when zoomed into a conversation, when the overlay spacers spread farther
67     * apart.
68     */
69    public abstract boolean isContiguous();
70
71    public View.OnKeyListener getOnKeyListener() {
72        return null;
73    }
74
75    /**
76     * Returns true if this overlay view is in its expanded state.
77     */
78    public boolean isExpanded() {
79        return true;
80    }
81
82    public int getGravity() {
83        return Gravity.BOTTOM;
84    }
85
86    /**
87     * This method's behavior is critical and requires some 'splainin.
88     * <p>
89     * Subclasses that return a zero-size height to the {@link ConversationContainer} will
90     * cause the scrolling/recycling logic there to remove any matching view from the container.
91     * The item should switch to returning a non-zero height when its view should re-appear.
92     * <p>
93     * It's imperative that this method stay in sync with the current height of the HTML spacer
94     * that matches this overlay.
95     */
96    public int getHeight() {
97        return mHeight;
98    }
99
100    /**
101     * Set a new height.
102     *
103     * @param h a new height
104     * @return true if the value changed
105     */
106    public boolean setHeight(int h) {
107        LogUtils.i(LOG_TAG, "IN setHeight=%dpx of overlay item: %s", h, this);
108        if (mHeight != h) {
109            mHeight = h;
110            mNeedsMeasure = true;
111            return true;
112        }
113        return false;
114    }
115
116    public int getTop() {
117        return mTop;
118    }
119
120    public void setTop(int top) {
121        mTop = top;
122    }
123
124    public boolean isMeasurementValid() {
125        return !mNeedsMeasure;
126    }
127
128    public void markMeasurementValid() {
129        mNeedsMeasure = false;
130    }
131
132    public void invalidateMeasurement() {
133        mNeedsMeasure = true;
134    }
135
136    public boolean canBecomeSnapHeader() {
137        return false;
138    }
139
140    public boolean canPushSnapHeader() {
141        return false;
142    }
143
144    public boolean belongsToMessage(ConversationMessage message) {
145        return false;
146    }
147
148    public void setMessage(ConversationMessage message) {
149    }
150
151    /**
152     * Given a view that is already bound to this item, force the view to re-render the item's
153     * current model data. This is typically called after a data model update, to update the
154     * affected view in-place.
155     */
156    public void onModelUpdated(View v) {
157    }
158
159    public void setPosition(int position) {
160        mPosition = position;
161    }
162
163    public int getPosition() {
164        return mPosition;
165    }
166
167    /**
168     * This is a hack. Now that one view can update the
169     * state of another view, we need a mechanism when the
170     * view's associated item changes to update the state of the
171     * view. Typically, classes that override this class should not
172     * override this method.<br><br>
173     *
174     * This method is used by
175     * {@link com.android.mail.browse.ConversationViewAdapter.BorderItem}
176     * to update the height of the border based on whether the neighboring messages
177     * are collapsed or expanded.<br><br>
178     *
179     * It is also used by {@link com.android.mail.browse.ConversationViewAdapter.MessageHeaderItem}
180     * in the case where the snap header is tapped to collapse the message but the
181     * message header is still on screen. Since the message header is still on screen,
182     * it does not get bound but will get a rebind.<br><br>
183     *
184     * The only other way to handle this case would be to call
185     * {@link com.android.mail.browse.ConversationViewAdapter#notifyDataSetChanged()}
186     * but that makes the entire screen flicker since the entire adapter performs
187     * a layout of the every item.
188     * @param view the view to be re-bound
189     */
190    public void rebindView(View view) {
191        // DO NOTHING
192    }
193
194    public View getFocusableView() {
195        // Focus the root view by default
196        return mRootView;
197    }
198
199    public void registerOnKeyListeners(View... views) {
200        final View.OnKeyListener listener = getOnKeyListener();
201        if (listener != null) {
202            for (View v : views) {
203                if (v != null) {
204                    v.setOnKeyListener(listener);
205                }
206            }
207        }
208    }
209}
210