blob: ea39f6d715739bce4cf7434cbd58f7d972cbcd5d [file] [log] [blame]
Evan Millare727f182009-08-27 11:15:55 -07001/*
2 * Copyright (C) 2009 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 android.widget;
18
Artur Satayeved5a6ae2019-12-10 17:47:54 +000019import android.compat.annotation.UnsupportedAppUsage;
Evan Millare727f182009-08-27 11:15:55 -070020import android.content.AsyncQueryHandler;
21import android.content.ContentResolver;
22import android.content.Context;
23import android.content.Intent;
24import android.content.res.TypedArray;
25import android.database.Cursor;
Gilles Debunne92350582011-01-07 19:39:26 -080026import android.graphics.Canvas;
Mark Wagnerf5935a92009-09-22 13:49:26 -070027import android.graphics.drawable.Drawable;
Evan Millare727f182009-08-27 11:15:55 -070028import android.net.Uri;
Yorke Lee02f304f2013-01-14 08:57:43 -080029import android.os.Bundle;
Gilles Debunne92350582011-01-07 19:39:26 -080030import android.provider.ContactsContract.CommonDataKinds.Email;
Evan Millare727f182009-08-27 11:15:55 -070031import android.provider.ContactsContract.Contacts;
32import android.provider.ContactsContract.Intents;
33import android.provider.ContactsContract.PhoneLookup;
Tadashi G. Takaoka01fd4e12009-10-01 18:10:46 -070034import android.provider.ContactsContract.QuickContact;
Dmitri Plotnikovea367472009-09-18 15:04:58 -070035import android.provider.ContactsContract.RawContacts;
Evan Millare727f182009-08-27 11:15:55 -070036import android.util.AttributeSet;
37import android.view.View;
38import android.view.View.OnClickListener;
Evan Millare727f182009-08-27 11:15:55 -070039
Aurimas Liutikas99441c52016-10-11 16:48:32 -070040import com.android.internal.R;
41
Evan Millare727f182009-08-27 11:15:55 -070042/**
Evan Millarc1a2a822009-09-29 18:02:06 -070043 * Widget used to show an image with the standard QuickContact badge
Evan Millare727f182009-08-27 11:15:55 -070044 * and on-click behavior.
Evan Millare727f182009-08-27 11:15:55 -070045 */
Evan Millarc1a2a822009-09-29 18:02:06 -070046public class QuickContactBadge extends ImageView implements OnClickListener {
Evan Millare727f182009-08-27 11:15:55 -070047 private Uri mContactUri;
48 private String mContactEmail;
49 private String mContactPhone;
Mathew Inwood978c6e22018-08-21 15:58:55 +010050 @UnsupportedAppUsage
Gilles Debunne92350582011-01-07 19:39:26 -080051 private Drawable mOverlay;
Evan Millare727f182009-08-27 11:15:55 -070052 private QueryHandler mQueryHandler;
Evan Millar9a20aa72010-04-13 09:51:21 -070053 private Drawable mDefaultAvatar;
Yorke Lee02f304f2013-01-14 08:57:43 -080054 private Bundle mExtras = null;
Brian Attwell7035f2b2015-02-25 20:06:08 -080055 private String mPrioritizedMimeType;
Evan Millare727f182009-08-27 11:15:55 -070056
57 protected String[] mExcludeMimes = null;
58
59 static final private int TOKEN_EMAIL_LOOKUP = 0;
60 static final private int TOKEN_PHONE_LOOKUP = 1;
61 static final private int TOKEN_EMAIL_LOOKUP_AND_TRIGGER = 2;
62 static final private int TOKEN_PHONE_LOOKUP_AND_TRIGGER = 3;
63
Yorke Lee02f304f2013-01-14 08:57:43 -080064 static final private String EXTRA_URI_CONTENT = "uri_content";
65
Evan Millare727f182009-08-27 11:15:55 -070066 static final String[] EMAIL_LOOKUP_PROJECTION = new String[] {
Dmitri Plotnikovea367472009-09-18 15:04:58 -070067 RawContacts.CONTACT_ID,
Evan Millare727f182009-08-27 11:15:55 -070068 Contacts.LOOKUP_KEY,
69 };
Tadashi G. Takaoka01fd4e12009-10-01 18:10:46 -070070 static final int EMAIL_ID_COLUMN_INDEX = 0;
71 static final int EMAIL_LOOKUP_STRING_COLUMN_INDEX = 1;
Evan Millare727f182009-08-27 11:15:55 -070072
73 static final String[] PHONE_LOOKUP_PROJECTION = new String[] {
74 PhoneLookup._ID,
75 PhoneLookup.LOOKUP_KEY,
76 };
Tadashi G. Takaoka01fd4e12009-10-01 18:10:46 -070077 static final int PHONE_ID_COLUMN_INDEX = 0;
78 static final int PHONE_LOOKUP_STRING_COLUMN_INDEX = 1;
Evan Millare727f182009-08-27 11:15:55 -070079
Evan Millarc1a2a822009-09-29 18:02:06 -070080 public QuickContactBadge(Context context) {
Evan Millare727f182009-08-27 11:15:55 -070081 this(context, null);
82 }
83
Evan Millarc1a2a822009-09-29 18:02:06 -070084 public QuickContactBadge(Context context, AttributeSet attrs) {
Evan Millare727f182009-08-27 11:15:55 -070085 this(context, attrs, 0);
86 }
87
Alan Viverette617feb92013-09-09 18:09:13 -070088 public QuickContactBadge(Context context, AttributeSet attrs, int defStyleAttr) {
89 this(context, attrs, defStyleAttr, 0);
90 }
91
92 public QuickContactBadge(
93 Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
94 super(context, attrs, defStyleAttr, defStyleRes);
Evan Millare727f182009-08-27 11:15:55 -070095
Gilles Debunne92350582011-01-07 19:39:26 -080096 TypedArray styledAttributes = mContext.obtainStyledAttributes(R.styleable.Theme);
Daniel Lehmanne13e55b2011-08-07 19:51:28 -070097 mOverlay = styledAttributes.getDrawable(
98 com.android.internal.R.styleable.Theme_quickContactBadgeOverlay);
Gilles Debunne92350582011-01-07 19:39:26 -080099 styledAttributes.recycle();
100
John Reckd0374c62015-10-20 13:25:01 -0700101 setOnClickListener(this);
102 }
103
104 @Override
105 protected void onAttachedToWindow() {
106 super.onAttachedToWindow();
107
Deepanshu Gupta34e9c692013-10-18 11:22:46 -0700108 if (!isInEditMode()) {
109 mQueryHandler = new QueryHandler(mContext.getContentResolver());
110 }
Evan Millare727f182009-08-27 11:15:55 -0700111 }
112
Gilles Debunne5cba8622011-01-12 19:52:56 -0800113 @Override
114 protected void drawableStateChanged() {
115 super.drawableStateChanged();
Alan Viverettead0020f2015-09-04 10:10:42 -0400116
117 final Drawable overlay = mOverlay;
118 if (overlay != null && overlay.isStateful()
119 && overlay.setState(getDrawableState())) {
120 invalidateDrawable(overlay);
Gilles Debunne5cba8622011-01-12 19:52:56 -0800121 }
122 }
123
Alan Viverettecebc6ba2014-06-13 15:52:13 -0700124 @Override
Alan Viverette8de14942014-06-18 18:05:15 -0700125 public void drawableHotspotChanged(float x, float y) {
126 super.drawableHotspotChanged(x, y);
Alan Viverettecebc6ba2014-06-13 15:52:13 -0700127
128 if (mOverlay != null) {
129 mOverlay.setHotspot(x, y);
130 }
131 }
132
Daniel Lehmanne13e55b2011-08-07 19:51:28 -0700133 /** This call has no effect anymore, as there is only one QuickContact mode */
134 @SuppressWarnings("unused")
Evan Millare3ec9972009-09-30 19:37:36 -0700135 public void setMode(int size) {
Evan Millare3ec9972009-09-30 19:37:36 -0700136 }
Gilles Debunne92350582011-01-07 19:39:26 -0800137
Brian Attwell7035f2b2015-02-25 20:06:08 -0800138 /**
139 * Set which mimetype should be prioritized in the QuickContacts UI. For example, passing the
140 * value {@link Email#CONTENT_ITEM_TYPE} can cause emails to be displayed more prominently in
141 * QuickContacts.
142 */
143 public void setPrioritizedMimeType(String prioritizedMimeType) {
144 mPrioritizedMimeType = prioritizedMimeType;
145 }
146
Gilles Debunne92350582011-01-07 19:39:26 -0800147 @Override
148 protected void onDraw(Canvas canvas) {
149 super.onDraw(canvas);
150
Daniel Lehmanne13e55b2011-08-07 19:51:28 -0700151 if (!isEnabled()) {
152 // not clickable? don't show triangle
153 return;
154 }
155
Gilles Debunne5cba8622011-01-12 19:52:56 -0800156 if (mOverlay == null || mOverlay.getIntrinsicWidth() == 0 ||
157 mOverlay.getIntrinsicHeight() == 0) {
Daniel Lehmanne13e55b2011-08-07 19:51:28 -0700158 // nothing to draw
159 return;
Gilles Debunne92350582011-01-07 19:39:26 -0800160 }
161
162 mOverlay.setBounds(0, 0, getWidth(), getHeight());
163
164 if (mPaddingTop == 0 && mPaddingLeft == 0) {
165 mOverlay.draw(canvas);
166 } else {
167 int saveCount = canvas.getSaveCount();
168 canvas.save();
169 canvas.translate(mPaddingLeft, mPaddingTop);
170 mOverlay.draw(canvas);
171 canvas.restoreToCount(saveCount);
172 }
173 }
174
Daniel Lehmanne13e55b2011-08-07 19:51:28 -0700175 /** True if a contact, an email address or a phone number has been assigned */
176 private boolean isAssigned() {
177 return mContactUri != null || mContactEmail != null || mContactPhone != null;
178 }
179
Evan Millar9a20aa72010-04-13 09:51:21 -0700180 /**
181 * Resets the contact photo to the default state.
182 */
183 public void setImageToDefault() {
184 if (mDefaultAvatar == null) {
Alan Viverette8eea3ea2014-02-03 18:40:20 -0800185 mDefaultAvatar = mContext.getDrawable(R.drawable.ic_contact_picture);
Evan Millar9a20aa72010-04-13 09:51:21 -0700186 }
187 setImageDrawable(mDefaultAvatar);
188 }
Evan Millare3ec9972009-09-30 19:37:36 -0700189
190 /**
Evan Millarc1a2a822009-09-29 18:02:06 -0700191 * Assign the contact uri that this QuickContactBadge should be associated
192 * with. Note that this is only used for displaying the QuickContact window and
Gilles Debunne92350582011-01-07 19:39:26 -0800193 * won't bind the contact's photo for you. Call {@link #setImageDrawable(Drawable)} to set the
194 * photo.
Evan Millare727f182009-08-27 11:15:55 -0700195 *
Jeff Sharkey0050ee32009-09-17 16:11:32 -0700196 * @param contactUri Either a {@link Contacts#CONTENT_URI} or
197 * {@link Contacts#CONTENT_LOOKUP_URI} style URI.
Evan Millare727f182009-08-27 11:15:55 -0700198 */
199 public void assignContactUri(Uri contactUri) {
200 mContactUri = contactUri;
Mark Wagnerf5935a92009-09-22 13:49:26 -0700201 mContactEmail = null;
202 mContactPhone = null;
203 onContactUriChanged();
204 }
205
Evan Millare727f182009-08-27 11:15:55 -0700206 /**
207 * Assign a contact based on an email address. This should only be used when
208 * the contact's URI is not available, as an extra query will have to be
209 * performed to lookup the URI based on the email.
210 *
211 * @param emailAddress The email address of the contact.
212 * @param lazyLookup If this is true, the lookup query will not be performed
213 * until this view is clicked.
214 */
215 public void assignContactFromEmail(String emailAddress, boolean lazyLookup) {
Yorke Lee02f304f2013-01-14 08:57:43 -0800216 assignContactFromEmail(emailAddress, lazyLookup, null);
217 }
218
219 /**
220 * Assign a contact based on an email address. This should only be used when
221 * the contact's URI is not available, as an extra query will have to be
222 * performed to lookup the URI based on the email.
223
224 @param emailAddress The email address of the contact.
225 @param lazyLookup If this is true, the lookup query will not be performed
226 until this view is clicked.
227 @param extras A bundle of extras to populate the contact edit page with if the contact
228 is not found and the user chooses to add the email address to an existing contact or
229 create a new contact. Uses the same string constants as those found in
Yorke Lee3e839f42013-01-15 16:46:00 -0800230 {@link android.provider.ContactsContract.Intents.Insert}
Yorke Lee02f304f2013-01-14 08:57:43 -0800231 */
232
233 public void assignContactFromEmail(String emailAddress, boolean lazyLookup, Bundle extras) {
Evan Millare727f182009-08-27 11:15:55 -0700234 mContactEmail = emailAddress;
Yorke Lee02f304f2013-01-14 08:57:43 -0800235 mExtras = extras;
Deepanshu Gupta34e9c692013-10-18 11:22:46 -0700236 if (!lazyLookup && mQueryHandler != null) {
Evan Millare727f182009-08-27 11:15:55 -0700237 mQueryHandler.startQuery(TOKEN_EMAIL_LOOKUP, null,
238 Uri.withAppendedPath(Email.CONTENT_LOOKUP_URI, Uri.encode(mContactEmail)),
239 EMAIL_LOOKUP_PROJECTION, null, null, null);
Jeff Sharkey0050ee32009-09-17 16:11:32 -0700240 } else {
241 mContactUri = null;
Mark Wagnerf5935a92009-09-22 13:49:26 -0700242 onContactUriChanged();
Evan Millare727f182009-08-27 11:15:55 -0700243 }
244 }
245
Yorke Lee02f304f2013-01-14 08:57:43 -0800246
Evan Millare727f182009-08-27 11:15:55 -0700247 /**
248 * Assign a contact based on a phone number. This should only be used when
249 * the contact's URI is not available, as an extra query will have to be
250 * performed to lookup the URI based on the phone number.
251 *
252 * @param phoneNumber The phone number of the contact.
253 * @param lazyLookup If this is true, the lookup query will not be performed
254 * until this view is clicked.
255 */
256 public void assignContactFromPhone(String phoneNumber, boolean lazyLookup) {
Yorke Lee02f304f2013-01-14 08:57:43 -0800257 assignContactFromPhone(phoneNumber, lazyLookup, new Bundle());
258 }
259
260 /**
261 * Assign a contact based on a phone number. This should only be used when
262 * the contact's URI is not available, as an extra query will have to be
263 * performed to lookup the URI based on the phone number.
264 *
265 * @param phoneNumber The phone number of the contact.
266 * @param lazyLookup If this is true, the lookup query will not be performed
267 * until this view is clicked.
268 * @param extras A bundle of extras to populate the contact edit page with if the contact
269 * is not found and the user chooses to add the phone number to an existing contact or
270 * create a new contact. Uses the same string constants as those found in
Yorke Lee3e839f42013-01-15 16:46:00 -0800271 * {@link android.provider.ContactsContract.Intents.Insert}
Yorke Lee02f304f2013-01-14 08:57:43 -0800272 */
273 public void assignContactFromPhone(String phoneNumber, boolean lazyLookup, Bundle extras) {
Evan Millare727f182009-08-27 11:15:55 -0700274 mContactPhone = phoneNumber;
Yorke Lee02f304f2013-01-14 08:57:43 -0800275 mExtras = extras;
Deepanshu Gupta34e9c692013-10-18 11:22:46 -0700276 if (!lazyLookup && mQueryHandler != null) {
Evan Millare727f182009-08-27 11:15:55 -0700277 mQueryHandler.startQuery(TOKEN_PHONE_LOOKUP, null,
278 Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI, mContactPhone),
279 PHONE_LOOKUP_PROJECTION, null, null, null);
Jeff Sharkey0050ee32009-09-17 16:11:32 -0700280 } else {
281 mContactUri = null;
Mark Wagnerf5935a92009-09-22 13:49:26 -0700282 onContactUriChanged();
Evan Millare727f182009-08-27 11:15:55 -0700283 }
284 }
285
Yorke Leea702ccd2014-04-28 12:41:44 -0700286 /**
287 * Assigns the drawable that is to be drawn on top of the assigned contact photo.
288 *
289 * @param overlay Drawable to be drawn over the assigned contact photo. Must have a non-zero
290 * instrinsic width and height.
291 */
292 public void setOverlay(Drawable overlay) {
293 mOverlay = overlay;
294 }
295
Daniel Lehmanne13e55b2011-08-07 19:51:28 -0700296 private void onContactUriChanged() {
297 setEnabled(isAssigned());
298 }
299
300 @Override
Evan Millare727f182009-08-27 11:15:55 -0700301 public void onClick(View v) {
Yorke Lee02f304f2013-01-14 08:57:43 -0800302 // If contact has been assigned, mExtras should no longer be null, but do a null check
303 // anyway just in case assignContactFromPhone or Email was called with a null bundle or
304 // wasn't assigned previously.
305 final Bundle extras = (mExtras == null) ? new Bundle() : mExtras;
Evan Millare727f182009-08-27 11:15:55 -0700306 if (mContactUri != null) {
Daniel Lehmanne13e55b2011-08-07 19:51:28 -0700307 QuickContact.showQuickContact(getContext(), QuickContactBadge.this, mContactUri,
Brian Attwell7035f2b2015-02-25 20:06:08 -0800308 mExcludeMimes, mPrioritizedMimeType);
Deepanshu Gupta34e9c692013-10-18 11:22:46 -0700309 } else if (mContactEmail != null && mQueryHandler != null) {
Yorke Lee02f304f2013-01-14 08:57:43 -0800310 extras.putString(EXTRA_URI_CONTENT, mContactEmail);
311 mQueryHandler.startQuery(TOKEN_EMAIL_LOOKUP_AND_TRIGGER, extras,
Evan Millare727f182009-08-27 11:15:55 -0700312 Uri.withAppendedPath(Email.CONTENT_LOOKUP_URI, Uri.encode(mContactEmail)),
313 EMAIL_LOOKUP_PROJECTION, null, null, null);
Deepanshu Gupta34e9c692013-10-18 11:22:46 -0700314 } else if (mContactPhone != null && mQueryHandler != null) {
Yorke Lee02f304f2013-01-14 08:57:43 -0800315 extras.putString(EXTRA_URI_CONTENT, mContactPhone);
316 mQueryHandler.startQuery(TOKEN_PHONE_LOOKUP_AND_TRIGGER, extras,
Evan Millare727f182009-08-27 11:15:55 -0700317 Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI, mContactPhone),
318 PHONE_LOOKUP_PROJECTION, null, null, null);
319 } else {
320 // If a contact hasn't been assigned, don't react to click.
321 return;
322 }
323 }
324
Svetoslav Ganov8a78fd42012-01-17 14:36:46 -0800325 @Override
Dianne Hackborna7bb6fb2015-02-03 18:13:40 -0800326 public CharSequence getAccessibilityClassName() {
327 return QuickContactBadge.class.getName();
Svetoslav Ganov8a78fd42012-01-17 14:36:46 -0800328 }
329
Evan Millare727f182009-08-27 11:15:55 -0700330 /**
331 * Set a list of specific MIME-types to exclude and not display. For
332 * example, this can be used to hide the {@link Contacts#CONTENT_ITEM_TYPE}
333 * profile icon.
334 */
335 public void setExcludeMimes(String[] excludeMimes) {
336 mExcludeMimes = excludeMimes;
337 }
338
Evan Millare727f182009-08-27 11:15:55 -0700339 private class QueryHandler extends AsyncQueryHandler {
340
341 public QueryHandler(ContentResolver cr) {
342 super(cr);
343 }
344
345 @Override
346 protected void onQueryComplete(int token, Object cookie, Cursor cursor) {
Jeff Sharkey0050ee32009-09-17 16:11:32 -0700347 Uri lookupUri = null;
348 Uri createUri = null;
Evan Millare727f182009-08-27 11:15:55 -0700349 boolean trigger = false;
Yorke Lee02f304f2013-01-14 08:57:43 -0800350 Bundle extras = (cookie != null) ? (Bundle) cookie : new Bundle();
Jeff Sharkey0050ee32009-09-17 16:11:32 -0700351 try {
Evan Millare727f182009-08-27 11:15:55 -0700352 switch(token) {
353 case TOKEN_PHONE_LOOKUP_AND_TRIGGER:
354 trigger = true;
Yorke Lee02f304f2013-01-14 08:57:43 -0800355 createUri = Uri.fromParts("tel", extras.getString(EXTRA_URI_CONTENT), null);
Jeff Sharkey0050ee32009-09-17 16:11:32 -0700356
Makoto Onukia362e1d2010-09-07 10:46:59 -0700357 //$FALL-THROUGH$
Evan Millare727f182009-08-27 11:15:55 -0700358 case TOKEN_PHONE_LOOKUP: {
359 if (cursor != null && cursor.moveToFirst()) {
360 long contactId = cursor.getLong(PHONE_ID_COLUMN_INDEX);
361 String lookupKey = cursor.getString(PHONE_LOOKUP_STRING_COLUMN_INDEX);
Jeff Sharkey0050ee32009-09-17 16:11:32 -0700362 lookupUri = Contacts.getLookupUri(contactId, lookupKey);
Evan Millare727f182009-08-27 11:15:55 -0700363 }
Jeff Sharkey0050ee32009-09-17 16:11:32 -0700364
Evan Millare727f182009-08-27 11:15:55 -0700365 break;
366 }
367 case TOKEN_EMAIL_LOOKUP_AND_TRIGGER:
368 trigger = true;
Yorke Lee02f304f2013-01-14 08:57:43 -0800369 createUri = Uri.fromParts("mailto",
370 extras.getString(EXTRA_URI_CONTENT), null);
Jeff Sharkey0050ee32009-09-17 16:11:32 -0700371
Makoto Onukia362e1d2010-09-07 10:46:59 -0700372 //$FALL-THROUGH$
Evan Millare727f182009-08-27 11:15:55 -0700373 case TOKEN_EMAIL_LOOKUP: {
374 if (cursor != null && cursor.moveToFirst()) {
375 long contactId = cursor.getLong(EMAIL_ID_COLUMN_INDEX);
376 String lookupKey = cursor.getString(EMAIL_LOOKUP_STRING_COLUMN_INDEX);
Jeff Sharkey0050ee32009-09-17 16:11:32 -0700377 lookupUri = Contacts.getLookupUri(contactId, lookupKey);
Evan Millare727f182009-08-27 11:15:55 -0700378 }
Makoto Onukia362e1d2010-09-07 10:46:59 -0700379 break;
Evan Millare727f182009-08-27 11:15:55 -0700380 }
381 }
382 } finally {
383 if (cursor != null) {
384 cursor.close();
385 }
386 }
387
Jeff Sharkey0050ee32009-09-17 16:11:32 -0700388 mContactUri = lookupUri;
Mark Wagnerf5935a92009-09-22 13:49:26 -0700389 onContactUriChanged();
Jeff Sharkey0050ee32009-09-17 16:11:32 -0700390
Brian Attwell7035f2b2015-02-25 20:06:08 -0800391 if (trigger && mContactUri != null) {
Daniel Lehmanne13e55b2011-08-07 19:51:28 -0700392 // Found contact, so trigger QuickContact
Brian Attwell7035f2b2015-02-25 20:06:08 -0800393 QuickContact.showQuickContact(getContext(), QuickContactBadge.this, mContactUri,
394 mExcludeMimes, mPrioritizedMimeType);
Jeff Sharkey0050ee32009-09-17 16:11:32 -0700395 } else if (createUri != null) {
396 // Prompt user to add this person to contacts
397 final Intent intent = new Intent(Intents.SHOW_OR_CREATE_CONTACT, createUri);
Yorke Lee02f304f2013-01-14 08:57:43 -0800398 if (extras != null) {
399 extras.remove(EXTRA_URI_CONTENT);
400 intent.putExtras(extras);
401 }
Jeff Sharkey0050ee32009-09-17 16:11:32 -0700402 getContext().startActivity(intent);
Evan Millare727f182009-08-27 11:15:55 -0700403 }
404 }
405 }
406}