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 
18 package com.android.mail.browse;
19 
20 import android.content.Context;
21 import android.view.Gravity;
22 import android.view.LayoutInflater;
23 import android.view.View;
24 import android.view.ViewGroup;
25 import android.widget.Adapter;
26 import android.widget.CursorAdapter;
27 
28 import com.android.mail.browse.ConversationViewAdapter.ConversationViewType;
29 import com.android.mail.ui.ConversationViewFragment;
30 import com.android.mail.utils.LogUtils;
31 
32 public 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      */
getType()47     public abstract @ConversationViewType int getType();
48 
49     /**
50      * Inflate and perform one-time initialization on a view for later binding.
51      */
createView(Context context, LayoutInflater inflater, ViewGroup parent)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      */
bindView(View v, boolean measureOnly)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      */
isContiguous()69     public abstract boolean isContiguous();
70 
getOnKeyListener()71     public View.OnKeyListener getOnKeyListener() {
72         return null;
73     }
74 
75     /**
76      * Returns true if this overlay view is in its expanded state.
77      */
isExpanded()78     public boolean isExpanded() {
79         return true;
80     }
81 
getGravity()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      */
getHeight()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      */
setHeight(int h)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 
getTop()116     public int getTop() {
117         return mTop;
118     }
119 
setTop(int top)120     public void setTop(int top) {
121         mTop = top;
122     }
123 
isMeasurementValid()124     public boolean isMeasurementValid() {
125         return !mNeedsMeasure;
126     }
127 
markMeasurementValid()128     public void markMeasurementValid() {
129         mNeedsMeasure = false;
130     }
131 
invalidateMeasurement()132     public void invalidateMeasurement() {
133         mNeedsMeasure = true;
134     }
135 
canBecomeSnapHeader()136     public boolean canBecomeSnapHeader() {
137         return false;
138     }
139 
canPushSnapHeader()140     public boolean canPushSnapHeader() {
141         return false;
142     }
143 
belongsToMessage(ConversationMessage message)144     public boolean belongsToMessage(ConversationMessage message) {
145         return false;
146     }
147 
setMessage(ConversationMessage message)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      */
onModelUpdated(View v)156     public void onModelUpdated(View v) {
157     }
158 
setPosition(int position)159     public void setPosition(int position) {
160         mPosition = position;
161     }
162 
getPosition()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      */
rebindView(View view)190     public void rebindView(View view) {
191         // DO NOTHING
192     }
193 
getFocusableView()194     public View getFocusableView() {
195         // Focus the root view by default
196         return mRootView;
197     }
198 
registerOnKeyListeners(View... views)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