blob: e40ff12cf558db6904e8eb219720ec0517d998b9 [file] [log] [blame]
mindyp6d11c8f2013-01-03 13:19:15 -08001/*
2 * Copyright (C) 2013 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
17package com.android.mail.ui;
18
19import android.content.Context;
mindypb47828f2013-01-17 13:55:44 -080020import android.content.res.Resources;
mindyp6d11c8f2013-01-03 13:19:15 -080021import android.graphics.Bitmap;
22import android.graphics.Canvas;
23import android.graphics.Paint;
Mark Wei479505d2013-03-21 06:04:46 -070024import android.graphics.PorterDuff.Mode;
25import android.graphics.PorterDuffXfermode;
mindyp6d11c8f2013-01-03 13:19:15 -080026import android.graphics.Rect;
27
mindypb47828f2013-01-17 13:55:44 -080028import com.android.mail.R;
Mark Wei479505d2013-03-21 06:04:46 -070029import com.android.mail.utils.Utils;
Andy Huang8b7fab52013-04-15 11:34:10 -070030import com.google.common.collect.Lists;
31import com.google.common.collect.Maps;
mindyp9009e592013-01-03 13:19:15 -080032
mindyp6d11c8f2013-01-03 13:19:15 -080033import java.util.ArrayList;
Andy Huang8b7fab52013-04-15 11:34:10 -070034import java.util.List;
35import java.util.Map;
mindyp6d11c8f2013-01-03 13:19:15 -080036
37/**
38 * DividedImageCanvas creates a canvas that can display into a minimum of 1
39 * and maximum of 4 images. As images are added, they
40 * are laid out according to the following algorithm:
41 * 1 Image: Draw the bitmap filling the entire canvas.
42 * 2 Images: Draw 2 bitmaps split vertically down the middle.
43 * 3 Images: Draw 3 bitmaps: the first takes up all vertical space; the 2nd and 3rd are stacked in
44 * the second vertical position.
45 * 4 Images: Divide the Canvas into 4 equal quadrants and draws 1 bitmap in each.
46 */
Mark Wei47cb5102013-03-19 04:27:06 -070047public class DividedImageCanvas implements ImageCanvas {
mindyp6d11c8f2013-01-03 13:19:15 -080048 public static final int MAX_DIVISIONS = 4;
49
Mark Wei479505d2013-03-21 06:04:46 -070050 private final Map<String, Integer> mDivisionMap = Maps
51 .newHashMapWithExpectedSize(MAX_DIVISIONS);
mindyp6d11c8f2013-01-03 13:19:15 -080052 private Bitmap mDividedBitmap;
53 private Canvas mCanvas;
54 private int mWidth;
55 private int mHeight;
56
57 private final Context mContext;
58 private final InvalidateCallback mCallback;
mindyp379ff592013-01-03 13:19:15 -080059 private final ArrayList<Bitmap> mDivisionImages = new ArrayList<Bitmap>(MAX_DIVISIONS);
mindyp6d11c8f2013-01-03 13:19:15 -080060
Andy Huang8b7fab52013-04-15 11:34:10 -070061 /**
62 * Ignore any request to draw final output when not yet ready. This prevents partially drawn
63 * canvases from appearing.
64 */
65 private boolean mBitmapValid = false;
mindypb47828f2013-01-17 13:55:44 -080066
Andy Huangb53fe052013-04-20 23:37:37 -070067 private int mGeneration;
68
mindyp6d11c8f2013-01-03 13:19:15 -080069 private static final Paint sPaint = new Paint();
Mark Wei479505d2013-03-21 06:04:46 -070070 private static final Paint sClearPaint = new Paint();
71 private static final Rect sSrc = new Rect();
mindyp6d11c8f2013-01-03 13:19:15 -080072 private static final Rect sDest = new Rect();
73
mindypb47828f2013-01-17 13:55:44 -080074 private static int sDividerLineWidth = -1;
75 private static int sDividerColor;
76
Mark Wei479505d2013-03-21 06:04:46 -070077 static {
78 sClearPaint.setXfermode(new PorterDuffXfermode(Mode.CLEAR));
79 }
80
mindyp6d11c8f2013-01-03 13:19:15 -080081 public DividedImageCanvas(Context context, InvalidateCallback callback) {
82 mContext = context;
83 mCallback = callback;
mindypb47828f2013-01-17 13:55:44 -080084 setupDividerLines();
mindyp6d11c8f2013-01-03 13:19:15 -080085 }
86
87 /**
88 * Get application context for this object.
89 */
90 public Context getContext() {
91 return mContext;
92 }
93
Andy Huang8b7fab52013-04-15 11:34:10 -070094 @Override
95 public String toString() {
96 final StringBuilder sb = new StringBuilder("{");
97 sb.append(super.toString());
98 sb.append(" mDivisionMap=");
99 sb.append(mDivisionMap);
100 sb.append(" mDivisionImages=");
101 sb.append(mDivisionImages);
102 sb.append(" mWidth=");
103 sb.append(mWidth);
104 sb.append(" mHeight=");
105 sb.append(mHeight);
106 sb.append("}");
107 return sb.toString();
108 }
109
mindyp6d11c8f2013-01-03 13:19:15 -0800110 /**
111 * Set the id associated with each quadrant. The quadrants are laid out:
112 * TopLeft, TopRight, Bottom Left, Bottom Right
Mark Wei479505d2013-03-21 06:04:46 -0700113 * @param keys
mindyp6d11c8f2013-01-03 13:19:15 -0800114 */
Mark Wei479505d2013-03-21 06:04:46 -0700115 public void setDivisionIds(List<Object> keys) {
116 if (keys.size() > MAX_DIVISIONS) {
117 throw new IllegalArgumentException("too many divisionIds: " + keys);
Andy Huang8b7fab52013-04-15 11:34:10 -0700118 }
Mark Wei479505d2013-03-21 06:04:46 -0700119
120 boolean needClear = getDivisionCount() != keys.size();
121 if (!needClear) {
122 for (int i = 0; i < keys.size(); i++) {
123 String divisionId = transformKeyToDivisionId(keys.get(i));
124 // different item or different place
125 if (!mDivisionMap.containsKey(divisionId) || mDivisionMap.get(divisionId) != i) {
126 needClear = true;
127 break;
128 }
129 }
130 }
131
132 if (needClear) {
133 mDivisionMap.clear();
134 mDivisionImages.clear();
135 int i = 0;
136 for (Object key : keys) {
137 String divisionId = transformKeyToDivisionId(key);
138 mDivisionMap.put(divisionId, i);
139 mDivisionImages.add(null);
140 i++;
141 }
mindyp6d11c8f2013-01-03 13:19:15 -0800142 }
143 }
144
Mark Wei479505d2013-03-21 06:04:46 -0700145 private void draw(Bitmap b, int left, int top, int right, int bottom) {
mindyp6d11c8f2013-01-03 13:19:15 -0800146 if (b != null) {
Mark Wei479505d2013-03-21 06:04:46 -0700147 // Some times we load taller images compared to the destination rect on the canvas
148 int srcTop = 0;
149 int srcBottom = b.getHeight();
150 int destHeight = bottom - top;
151 if (b.getHeight() > bottom - top) {
152 srcTop = b.getHeight() / 2 - destHeight/2;
153 srcBottom = b.getHeight() / 2 + destHeight/2;
154 }
155
156// todo:markwei do not scale very small bitmaps
mindyp6d11c8f2013-01-03 13:19:15 -0800157 // l t r b
Mark Wei479505d2013-03-21 06:04:46 -0700158 sSrc.set(0, srcTop, b.getWidth(), srcBottom);
mindyp6d11c8f2013-01-03 13:19:15 -0800159 sDest.set(left, top, right, bottom);
Mark Wei479505d2013-03-21 06:04:46 -0700160 mCanvas.drawBitmap(b, sSrc, sDest, sPaint);
161 } else {
162 // clear
163 mCanvas.drawRect(left, top, right, bottom, sClearPaint);
mindyp6d11c8f2013-01-03 13:19:15 -0800164 }
165 }
166
167 /**
mindyp53955552013-01-10 15:42:39 -0800168 * Get the desired dimensions and scale for the bitmap to be placed in the
Andy Huang8b7fab52013-04-15 11:34:10 -0700169 * location corresponding to id. Caller must allocate the Dimensions object.
Mark Wei479505d2013-03-21 06:04:46 -0700170 * @param key
Andy Huang8b7fab52013-04-15 11:34:10 -0700171 * @param outDim a {@link ImageCanvas.Dimensions} object to write results into
mindyp53955552013-01-10 15:42:39 -0800172 */
Andy Huang8b7fab52013-04-15 11:34:10 -0700173 @Override
Mark Wei479505d2013-03-21 06:04:46 -0700174 public void getDesiredDimensions(Object key, Dimensions outDim) {
175 Utils.traceBeginSection("get desired dimensions");
mindyp53955552013-01-10 15:42:39 -0800176 int w = 0, h = 0;
177 float scale = 0;
Mark Wei479505d2013-03-21 06:04:46 -0700178 final Integer pos = mDivisionMap.get(transformKeyToDivisionId(key));
Andy Huang8b7fab52013-04-15 11:34:10 -0700179 if (pos != null && pos >= 0) {
180 final int size = mDivisionMap.size();
mindyp53955552013-01-10 15:42:39 -0800181 switch (size) {
182 case 0:
183 break;
184 case 1:
185 w = mWidth;
186 h = mHeight;
Andy Huang8b7fab52013-04-15 11:34:10 -0700187 scale = Dimensions.SCALE_ONE;
mindyp53955552013-01-10 15:42:39 -0800188 break;
189 case 2:
190 w = mWidth / 2;
191 h = mHeight;
Andy Huang8b7fab52013-04-15 11:34:10 -0700192 scale = Dimensions.SCALE_HALF;
mindyp53955552013-01-10 15:42:39 -0800193 break;
194 case 3:
195 switch (pos) {
196 case 0:
197 w = mWidth / 2;
198 h = mHeight;
Andy Huang8b7fab52013-04-15 11:34:10 -0700199 scale = Dimensions.SCALE_HALF;
mindyp53955552013-01-10 15:42:39 -0800200 break;
201 default:
202 w = mWidth / 2;
203 h = mHeight / 2;
Andy Huang8b7fab52013-04-15 11:34:10 -0700204 scale = Dimensions.SCALE_QUARTER;
mindyp53955552013-01-10 15:42:39 -0800205 }
206 break;
207 case 4:
208 w = mWidth / 2;
209 h = mHeight / 2;
Andy Huang8b7fab52013-04-15 11:34:10 -0700210 scale = Dimensions.SCALE_QUARTER;
mindyp53955552013-01-10 15:42:39 -0800211 break;
212 }
213 }
Andy Huang8b7fab52013-04-15 11:34:10 -0700214 outDim.width = w;
215 outDim.height = h;
216 outDim.scale = scale;
Mark Wei479505d2013-03-21 06:04:46 -0700217 Utils.traceEndSection();
Andy Huang8b7fab52013-04-15 11:34:10 -0700218 }
219
220 @Override
Mark Wei479505d2013-03-21 06:04:46 -0700221 public void drawImage(Bitmap b, Object key) {
222 addDivisionImage(b, key);
mindyp53955552013-01-10 15:42:39 -0800223 }
224
mindyp379ff592013-01-03 13:19:15 -0800225 /**
mindyp6d11c8f2013-01-03 13:19:15 -0800226 * Add a bitmap to this view in the quadrant matching its id.
227 * @param b Bitmap
Mark Wei479505d2013-03-21 06:04:46 -0700228 * @param key Id to look for that was previously set in setDivisionIds.
mindyp6d11c8f2013-01-03 13:19:15 -0800229 */
Mark Wei479505d2013-03-21 06:04:46 -0700230 public void addDivisionImage(Bitmap b, Object key) {
231 if (b != null) {
232 addOrClearDivisionImage(b, key);
233 }
234 }
235
236 public void clearDivisionImage(Object key) {
237 addOrClearDivisionImage(null, key);
238 }
239 private void addOrClearDivisionImage(Bitmap b, Object key) {
240 Utils.traceBeginSection("add or clear division image");
241 final Integer pos = mDivisionMap.get(transformKeyToDivisionId(key));
242 if (pos != null && pos >= 0) {
mindyp379ff592013-01-03 13:19:15 -0800243 mDivisionImages.set(pos, b);
mindyp6d11c8f2013-01-03 13:19:15 -0800244 boolean complete = false;
Andy Huang8b7fab52013-04-15 11:34:10 -0700245 final int width = mWidth;
246 final int height = mHeight;
mindyp6d11c8f2013-01-03 13:19:15 -0800247 // Different layouts depending on count.
Andy Huang8b7fab52013-04-15 11:34:10 -0700248 final int size = mDivisionMap.size();
mindyp6d11c8f2013-01-03 13:19:15 -0800249 switch (size) {
250 case 0:
251 // Do nothing.
252 break;
253 case 1:
254 // Draw the bitmap filling the entire canvas.
Mark Wei479505d2013-03-21 06:04:46 -0700255 draw(mDivisionImages.get(0), 0, 0, width, height);
mindyp6d11c8f2013-01-03 13:19:15 -0800256 complete = true;
257 break;
258 case 2:
259 // Draw 2 bitmaps split vertically down the middle
mindyp6d11c8f2013-01-03 13:19:15 -0800260 switch (pos) {
261 case 0:
Mark Wei479505d2013-03-21 06:04:46 -0700262 draw(mDivisionImages.get(0), 0, 0, width / 2, height);
mindyp6d11c8f2013-01-03 13:19:15 -0800263 break;
264 case 1:
Mark Wei479505d2013-03-21 06:04:46 -0700265 draw(mDivisionImages.get(1), width / 2, 0, width, height);
mindyp6d11c8f2013-01-03 13:19:15 -0800266 break;
267 }
Mark Wei479505d2013-03-21 06:04:46 -0700268 complete = mDivisionImages.get(0) != null && mDivisionImages.get(1) != null
269 || isPartialBitmapComplete();
mindypb47828f2013-01-17 13:55:44 -0800270 if (complete) {
271 // Draw dividers
272 drawVerticalDivider(width, height);
273 }
mindyp6d11c8f2013-01-03 13:19:15 -0800274 break;
275 case 3:
276 // Draw 3 bitmaps: the first takes up all vertical
mindypb47828f2013-01-17 13:55:44 -0800277 // space, the 2nd and 3rd are stacked in the second vertical
mindyp6d11c8f2013-01-03 13:19:15 -0800278 // position.
279 switch (pos) {
280 case 0:
Mark Wei479505d2013-03-21 06:04:46 -0700281 draw(mDivisionImages.get(0), 0, 0, width / 2, height);
mindyp6d11c8f2013-01-03 13:19:15 -0800282 break;
283 case 1:
Mark Wei479505d2013-03-21 06:04:46 -0700284 draw(mDivisionImages.get(1), width / 2, 0, width, height / 2);
mindyp6d11c8f2013-01-03 13:19:15 -0800285 break;
286 case 2:
Mark Wei479505d2013-03-21 06:04:46 -0700287 draw(mDivisionImages.get(2), width / 2, height / 2, width, height);
mindyp6d11c8f2013-01-03 13:19:15 -0800288 break;
289 }
290 complete = mDivisionImages.get(0) != null && mDivisionImages.get(1) != null
Mark Wei479505d2013-03-21 06:04:46 -0700291 && mDivisionImages.get(2) != null || isPartialBitmapComplete();
mindypb47828f2013-01-17 13:55:44 -0800292 if (complete) {
293 // Draw dividers
294 drawVerticalDivider(width, height);
mindyp3f225732013-01-17 14:45:26 -0800295 drawHorizontalDivider(width / 2, height / 2, width, height / 2);
mindypb47828f2013-01-17 13:55:44 -0800296 }
mindyp6d11c8f2013-01-03 13:19:15 -0800297 break;
298 default:
299 // Draw all 4 bitmaps in a grid
mindyp6d11c8f2013-01-03 13:19:15 -0800300 switch (pos) {
301 case 0:
Mark Wei479505d2013-03-21 06:04:46 -0700302 draw(mDivisionImages.get(0), 0, 0, width / 2, height / 2);
mindyp6d11c8f2013-01-03 13:19:15 -0800303 break;
304 case 1:
Mark Wei479505d2013-03-21 06:04:46 -0700305 draw(mDivisionImages.get(1), width / 2, 0, width, height / 2);
mindyp6d11c8f2013-01-03 13:19:15 -0800306 break;
307 case 2:
Mark Wei479505d2013-03-21 06:04:46 -0700308 draw(mDivisionImages.get(2), 0, height / 2, width / 2, height);
mindyp6d11c8f2013-01-03 13:19:15 -0800309 break;
310 case 3:
Mark Wei479505d2013-03-21 06:04:46 -0700311 draw(mDivisionImages.get(3), width / 2, height / 2, width, height);
mindyp6d11c8f2013-01-03 13:19:15 -0800312 break;
313 }
314 complete = mDivisionImages.get(0) != null && mDivisionImages.get(1) != null
Mark Wei479505d2013-03-21 06:04:46 -0700315 && mDivisionImages.get(2) != null && mDivisionImages.get(3) != null
316 || isPartialBitmapComplete();
mindypb47828f2013-01-17 13:55:44 -0800317 if (complete) {
318 // Draw dividers
319 drawVerticalDivider(width, height);
mindyp3f225732013-01-17 14:45:26 -0800320 drawHorizontalDivider(0, height / 2, width, height / 2);
mindypb47828f2013-01-17 13:55:44 -0800321 }
mindyp6d11c8f2013-01-03 13:19:15 -0800322 break;
323 }
324 // Create the new image bitmap.
325 if (complete) {
Andy Huang8b7fab52013-04-15 11:34:10 -0700326 mBitmapValid = true;
mindyp6d11c8f2013-01-03 13:19:15 -0800327 mCallback.invalidate();
328 }
329 }
Mark Wei479505d2013-03-21 06:04:46 -0700330 Utils.traceEndSection();
mindyp6d11c8f2013-01-03 13:19:15 -0800331 }
332
Mark Wei479505d2013-03-21 06:04:46 -0700333 public boolean hasImageFor(Object key) {
334 final Integer pos = mDivisionMap.get(transformKeyToDivisionId(key));
Andy Huangbbe7f922013-04-22 14:35:28 -0700335 return pos != null && mDivisionImages.get(pos) != null;
336 }
337
mindypb47828f2013-01-17 13:55:44 -0800338 private void setupDividerLines() {
339 if (sDividerLineWidth == -1) {
340 Resources res = getContext().getResources();
341 sDividerLineWidth = res
342 .getDimensionPixelSize(R.dimen.tile_divider_width);
343 sDividerColor = res.getColor(R.color.tile_divider_color);
344 }
345 }
346
Scott Kennedyff8553f2013-04-05 20:57:44 -0700347 private static void setupPaint() {
mindyp3f225732013-01-17 14:45:26 -0800348 sPaint.setStrokeWidth(sDividerLineWidth);
mindypb47828f2013-01-17 13:55:44 -0800349 sPaint.setColor(sDividerColor);
mindyp3f225732013-01-17 14:45:26 -0800350 }
351
Mark Wei479505d2013-03-21 06:04:46 -0700352 protected void drawVerticalDivider(int width, int height) {
mindyp3f225732013-01-17 14:45:26 -0800353 int x1 = width / 2, y1 = 0, x2 = width/2, y2 = height;
354 setupPaint();
mindypb47828f2013-01-17 13:55:44 -0800355 mCanvas.drawLine(x1, y1, x2, y2, sPaint);
356 }
357
Mark Wei479505d2013-03-21 06:04:46 -0700358 protected void drawHorizontalDivider(int x1, int y1, int x2, int y2) {
mindyp3f225732013-01-17 14:45:26 -0800359 setupPaint();
mindypb47828f2013-01-17 13:55:44 -0800360 mCanvas.drawLine(x1, y1, x2, y2, sPaint);
361 }
362
Mark Wei479505d2013-03-21 06:04:46 -0700363 protected boolean isPartialBitmapComplete() {
364 return false;
365 }
366
367 protected String transformKeyToDivisionId(Object key) {
368 return key.toString();
369 }
370
mindyp6d11c8f2013-01-03 13:19:15 -0800371 /**
372 * Draw the contents of the DividedImageCanvas to the supplied canvas.
373 */
374 public void draw(Canvas canvas) {
Mark Wei479505d2013-03-21 06:04:46 -0700375 // todo:markwei we can see the old image behind transparency regions. Should we also
376 // "clear" the canvas? ath
Andy Huang8b7fab52013-04-15 11:34:10 -0700377 if (mDividedBitmap != null && mBitmapValid) {
378 canvas.drawBitmap(mDividedBitmap, 0, 0, null);
mindyp6d11c8f2013-01-03 13:19:15 -0800379 }
380 }
381
Mark Wei47cb5102013-03-19 04:27:06 -0700382 @Override
mindyp6d11c8f2013-01-03 13:19:15 -0800383 public void reset() {
mindyp379ff592013-01-03 13:19:15 -0800384 if (mCanvas != null && mDividedBitmap != null) {
Andy Huang8b7fab52013-04-15 11:34:10 -0700385 mBitmapValid = false;
mindyp379ff592013-01-03 13:19:15 -0800386 }
Andy Huang8b7fab52013-04-15 11:34:10 -0700387 mDivisionMap.clear();
mindyp379ff592013-01-03 13:19:15 -0800388 mDivisionImages.clear();
Andy Huangb53fe052013-04-20 23:37:37 -0700389 mGeneration++;
mindyp6d11c8f2013-01-03 13:19:15 -0800390 }
391
Andy Huang8b7fab52013-04-15 11:34:10 -0700392 @Override
Andy Huangb53fe052013-04-20 23:37:37 -0700393 public int getGeneration() {
394 return mGeneration;
Andy Huang8b7fab52013-04-15 11:34:10 -0700395 }
396
mindyp6d11c8f2013-01-03 13:19:15 -0800397 /**
398 * Set the width and height of the canvas.
399 * @param width
400 * @param height
401 */
402 public void setDimensions(int width, int height) {
Mark Wei479505d2013-03-21 06:04:46 -0700403 Utils.traceBeginSection("set dimensions");
Andy Huang8b7fab52013-04-15 11:34:10 -0700404 if (mWidth == width && mHeight == height) {
Mark Wei479505d2013-03-21 06:04:46 -0700405 Utils.traceEndSection();
Andy Huang8b7fab52013-04-15 11:34:10 -0700406 return;
407 }
408
mindyp6d11c8f2013-01-03 13:19:15 -0800409 mWidth = width;
410 mHeight = height;
Andy Huang8b7fab52013-04-15 11:34:10 -0700411
Mark Wei479505d2013-03-21 06:04:46 -0700412 // todo:ath this bitmap is creating a GC which is killing CIV.loadAttachmentPreviews()
Andy Huang8b7fab52013-04-15 11:34:10 -0700413 mDividedBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
414 mCanvas = new Canvas(mDividedBitmap);
415 mBitmapValid = true;
Mark Wei479505d2013-03-21 06:04:46 -0700416 Utils.traceEndSection();
mindyp6d11c8f2013-01-03 13:19:15 -0800417 }
418
419 /**
420 * Get the resulting canvas width.
421 */
422 public int getWidth() {
423 return mWidth;
424 }
425
426 /**
427 * Get the resulting canvas height.
428 */
429 public int getHeight() {
430 return mHeight;
431 }
432
433 /**
434 * The class that will provided the canvas to which the DividedImageCanvas
435 * should render its contents must implement this interface.
436 */
437 public interface InvalidateCallback {
438 public void invalidate();
439 }
mindyp53955552013-01-10 15:42:39 -0800440
mindyp646d4fc2013-01-15 17:17:10 -0800441 public int getDivisionCount() {
Andy Huang8b7fab52013-04-15 11:34:10 -0700442 return mDivisionMap.size();
mindyp646d4fc2013-01-15 17:17:10 -0800443 }
444
445 /**
mindyp6fbad632013-01-17 09:59:25 -0800446 * Get the division ids currently associated with this DivisionImageCanvas.
447 */
448 public ArrayList<String> getDivisionIds() {
Andy Huang8b7fab52013-04-15 11:34:10 -0700449 return Lists.newArrayList(mDivisionMap.keySet());
mindyp6fbad632013-01-17 09:59:25 -0800450 }
mindyp6d11c8f2013-01-03 13:19:15 -0800451}