blob: 2eae8aaff2c7d57ce7dec26e872dc4676e6b7851 [file] [log] [blame]
Jeff Sharkey3f177592009-05-18 15:23:12 -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 com.android.contacts;
18
19import com.android.contacts.NotifyingAsyncQueryHandler.QueryCompleteListener;
Jeff Sharkey3f177592009-05-18 15:23:12 -070020import com.android.contacts.SocialStreamActivity.MappingCache;
Jeff Sharkey549aa162009-05-21 01:33:30 -070021import com.android.contacts.SocialStreamActivity.Mapping;
22import com.android.internal.policy.PolicyManager;
Jeff Sharkey3f177592009-05-18 15:23:12 -070023
Jeff Sharkey549aa162009-05-21 01:33:30 -070024import android.app.Activity;
Jeff Sharkey3f177592009-05-18 15:23:12 -070025import android.content.ActivityNotFoundException;
26import android.content.ContentUris;
27import android.content.Context;
28import android.content.Intent;
29import android.content.res.Resources;
30import android.database.Cursor;
Jeff Sharkeyecedf752009-05-18 22:07:40 -070031import android.graphics.Bitmap;
32import android.graphics.BitmapFactory;
Jeff Sharkey80a193a2009-05-21 14:18:18 -070033import android.graphics.Color;
Jeff Sharkey51ca5442009-05-21 16:12:18 -070034import android.graphics.drawable.BitmapDrawable;
Jeff Sharkey3f177592009-05-18 15:23:12 -070035import android.net.Uri;
Jeff Sharkey549aa162009-05-21 01:33:30 -070036import android.provider.Contacts.Phones;
Evan Millar66388be2009-05-28 15:41:07 -070037import android.provider.ContactsContract;
38import android.provider.ContactsContract.Aggregates;
39import android.provider.ContactsContract.CommonDataKinds;
40import android.provider.ContactsContract.Data;
41import android.provider.ContactsContract.CommonDataKinds.Email;
42import android.provider.ContactsContract.CommonDataKinds.Im;
43import android.provider.ContactsContract.CommonDataKinds.Phone;
44import android.provider.ContactsContract.CommonDataKinds.Photo;
45import android.provider.ContactsContract.CommonDataKinds.Postal;
Jeff Sharkey80a193a2009-05-21 14:18:18 -070046import android.text.SpannableStringBuilder;
47import android.text.style.CharacterStyle;
48import android.text.style.ForegroundColorSpan;
49import android.text.style.StyleSpan;
Jeff Sharkey3f177592009-05-18 15:23:12 -070050import android.util.Log;
Jeff Sharkey549aa162009-05-21 01:33:30 -070051import android.view.ContextThemeWrapper;
Jeff Sharkey3f177592009-05-18 15:23:12 -070052import android.view.Gravity;
Jeff Sharkey549aa162009-05-21 01:33:30 -070053import android.view.KeyEvent;
Jeff Sharkey3f177592009-05-18 15:23:12 -070054import android.view.LayoutInflater;
Jeff Sharkey549aa162009-05-21 01:33:30 -070055import android.view.Menu;
56import android.view.MenuItem;
57import android.view.MotionEvent;
Jeff Sharkey3f177592009-05-18 15:23:12 -070058import android.view.View;
Jeff Sharkey549aa162009-05-21 01:33:30 -070059import android.view.ViewConfiguration;
Jeff Sharkey3f177592009-05-18 15:23:12 -070060import android.view.ViewGroup;
61import android.view.ViewTreeObserver;
Jeff Sharkey549aa162009-05-21 01:33:30 -070062import android.view.Window;
63import android.view.WindowManager;
Jeff Sharkey3f177592009-05-18 15:23:12 -070064import android.view.View.OnClickListener;
65import android.view.ViewTreeObserver.OnScrollChangedListener;
Jeff Sharkey549aa162009-05-21 01:33:30 -070066import android.view.accessibility.AccessibilityEvent;
Jeff Sharkey3f177592009-05-18 15:23:12 -070067import android.widget.AbsListView;
Jeff Sharkey51ca5442009-05-21 16:12:18 -070068import android.widget.AdapterView;
69import android.widget.BaseAdapter;
Jeff Sharkey3f177592009-05-18 15:23:12 -070070import android.widget.ImageView;
71import android.widget.LinearLayout;
72import android.widget.ListView;
73import android.widget.PopupWindow;
74import android.widget.TextView;
75import android.widget.Toast;
76import android.widget.AbsListView.OnScrollListener;
77import android.widget.Gallery.LayoutParams;
78
79import java.lang.ref.WeakReference;
80import java.util.ArrayList;
Jeff Sharkey549aa162009-05-21 01:33:30 -070081import java.util.Arrays;
Jeff Sharkey3f177592009-05-18 15:23:12 -070082import java.util.Collections;
83import java.util.Comparator;
84import java.util.HashMap;
85import java.util.Iterator;
Jeff Sharkey549aa162009-05-21 01:33:30 -070086import java.util.LinkedList;
Jeff Sharkey3f177592009-05-18 15:23:12 -070087import java.util.PriorityQueue;
Jeff Sharkey549aa162009-05-21 01:33:30 -070088import java.util.Set;
Jeff Sharkey3f177592009-05-18 15:23:12 -070089
90/**
Jeff Sharkey549aa162009-05-21 01:33:30 -070091 * Window that shows fast-track contact details for a specific
92 * {@link Aggregate#_ID}.
Jeff Sharkey3f177592009-05-18 15:23:12 -070093 */
Jeff Sharkey51ca5442009-05-21 16:12:18 -070094public class FastTrackWindow implements Window.Callback, QueryCompleteListener, OnClickListener, AbsListView.OnItemClickListener {
Jeff Sharkey3f177592009-05-18 15:23:12 -070095 private static final String TAG = "FastTrackWindow";
96
Jeff Sharkey549aa162009-05-21 01:33:30 -070097 /**
98 * Interface used to allow the person showing a {@link FastTrackWindow} to
99 * know when the window has been dismissed.
100 */
101 interface OnDismissListener {
102 public void onDismiss(FastTrackWindow dialog);
103 }
104
105 final Context mContext;
106 final LayoutInflater mInflater;
107 final WindowManager mWindowManager;
108 Window mWindow;
109 View mDecor;
110
111 private boolean mQuerying = false;
112 private boolean mShowing = false;
Jeff Sharkey3f177592009-05-18 15:23:12 -0700113
114 /** Mapping cache from mime-type to icons and actions */
115 private MappingCache mMappingCache;
116
Jeff Sharkey3f177592009-05-18 15:23:12 -0700117 private NotifyingAsyncQueryHandler mHandler;
Jeff Sharkey549aa162009-05-21 01:33:30 -0700118 private OnDismissListener mDismissListener;
Jeff Sharkey3f177592009-05-18 15:23:12 -0700119
Jeff Sharkey549aa162009-05-21 01:33:30 -0700120 private long mAggId;
Jeff Sharkey80a193a2009-05-21 14:18:18 -0700121 private int mAnchorX;
122 private int mAnchorY;
123 private int mAnchorHeight;
Jeff Sharkey3f177592009-05-18 15:23:12 -0700124
Jeff Sharkey549aa162009-05-21 01:33:30 -0700125 private boolean mHasProfile = false;
126 private boolean mHasActions = false;
Jeff Sharkey3f177592009-05-18 15:23:12 -0700127
Jeff Sharkey80a193a2009-05-21 14:18:18 -0700128 private View mArrowUp;
129 private View mArrowDown;
130
Jeff Sharkey549aa162009-05-21 01:33:30 -0700131 private ImageView mPhoto;
132 private ImageView mPresence;
Jeff Sharkey80a193a2009-05-21 14:18:18 -0700133 private TextView mContent;
134 private TextView mPublished;
Jeff Sharkey549aa162009-05-21 01:33:30 -0700135 private ViewGroup mTrack;
Jeff Sharkey51ca5442009-05-21 16:12:18 -0700136 private ListView mResolveList;
Jeff Sharkey549aa162009-05-21 01:33:30 -0700137
138 // TODO: read from a resource somewhere
Jeff Sharkey80a193a2009-05-21 14:18:18 -0700139 private static final int mHeight = 138;
140 private static final int mArrowHeight = 10;
141 private static final int mArrowWidth = 24;
Jeff Sharkey549aa162009-05-21 01:33:30 -0700142
143 /**
144 * Set of {@link ActionInfo} that are associated with the aggregate
145 * currently displayed by this fast-track window.
146 */
147 private ActionSet mActions = new ActionSet();
148
149 /**
150 * Specific mime-type for {@link Phone#CONTENT_ITEM_TYPE} entries that
151 * distinguishes actions that should initiate a text message.
152 */
153 public static final String MIME_SMS_ADDRESS = "vnd.android.cursor.item/sms-address";
154
155 /**
156 * Specific mime-types that should be bumped to the front of the fast-track.
157 * Other mime-types not appearing in this list follow in alphabetic order.
158 */
159 private static final String[] ORDERED_MIMETYPES = new String[] {
160 Aggregates.CONTENT_ITEM_TYPE,
161 Phones.CONTENT_ITEM_TYPE,
162 MIME_SMS_ADDRESS,
163 Email.CONTENT_ITEM_TYPE,
164 };
165
Jeff Sharkey9bd858b2009-05-21 01:33:30 -0700166// public static final int ICON_SIZE = 42;
167// public static final int ICON_PADDING = 3;
168
Jeff Sharkey549aa162009-05-21 01:33:30 -0700169 // TODO: read this status from actual query
170 private static final String STUB_STATUS = "has a really long random status message that would be far too long to read on a single device without the need for tiny reading glasses";
171
172 private static final boolean INCLUDE_PROFILE_ACTION = true;
Jeff Sharkey3f177592009-05-18 15:23:12 -0700173
Jeff Sharkeyecedf752009-05-18 22:07:40 -0700174 private static final int TOKEN_DISPLAY_NAME = 1;
175 private static final int TOKEN_DATA = 2;
Jeff Sharkey3f177592009-05-18 15:23:12 -0700176
Jeff Sharkey3f177592009-05-18 15:23:12 -0700177 /** Message to show when no activity is found to perform an action */
178 // TODO: move this value into a resources string
179 private static final String NOT_FOUND = "Couldn't find an app to handle this action";
180
Jeff Sharkey549aa162009-05-21 01:33:30 -0700181 /**
182 * Prepare a fast-track window to show in the given {@link Context}.
183 */
184 public FastTrackWindow(Context context) {
185 mContext = new ContextThemeWrapper(context, R.style.FastTrack);
186 mInflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
187 mWindowManager = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);
Jeff Sharkey3f177592009-05-18 15:23:12 -0700188
Jeff Sharkey549aa162009-05-21 01:33:30 -0700189 mWindow = PolicyManager.makeNewWindow(mContext);
190 mWindow.setCallback(this);
191 mWindow.setWindowManager(mWindowManager, null, null);
Jeff Sharkey3f177592009-05-18 15:23:12 -0700192
Jeff Sharkey549aa162009-05-21 01:33:30 -0700193 mWindow.setContentView(R.layout.fasttrack);
194
Jeff Sharkey80a193a2009-05-21 14:18:18 -0700195 mArrowUp = (View)mWindow.findViewById(R.id.arrow_up);
196 mArrowDown = (View)mWindow.findViewById(R.id.arrow_down);
197
Jeff Sharkey549aa162009-05-21 01:33:30 -0700198 mPhoto = (ImageView)mWindow.findViewById(R.id.photo);
199 mPresence = (ImageView)mWindow.findViewById(R.id.presence);
Jeff Sharkey80a193a2009-05-21 14:18:18 -0700200 mContent = (TextView)mWindow.findViewById(R.id.content);
201 mPublished = (TextView)mWindow.findViewById(R.id.published);
Jeff Sharkey549aa162009-05-21 01:33:30 -0700202 mTrack = (ViewGroup)mWindow.findViewById(R.id.fasttrack);
Jeff Sharkey51ca5442009-05-21 16:12:18 -0700203 mResolveList = (ListView)mWindow.findViewById(android.R.id.list);
Jeff Sharkey549aa162009-05-21 01:33:30 -0700204
205 // TODO: move generation of mime-type cache to more-efficient place
206 generateMappingCache();
207
Jeff Sharkey3f177592009-05-18 15:23:12 -0700208 }
209
210 /**
Jeff Sharkey549aa162009-05-21 01:33:30 -0700211 * Prepare a fast-track window to show in the given {@link Context}, and
212 * notify the given {@link OnDismissListener} each time this dialog is
213 * dismissed.
Jeff Sharkey3f177592009-05-18 15:23:12 -0700214 */
Jeff Sharkey549aa162009-05-21 01:33:30 -0700215 public FastTrackWindow(Context context, OnDismissListener dismissListener) {
216 this(context);
217 mDismissListener = dismissListener;
218 }
Jeff Sharkey3f177592009-05-18 15:23:12 -0700219
Jeff Sharkey549aa162009-05-21 01:33:30 -0700220 /**
221 * Generate {@link MappingCache} specifically for fast-track windows. This
222 * cache knows how to display {@link CommonDataKinds#PACKAGE_COMMON} data
223 * types using generic icons.
224 */
225 private void generateMappingCache() {
226 mMappingCache = MappingCache.createAndFill(mContext);
Jeff Sharkey3f177592009-05-18 15:23:12 -0700227
Jeff Sharkey549aa162009-05-21 01:33:30 -0700228 Resources res = mContext.getResources();
229 Mapping mapping;
Jeff Sharkey3f177592009-05-18 15:23:12 -0700230
Jeff Sharkey549aa162009-05-21 01:33:30 -0700231 mapping = new Mapping(CommonDataKinds.PACKAGE_COMMON, Aggregates.CONTENT_ITEM_TYPE);
232 mapping.icon = BitmapFactory.decodeResource(res, R.drawable.ic_contacts_details);
233 mMappingCache.addMapping(mapping);
Jeff Sharkey3f177592009-05-18 15:23:12 -0700234
Jeff Sharkey549aa162009-05-21 01:33:30 -0700235 mapping = new Mapping(CommonDataKinds.PACKAGE_COMMON, Phone.CONTENT_ITEM_TYPE);
236 mapping.summaryColumn = Phone.NUMBER;
237 mapping.icon = BitmapFactory.decodeResource(res, android.R.drawable.sym_action_call);
238 mMappingCache.addMapping(mapping);
Jeff Sharkey3f177592009-05-18 15:23:12 -0700239
Jeff Sharkey549aa162009-05-21 01:33:30 -0700240 mapping = new Mapping(CommonDataKinds.PACKAGE_COMMON, MIME_SMS_ADDRESS);
241 mapping.summaryColumn = Phone.NUMBER;
242 mapping.icon = BitmapFactory.decodeResource(res, R.drawable.sym_action_sms);
243 mMappingCache.addMapping(mapping);
Jeff Sharkey3f177592009-05-18 15:23:12 -0700244
Jeff Sharkey549aa162009-05-21 01:33:30 -0700245 mapping = new Mapping(CommonDataKinds.PACKAGE_COMMON, Email.CONTENT_ITEM_TYPE);
246 mapping.summaryColumn = Email.DATA;
247 mapping.icon = BitmapFactory.decodeResource(res, android.R.drawable.sym_action_email);
248 mMappingCache.addMapping(mapping);
Jeff Sharkey3f177592009-05-18 15:23:12 -0700249
Jeff Sharkey549aa162009-05-21 01:33:30 -0700250 }
Jeff Sharkey3f177592009-05-18 15:23:12 -0700251
Jeff Sharkey549aa162009-05-21 01:33:30 -0700252 /**
253 * Start showing a fast-track window for the given {@link Aggregate#_ID}
254 * pointing towards the given location.
255 */
Jeff Sharkey80a193a2009-05-21 14:18:18 -0700256 public void show(Uri aggUri, int x, int y, int height) {
Jeff Sharkey549aa162009-05-21 01:33:30 -0700257 if (mShowing || mQuerying) {
258 Log.w(TAG, "already in process of showing");
259 return;
260 }
261
262 mAggId = ContentUris.parseId(aggUri);
Jeff Sharkey80a193a2009-05-21 14:18:18 -0700263 mAnchorX = x;
264 mAnchorY = y;
265 mAnchorHeight = height;
Jeff Sharkey549aa162009-05-21 01:33:30 -0700266 mQuerying = true;
Jeff Sharkey3f177592009-05-18 15:23:12 -0700267
268 // Start data query in background
Jeff Sharkey549aa162009-05-21 01:33:30 -0700269 Uri dataUri = Uri.withAppendedPath(aggUri,
270 ContactsContract.Aggregates.Data.CONTENT_DIRECTORY);
Jeff Sharkey3f177592009-05-18 15:23:12 -0700271
Jeff Sharkey549aa162009-05-21 01:33:30 -0700272 // TODO: also query for latest status message
273 mHandler = new NotifyingAsyncQueryHandler(mContext, this);
Jeff Sharkeyecedf752009-05-18 22:07:40 -0700274 mHandler.startQuery(TOKEN_DISPLAY_NAME, null, aggUri, null, null, null, null);
Jeff Sharkey549aa162009-05-21 01:33:30 -0700275 mHandler.startQuery(TOKEN_DATA, null, dataUri, null, null, null, null);
Jeff Sharkey3f177592009-05-18 15:23:12 -0700276
277 }
278
279 /**
Jeff Sharkey80a193a2009-05-21 14:18:18 -0700280 * Show the correct callout arrow based on a {@link R.id} reference.
281 */
282 private void showArrow(int whichArrow, int requestedX) {
283 final View showArrow = (whichArrow == R.id.arrow_up) ? mArrowUp : mArrowDown;
284 final View hideArrow = (whichArrow == R.id.arrow_up) ? mArrowDown : mArrowUp;
285
286 showArrow.setVisibility(View.VISIBLE);
287 LinearLayout.LayoutParams param = (LinearLayout.LayoutParams)showArrow.getLayoutParams();
288 param.leftMargin = requestedX - mArrowWidth / 2;
289
290 hideArrow.setVisibility(View.INVISIBLE);
291 }
292
293 /**
Jeff Sharkey549aa162009-05-21 01:33:30 -0700294 * Actual internal method to show this fast-track window. Called only by
295 * {@link #considerShowing()} when all data requirements have been met.
Jeff Sharkey3f177592009-05-18 15:23:12 -0700296 */
Jeff Sharkey549aa162009-05-21 01:33:30 -0700297 private void showInternal() {
298 mDecor = mWindow.getDecorView();
299 WindowManager.LayoutParams l = mWindow.getAttributes();
Jeff Sharkey80a193a2009-05-21 14:18:18 -0700300
Jeff Sharkey549aa162009-05-21 01:33:30 -0700301 l.gravity = Gravity.TOP | Gravity.LEFT;
302 l.x = 0;
Jeff Sharkey80a193a2009-05-21 14:18:18 -0700303
304 if (mAnchorY > mHeight) {
305 // Show downwards callout when enough room
306 showArrow(R.id.arrow_down, mAnchorX);
307 l.y = mAnchorY - (mHeight - (mArrowHeight * 2) - 5);
308
309 } else {
310 // Otherwise show upwards callout
311 showArrow(R.id.arrow_up, mAnchorX);
312 l.y = mAnchorY + mAnchorHeight - 10;
313
314 }
315
Jeff Sharkey549aa162009-05-21 01:33:30 -0700316 l.width = WindowManager.LayoutParams.FILL_PARENT;
Jeff Sharkey80a193a2009-05-21 14:18:18 -0700317 l.height = mHeight;
Jeff Sharkey549aa162009-05-21 01:33:30 -0700318
319 l.dimAmount = 0.6f;
320 l.flags = WindowManager.LayoutParams.FLAG_DIM_BEHIND
321 | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
322 | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
323 | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
324 | WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR;
325
326 mWindowManager.addView(mDecor, l);
327 mShowing = true;
328 mQuerying = false;
329 }
330
331 /**
332 * Dismiss this fast-track window if showing.
333 */
334 public void dismiss() {
335 if (!mQuerying && !mShowing) {
336 Log.d(TAG, "not visible, ignore");
337 return;
338 }
339
340 // Cancel any pending queries
341 mHandler.cancelOperation(TOKEN_DISPLAY_NAME);
342 mHandler.cancelOperation(TOKEN_DATA);
343
344 // Reset all views to prepare for possible recycling
345 mPhoto.setImageResource(R.drawable.ic_contact_list_picture);
Jeff Sharkey51ca5442009-05-21 16:12:18 -0700346// mPresence.setImageDrawable(null);
347// mPublished.setText(null);
Jeff Sharkey80a193a2009-05-21 14:18:18 -0700348 mContent.setText(null);
Jeff Sharkey549aa162009-05-21 01:33:30 -0700349
350 mActions.clear();
351 mTrack.removeAllViews();
352
353 mHasProfile = false;
354 mHasActions = false;
355
356 if (mDecor == null || !mShowing) {
357 Log.d(TAG, "not showing, ignore");
358 return;
359 }
360
361 mWindowManager.removeView(mDecor);
362 mDecor = null;
363 mWindow.closeAllPanels();
364 mShowing = false;
365
366 // Notify any listeners that we've been dismissed
367 if (mDismissListener != null) {
368 mDismissListener.onDismiss(this);
Jeff Sharkey3f177592009-05-18 15:23:12 -0700369 }
370 }
371
Jeff Sharkey549aa162009-05-21 01:33:30 -0700372 /**
373 * Returns true if this fast-track window is showing or querying.
374 */
375 public boolean isShowing() {
376 return mShowing || mQuerying;
377 }
Jeff Sharkey3f177592009-05-18 15:23:12 -0700378
Jeff Sharkey549aa162009-05-21 01:33:30 -0700379 /**
380 * Consider showing this window, which will only call through to
381 * {@link #showInternal()} when all data items are present.
382 */
383 private synchronized void considerShowing() {
384 if (mHasActions && mHasProfile && !mShowing) {
385 showInternal();
Jeff Sharkey3f177592009-05-18 15:23:12 -0700386 }
387 }
388
389 /** {@inheritDoc} */
390 public void onQueryComplete(int token, Object cookie, Cursor cursor) {
Jeff Sharkeyecedf752009-05-18 22:07:40 -0700391 if (cursor == null) {
392 return;
Jeff Sharkey549aa162009-05-21 01:33:30 -0700393 } else if (token == TOKEN_DISPLAY_NAME) {
394 handleDisplayName(cursor);
395 } else if (token == TOKEN_DATA) {
396 handleData(cursor);
Jeff Sharkeyecedf752009-05-18 22:07:40 -0700397 }
Jeff Sharkeyecedf752009-05-18 22:07:40 -0700398 }
Jeff Sharkey80a193a2009-05-21 14:18:18 -0700399
400 private SpannableStringBuilder mBuilder = new SpannableStringBuilder();
401 private CharacterStyle mStyleBold = new StyleSpan(android.graphics.Typeface.BOLD);
402 private CharacterStyle mStyleBlack = new ForegroundColorSpan(Color.BLACK);
Jeff Sharkeyecedf752009-05-18 22:07:40 -0700403
404 /**
405 * Handle the result from the {@link TOKEN_DISPLAY_NAME} query.
406 */
407 private void handleDisplayName(Cursor cursor) {
Jeff Sharkeyecedf752009-05-18 22:07:40 -0700408 final int COL_DISPLAY_NAME = cursor.getColumnIndex(Aggregates.DISPLAY_NAME);
409
410 if (cursor.moveToNext()) {
411 String foundName = cursor.getString(COL_DISPLAY_NAME);
Jeff Sharkey80a193a2009-05-21 14:18:18 -0700412
413 mBuilder.clear();
414 mBuilder.append(foundName);
415 mBuilder.append(" ");
416 mBuilder.append(STUB_STATUS);
417 mBuilder.setSpan(mStyleBold, 0, foundName.length(), 0);
418 mBuilder.setSpan(mStyleBlack, 0, foundName.length(), 0);
419 mContent.setText(mBuilder);
420
421 mPublished.setText("4 hours ago");
422
Jeff Sharkeyecedf752009-05-18 22:07:40 -0700423 }
424
Jeff Sharkey549aa162009-05-21 01:33:30 -0700425 mHasProfile = true;
426 considerShowing();
427 }
428
429 /**
430 * Description of a specific, actionable {@link Data#_ID} item. May have a
431 * {@link Mapping} associated with it to find {@link RemoteViews} or icon,
432 * and may have built a summary of itself for UI display.
433 */
434 private class ActionInfo {
435 long dataId;
436 String packageName;
437 String mimeType;
438
439 Mapping mapping;
440 String summaryValue;
441
442 /**
443 * Create an action from common {@link Data} elements.
444 */
445 public ActionInfo(long dataId, String packageName, String mimeType) {
446 this.dataId = dataId;
447 this.packageName = packageName;
448 this.mimeType = mimeType;
449 }
450
451 /**
452 * Attempt to find a {@link Mapping} for the package and mime-type
453 * defined by this action. Returns true if one was found.
454 */
455 public boolean findMapping(MappingCache cache) {
456 mapping = cache.findMapping(packageName, mimeType);
457 return (mapping != null);
458 }
459
460 /**
461 * Given a {@link Cursor} pointed at the {@link Data} row associated
462 * with this action, use the {@link Mapping} to build a text summary.
463 */
464 public void buildSummary(Cursor cursor) {
465 if (mapping == null || mapping.summaryColumn == null) return;
466 int index = cursor.getColumnIndex(mapping.summaryColumn);
467 if (index != -1) {
468 summaryValue = cursor.getString(index);
469 }
470 }
471
472 /**
473 * Build an {@link Intent} that will perform this action.
474 */
475 public Intent buildIntent() {
476 // Handle well-known mime-types with special care
477 if (CommonDataKinds.Phone.CONTENT_ITEM_TYPE.equals(mimeType)) {
478 Uri callUri = Uri.parse("tel:" + Uri.encode(summaryValue));
479 return new Intent(Intent.ACTION_DIAL, callUri);
480
481 } else if (MIME_SMS_ADDRESS.equals(mimeType)) {
482 Uri smsUri = Uri.fromParts("smsto", summaryValue, null);
483 return new Intent(Intent.ACTION_SENDTO, smsUri);
484
485 } else if (CommonDataKinds.Email.CONTENT_ITEM_TYPE.equals(mimeType)) {
486 Uri mailUri = Uri.fromParts("mailto", summaryValue, null);
487 return new Intent(Intent.ACTION_SENDTO, mailUri);
488
489 }
490
491 // Otherwise fall back to default VIEW action
492 Uri dataUri = ContentUris.withAppendedId(ContactsContract.Data.CONTENT_URI, dataId);
493
494 Intent intent = new Intent(Intent.ACTION_VIEW);
495 intent.setData(dataUri);
496
497 return intent;
498 }
499 }
500
501 /**
502 * Provide a simple way of collecting one or more {@link ActionInfo} objects
503 * under a mime-type key.
504 */
505 private class ActionSet extends HashMap<String, LinkedList<ActionInfo>> {
506 private void collect(String mimeType, ActionInfo info) {
507 // Create mime-type set if needed
508 if (!containsKey(mimeType)) {
509 put(mimeType, new LinkedList<ActionInfo>());
510 }
511 LinkedList<ActionInfo> collectList = get(mimeType);
512 collectList.add(info);
513 }
Jeff Sharkeyecedf752009-05-18 22:07:40 -0700514 }
515
516 /**
517 * Handle the result from the {@link TOKEN_DATA} query.
518 */
519 private void handleData(Cursor cursor) {
Jeff Sharkey3f177592009-05-18 15:23:12 -0700520 final int COL_ID = cursor.getColumnIndex(Data._ID);
521 final int COL_PACKAGE = cursor.getColumnIndex(Data.PACKAGE);
522 final int COL_MIMETYPE = cursor.getColumnIndex(Data.MIMETYPE);
Jeff Sharkeyecedf752009-05-18 22:07:40 -0700523 final int COL_PHOTO = cursor.getColumnIndex(Photo.PHOTO);
524
Jeff Sharkey549aa162009-05-21 01:33:30 -0700525 ActionInfo info;
526
527 // Add the profile shortcut action if requested
528 if (INCLUDE_PROFILE_ACTION) {
529 final String mimeType = Aggregates.CONTENT_ITEM_TYPE;
530 info = new ActionInfo(mAggId, CommonDataKinds.PACKAGE_COMMON, mimeType);
531 if (info.findMapping(mMappingCache)) {
532 mActions.collect(mimeType, info);
533 }
534 }
Jeff Sharkey3f177592009-05-18 15:23:12 -0700535
536 while (cursor.moveToNext()) {
537 final long dataId = cursor.getLong(COL_ID);
538 final String packageName = cursor.getString(COL_PACKAGE);
539 final String mimeType = cursor.getString(COL_MIMETYPE);
540
Jeff Sharkey549aa162009-05-21 01:33:30 -0700541 // Handle when a photo appears in the various data items
542 // TODO: accept a photo only if its marked as primary
543 if (Photo.CONTENT_ITEM_TYPE.equals(mimeType)) {
Jeff Sharkeyecedf752009-05-18 22:07:40 -0700544 byte[] photoBlob = cursor.getBlob(COL_PHOTO);
545 Bitmap photoBitmap = BitmapFactory.decodeByteArray(photoBlob, 0, photoBlob.length);
Jeff Sharkey549aa162009-05-21 01:33:30 -0700546 mPhoto.setImageBitmap(photoBitmap);
Jeff Sharkey69126682009-05-18 21:33:41 -0700547 continue;
Jeff Sharkey3f177592009-05-18 15:23:12 -0700548 }
549
Jeff Sharkey549aa162009-05-21 01:33:30 -0700550 // Build an action for this data entry, find a mapping to a UI
551 // element, build its summary from the cursor, and collect it along
552 // with all others of this mime-type.
553 info = new ActionInfo(dataId, packageName, mimeType);
554 if (info.findMapping(mMappingCache)) {
555 info.buildSummary(cursor);
556 mActions.collect(info.mimeType, info);
Jeff Sharkey3f177592009-05-18 15:23:12 -0700557 }
558
Jeff Sharkey549aa162009-05-21 01:33:30 -0700559 // If phone number, also insert as text message action
560 if (Phones.CONTENT_ITEM_TYPE.equals(mimeType)) {
561 info = new ActionInfo(dataId, packageName, MIME_SMS_ADDRESS);
562 if (info.findMapping(mMappingCache)) {
563 info.buildSummary(cursor);
564 mActions.collect(info.mimeType, info);
Jeff Sharkey3f177592009-05-18 15:23:12 -0700565 }
Jeff Sharkey549aa162009-05-21 01:33:30 -0700566 }
Jeff Sharkey3f177592009-05-18 15:23:12 -0700567 }
568
569 cursor.close();
570
Jeff Sharkey549aa162009-05-21 01:33:30 -0700571 // Turn our list of actions into UI elements, starting with common types
572 Set<String> containedTypes = mActions.keySet();
573 for (String mimeType : ORDERED_MIMETYPES) {
574 if (containedTypes.contains(mimeType)) {
575 mTrack.addView(inflateAction(mimeType));
576 containedTypes.remove(mimeType);
Jeff Sharkey3f177592009-05-18 15:23:12 -0700577 }
Jeff Sharkey3f177592009-05-18 15:23:12 -0700578 }
579
Jeff Sharkey549aa162009-05-21 01:33:30 -0700580 // Then continue with remaining mime-types in alphabetical order
581 String[] remainingTypes = containedTypes.toArray(new String[containedTypes.size()]);
582 Arrays.sort(remainingTypes);
583 for (String mimeType : remainingTypes) {
584 mTrack.addView(inflateAction(mimeType));
585 }
586
587 mHasActions = true;
588 considerShowing();
Jeff Sharkey3f177592009-05-18 15:23:12 -0700589 }
590
591 /**
Jeff Sharkey549aa162009-05-21 01:33:30 -0700592 * Inflate the in-track view for the action of the given mime-type. Will use
593 * the icon provided by the {@link Mapping}.
Jeff Sharkey3f177592009-05-18 15:23:12 -0700594 */
Jeff Sharkey549aa162009-05-21 01:33:30 -0700595 private View inflateAction(String mimeType) {
596 ImageView view = (ImageView)mInflater.inflate(R.layout.fasttrack_item, mTrack, false);
Jeff Sharkey3f177592009-05-18 15:23:12 -0700597
Jeff Sharkey549aa162009-05-21 01:33:30 -0700598 // Add direct intent if single child, otherwise flag for multiple
599 LinkedList<ActionInfo> children = mActions.get(mimeType);
600 ActionInfo firstInfo = children.get(0);
Jeff Sharkey51ca5442009-05-21 16:12:18 -0700601// if (children.size() == 1) {
Jeff Sharkey549aa162009-05-21 01:33:30 -0700602 view.setTag(firstInfo.buildIntent());
Jeff Sharkey51ca5442009-05-21 16:12:18 -0700603// } else {
604// view.setTag(children);
605// }
Jeff Sharkey3f177592009-05-18 15:23:12 -0700606
Jeff Sharkey549aa162009-05-21 01:33:30 -0700607 // Set icon and listen for clicks
608 view.setImageBitmap(firstInfo.mapping.icon);
609 view.setOnClickListener(this);
610 return view;
611 }
Jeff Sharkey51ca5442009-05-21 16:12:18 -0700612
613 /** {@inheritDoc} */
614 public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
615 // Pass list item clicks along so that Intents are handled uniformly
616 onClick(view);
617 }
Jeff Sharkey3f177592009-05-18 15:23:12 -0700618
Jeff Sharkey549aa162009-05-21 01:33:30 -0700619 /** {@inheritDoc} */
620 public void onClick(View v) {
621 final Object tag = v.getTag();
622 if (tag instanceof Intent) {
Jeff Sharkey51ca5442009-05-21 16:12:18 -0700623 // Hide the resolution list, if present
624 mResolveList.setVisibility(View.GONE);
625
Jeff Sharkey549aa162009-05-21 01:33:30 -0700626 // Incoming tag is concrete intent, so launch
627 try {
628 mContext.startActivity((Intent)tag);
629 } catch (ActivityNotFoundException e) {
630 Log.w(TAG, NOT_FOUND);
631 Toast.makeText(mContext, NOT_FOUND, Toast.LENGTH_SHORT).show();
632 }
Jeff Sharkey51ca5442009-05-21 16:12:18 -0700633// } else if (tag instanceof LinkedList) {
634// // Incoming tag is a mime-type, so show resolution list
635// final LinkedList<ActionInfo> children = (LinkedList<ActionInfo>)tag;
636// Log.d(TAG, "found chidlren=" + children);
637//
638// mResolveList.setVisibility(View.VISIBLE);
639// mResolveList.setOnItemClickListener(this);
640// mResolveList.setAdapter(new BaseAdapter() {
641// public int getCount() {
642// return children.size();
643// }
644//
645// public Object getItem(int position) {
646// return children.get(position);
647// }
648//
649// public long getItemId(int position) {
650// return position;
651// }
652//
653// public View getView(int position, View convertView, ViewGroup parent) {
654// if (convertView == null) {
655// convertView = mInflater.inflate(R.layout.fasttrack_resolve_item, parent, false);
656// }
657//
658// // Set action title based on summary value
659// ActionInfo info = (ActionInfo)getItem(position);
660// TextView textView = (TextView)convertView;
661// textView.setText(info.summaryValue);
662// textView.setTag(info.buildIntent());
663// textView.setCompoundDrawablesWithIntrinsicBounds(
664// new BitmapDrawable(info.mapping.icon), null, null, null);
665//
666// return convertView;
667// }
668// });
669//
670// // Make sure we resize to make room for ListView
671// onWindowAttributesChanged(mWindow.getAttributes());
672//
673// // TODO: show drop-down resolution list
674// Log.d(TAG, "would show list between several options here");
Jeff Sharkey549aa162009-05-21 01:33:30 -0700675
676 }
677 }
678
679 /** {@inheritDoc} */
680 public boolean dispatchKeyEvent(KeyEvent event) {
681 return mWindow.superDispatchKeyEvent(event);
682 }
683
684 /** {@inheritDoc} */
685 public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
686 // TODO: make this window accessible
687 return false;
688 }
689
690 /** {@inheritDoc} */
691 public boolean dispatchTouchEvent(MotionEvent event) {
692 if (event.getAction() == MotionEvent.ACTION_OUTSIDE) {
693 dismiss();
694 return true;
695 }
696 return mWindow.superDispatchTouchEvent(event);
697 }
698
699 /** {@inheritDoc} */
700 public boolean dispatchTrackballEvent(MotionEvent event) {
701 return mWindow.superDispatchTrackballEvent(event);
702 }
703
704 /** {@inheritDoc} */
705 public void onContentChanged() {
706 }
707
708 /** {@inheritDoc} */
709 public boolean onCreatePanelMenu(int featureId, Menu menu) {
710 return false;
711 }
712
713 /** {@inheritDoc} */
714 public View onCreatePanelView(int featureId) {
715 return null;
716 }
717
718 /** {@inheritDoc} */
719 public boolean onMenuItemSelected(int featureId, MenuItem item) {
720 return false;
721 }
722
723 /** {@inheritDoc} */
724 public boolean onMenuOpened(int featureId, Menu menu) {
725 return false;
726 }
727
728 /** {@inheritDoc} */
729 public void onPanelClosed(int featureId, Menu menu) {
730 }
731
732 /** {@inheritDoc} */
733 public boolean onPreparePanel(int featureId, View view, Menu menu) {
734 return false;
735 }
736
737 /** {@inheritDoc} */
738 public boolean onSearchRequested() {
739 return false;
740 }
741
742 /** {@inheritDoc} */
743 public void onWindowAttributesChanged(android.view.WindowManager.LayoutParams attrs) {
744 if (mDecor != null) {
745 mWindowManager.updateViewLayout(mDecor, attrs);
746 }
747 }
748
749 /** {@inheritDoc} */
750 public void onWindowFocusChanged(boolean hasFocus) {
Jeff Sharkey3f177592009-05-18 15:23:12 -0700751 }
752}