1/*
2 * Copyright (C) 2011 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
18package com.android.email.activity;
19
20import android.content.Context;
21import android.content.res.Resources;
22import android.graphics.Typeface;
23import android.text.TextPaint;
24import android.util.SparseArray;
25import android.view.LayoutInflater;
26import android.view.View;
27import android.view.View.MeasureSpec;
28import android.view.ViewGroup;
29import android.view.ViewParent;
30import android.widget.TextView;
31
32import com.android.email.R;
33
34/**
35 * Represents the coordinates of elements inside a CanvasConversationHeaderView
36 * (eg, checkmark, star, subject, sender, labels, etc.) It will inflate a view,
37 * and record the coordinates of each element after layout. This will allows us
38 * to easily improve performance by creating custom view while still defining
39 * layout in XML files.
40 */
41public class MessageListItemCoordinates {
42    // Modes.
43    public static final int WIDE_MODE = 0;
44    public static final int NORMAL_MODE = 1;
45
46    // Static threshold.
47    private static int MINIMUM_WIDTH_WIDE_MODE = -1;
48    private static int[] SUBJECT_LENGTHS;
49
50    // Checkmark.
51    int checkmarkX;
52    int checkmarkY;
53    int checkmarkWidthIncludingMargins;
54
55    // Reply and forward state.
56    int stateX;
57    int stateY;
58
59    // Star.
60    int starX;
61    int starY;
62
63    // Senders.
64    int sendersX;
65    int sendersY;
66    int sendersWidth;
67    int sendersLineCount;
68    int sendersFontSize;
69    int sendersAscent;
70
71    // Subject.
72    int subjectX;
73    int subjectY;
74    int subjectWidth;
75    int subjectLineCount;
76    int subjectFontSize;
77    int subjectAscent;
78
79    // Color chip.
80    int chipX;
81    int chipY;
82    int chipWidth;
83    int chipHeight;
84
85    // Date.
86    int dateXEnd;
87    int dateY;
88    int dateFontSize;
89    int dateAscent;
90
91    // Paperclip.
92    int paperclipY;
93
94    // Cache to save Coordinates based on view width.
95    private static SparseArray<MessageListItemCoordinates> mCache =
96            new SparseArray<MessageListItemCoordinates>();
97
98    private static TextPaint sPaint = new TextPaint();
99
100    static {
101        sPaint.setTypeface(Typeface.DEFAULT);
102        sPaint.setAntiAlias(true);
103    }
104
105    // Not directly instantiable.
106    private MessageListItemCoordinates() {}
107
108    /**
109     * Returns the mode of the header view (Wide/Normal/Narrow) given the its
110     * measured width.
111     */
112    public static int getMode(Context context, int width) {
113        Resources res = context.getResources();
114        if (MINIMUM_WIDTH_WIDE_MODE <= 0) {
115            MINIMUM_WIDTH_WIDE_MODE = res.getDimensionPixelSize(R.dimen.minimum_width_wide_mode);
116        }
117
118        // Choose the correct mode based on view width.
119        int mode = NORMAL_MODE;
120        if (width > MINIMUM_WIDTH_WIDE_MODE) {
121            mode = WIDE_MODE;
122        }
123        return mode;
124    }
125
126    public static boolean isMultiPane(Context context) {
127        return UiUtilities.useTwoPane(context);
128    }
129
130    /**
131     * Returns the layout id to be inflated in this mode.
132     */
133    private static int getLayoutId(int mode) {
134        switch (mode) {
135            case WIDE_MODE:
136                return R.layout.message_list_item_wide;
137            case NORMAL_MODE:
138                return R.layout.message_list_item_normal;
139            default:
140                throw new IllegalArgumentException("Unknown conversation header view mode " + mode);
141        }
142    }
143
144    /**
145     * Returns a value array multiplied by the specified density.
146     */
147    public static int[] getDensityDependentArray(int[] values, float density) {
148        int result[] = new int[values.length];
149        for (int i = 0; i < values.length; ++i) {
150            result[i] = (int) (values[i] * density);
151        }
152        return result;
153    }
154
155    /**
156     * Returns the height of the view in this mode.
157     */
158    public static int getHeight(Context context, int mode) {
159        return context.getResources().getDimensionPixelSize(
160                (mode == WIDE_MODE)
161                        ? R.dimen.message_list_item_height_wide
162                        : R.dimen.message_list_item_height_normal);
163    }
164
165    /**
166     * Returns the x coordinates of a view by tracing up its hierarchy.
167     */
168    private static int getX(View view) {
169        int x = 0;
170        while (view != null) {
171            x += (int) view.getX();
172            ViewParent parent = view.getParent();
173            view = parent != null ? (View) parent : null;
174        }
175        return x;
176    }
177
178    /**
179     * Returns the y coordinates of a view by tracing up its hierarchy.
180     */
181    private static int getY(View view) {
182        int y = 0;
183        while (view != null) {
184            y += (int) view.getY();
185            ViewParent parent = view.getParent();
186            view = parent != null ? (View) parent : null;
187        }
188        return y;
189    }
190
191    /**
192     * Returns the width of a view.
193     *
194     * @param includeMargins whether or not to include margins when calculating
195     *            width.
196     */
197    public static int getWidth(View view, boolean includeMargins) {
198        ViewGroup.MarginLayoutParams params = (ViewGroup.MarginLayoutParams) view.getLayoutParams();
199        return view.getWidth() + (includeMargins ? params.leftMargin + params.rightMargin : 0);
200    }
201
202    /**
203     * Returns the height of a view.
204     *
205     * @param includeMargins whether or not to include margins when calculating
206     *            height.
207     */
208    public static int getHeight(View view, boolean includeMargins) {
209        ViewGroup.MarginLayoutParams params = (ViewGroup.MarginLayoutParams) view.getLayoutParams();
210        return view.getHeight() + (includeMargins ? params.topMargin + params.bottomMargin : 0);
211    }
212
213    /**
214     * Returns the number of lines of this text view.
215     */
216    private static int getLineCount(TextView textView) {
217        return textView.getHeight() / textView.getLineHeight();
218    }
219
220    /**
221     * Returns the length (maximum of characters) of subject in this mode.
222     */
223    public static int getSubjectLength(Context context, int mode) {
224        Resources res = context.getResources();
225        if (SUBJECT_LENGTHS == null) {
226            SUBJECT_LENGTHS = res.getIntArray(R.array.subject_lengths);
227        }
228        return SUBJECT_LENGTHS[mode];
229    }
230
231    /**
232     * Reset the caches associated with the coordinate layouts.
233     */
234    static void resetCaches() {
235        mCache.clear();
236    }
237
238    /**
239     * Returns coordinates for elements inside a conversation header view given
240     * the view width.
241     */
242    public static MessageListItemCoordinates forWidth(Context context, int width) {
243        MessageListItemCoordinates coordinates = mCache.get(width);
244        if (coordinates == null) {
245            coordinates = new MessageListItemCoordinates();
246            mCache.put(width, coordinates);
247            // TODO: make the field computation done inside of the constructor and mark fields final
248
249            // Layout the appropriate view.
250            int mode = getMode(context, width);
251            int height = getHeight(context, mode);
252            View view = LayoutInflater.from(context).inflate(getLayoutId(mode), null);
253            int widthSpec = MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY);
254            int heightSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY);
255            view.measure(widthSpec, heightSpec);
256            view.layout(0, 0, width, height);
257
258            // Records coordinates.
259            View checkmark = view.findViewById(R.id.checkmark);
260            coordinates.checkmarkX = getX(checkmark);
261            coordinates.checkmarkY = getY(checkmark);
262            coordinates.checkmarkWidthIncludingMargins = getWidth(checkmark, true);
263
264            View star = view.findViewById(R.id.star);
265            coordinates.starX = getX(star);
266            coordinates.starY = getY(star);
267
268            View state = view.findViewById(R.id.reply_state);
269            coordinates.stateX = getX(state);
270            coordinates.stateY = getY(state);
271
272            TextView senders = (TextView) view.findViewById(R.id.senders);
273            coordinates.sendersX = getX(senders);
274            coordinates.sendersY = getY(senders);
275            coordinates.sendersWidth = getWidth(senders, false);
276            coordinates.sendersLineCount = getLineCount(senders);
277            coordinates.sendersFontSize = (int) senders.getTextSize();
278            coordinates.sendersAscent = Math.round(senders.getPaint().ascent());
279
280            TextView subject = (TextView) view.findViewById(R.id.subject);
281            coordinates.subjectX = getX(subject);
282            coordinates.subjectY = getY(subject);
283            coordinates.subjectWidth = getWidth(subject, false);
284            coordinates.subjectLineCount = getLineCount(subject);
285            coordinates.subjectFontSize = (int) subject.getTextSize();
286            coordinates.subjectAscent = Math.round(subject.getPaint().ascent());
287
288            View chip = view.findViewById(R.id.color_chip);
289            coordinates.chipX = getX(chip);
290            coordinates.chipY = getY(chip);
291            coordinates.chipWidth = getWidth(chip, false);
292            coordinates.chipHeight = getHeight(chip, false);
293
294            TextView date = (TextView) view.findViewById(R.id.date);
295            coordinates.dateXEnd = getX(date) + date.getWidth();
296            coordinates.dateY = getY(date);
297            coordinates.dateFontSize = (int) date.getTextSize();
298            coordinates.dateAscent = Math.round(date.getPaint().ascent());
299
300            // The x-value is computed relative to the date.
301            View paperclip = view.findViewById(R.id.paperclip);
302            coordinates.paperclipY = getY(paperclip);
303        }
304        return coordinates;
305    }
306}
307