blob: ecff1bd9a6781956f14b457ba40dcbb44ae8c9f6 [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 -070023import com.android.providers.contacts2.ContactsContract;
Jeff Sharkeyecedf752009-05-18 22:07:40 -070024import com.android.providers.contacts2.ContactsContract.Aggregates;
Jeff Sharkey3f177592009-05-18 15:23:12 -070025import com.android.providers.contacts2.ContactsContract.CommonDataKinds;
26import com.android.providers.contacts2.ContactsContract.Data;
27import com.android.providers.contacts2.ContactsContract.CommonDataKinds.Email;
28import com.android.providers.contacts2.ContactsContract.CommonDataKinds.Im;
29import com.android.providers.contacts2.ContactsContract.CommonDataKinds.Phone;
Jeff Sharkeyecedf752009-05-18 22:07:40 -070030import com.android.providers.contacts2.ContactsContract.CommonDataKinds.Photo;
Jeff Sharkey3f177592009-05-18 15:23:12 -070031import com.android.providers.contacts2.ContactsContract.CommonDataKinds.Postal;
32
Jeff Sharkey549aa162009-05-21 01:33:30 -070033import android.app.Activity;
Jeff Sharkey3f177592009-05-18 15:23:12 -070034import android.content.ActivityNotFoundException;
35import android.content.ContentUris;
36import android.content.Context;
37import android.content.Intent;
38import android.content.res.Resources;
39import android.database.Cursor;
Jeff Sharkeyecedf752009-05-18 22:07:40 -070040import android.graphics.Bitmap;
41import android.graphics.BitmapFactory;
Jeff Sharkey3f177592009-05-18 15:23:12 -070042import android.net.Uri;
Jeff Sharkey549aa162009-05-21 01:33:30 -070043import android.provider.Contacts.Phones;
Jeff Sharkey3f177592009-05-18 15:23:12 -070044import android.util.Log;
Jeff Sharkey549aa162009-05-21 01:33:30 -070045import android.view.ContextThemeWrapper;
Jeff Sharkey3f177592009-05-18 15:23:12 -070046import android.view.Gravity;
Jeff Sharkey549aa162009-05-21 01:33:30 -070047import android.view.KeyEvent;
Jeff Sharkey3f177592009-05-18 15:23:12 -070048import android.view.LayoutInflater;
Jeff Sharkey549aa162009-05-21 01:33:30 -070049import android.view.Menu;
50import android.view.MenuItem;
51import android.view.MotionEvent;
Jeff Sharkey3f177592009-05-18 15:23:12 -070052import android.view.View;
Jeff Sharkey549aa162009-05-21 01:33:30 -070053import android.view.ViewConfiguration;
Jeff Sharkey3f177592009-05-18 15:23:12 -070054import android.view.ViewGroup;
55import android.view.ViewTreeObserver;
Jeff Sharkey549aa162009-05-21 01:33:30 -070056import android.view.Window;
57import android.view.WindowManager;
Jeff Sharkey3f177592009-05-18 15:23:12 -070058import android.view.View.OnClickListener;
59import android.view.ViewTreeObserver.OnScrollChangedListener;
Jeff Sharkey549aa162009-05-21 01:33:30 -070060import android.view.accessibility.AccessibilityEvent;
Jeff Sharkey3f177592009-05-18 15:23:12 -070061import android.widget.AbsListView;
62import android.widget.ImageView;
63import android.widget.LinearLayout;
64import android.widget.ListView;
65import android.widget.PopupWindow;
66import android.widget.TextView;
67import android.widget.Toast;
68import android.widget.AbsListView.OnScrollListener;
69import android.widget.Gallery.LayoutParams;
70
71import java.lang.ref.WeakReference;
72import java.util.ArrayList;
Jeff Sharkey549aa162009-05-21 01:33:30 -070073import java.util.Arrays;
Jeff Sharkey3f177592009-05-18 15:23:12 -070074import java.util.Collections;
75import java.util.Comparator;
76import java.util.HashMap;
77import java.util.Iterator;
Jeff Sharkey549aa162009-05-21 01:33:30 -070078import java.util.LinkedList;
Jeff Sharkey3f177592009-05-18 15:23:12 -070079import java.util.PriorityQueue;
Jeff Sharkey549aa162009-05-21 01:33:30 -070080import java.util.Set;
Jeff Sharkey3f177592009-05-18 15:23:12 -070081
82/**
Jeff Sharkey549aa162009-05-21 01:33:30 -070083 * Window that shows fast-track contact details for a specific
84 * {@link Aggregate#_ID}.
Jeff Sharkey3f177592009-05-18 15:23:12 -070085 */
Jeff Sharkey549aa162009-05-21 01:33:30 -070086public class FastTrackWindow implements Window.Callback, QueryCompleteListener, OnClickListener {
Jeff Sharkey3f177592009-05-18 15:23:12 -070087 private static final String TAG = "FastTrackWindow";
88
Jeff Sharkey549aa162009-05-21 01:33:30 -070089 /**
90 * Interface used to allow the person showing a {@link FastTrackWindow} to
91 * know when the window has been dismissed.
92 */
93 interface OnDismissListener {
94 public void onDismiss(FastTrackWindow dialog);
95 }
96
97 final Context mContext;
98 final LayoutInflater mInflater;
99 final WindowManager mWindowManager;
100 Window mWindow;
101 View mDecor;
102
103 private boolean mQuerying = false;
104 private boolean mShowing = false;
Jeff Sharkey3f177592009-05-18 15:23:12 -0700105
106 /** Mapping cache from mime-type to icons and actions */
107 private MappingCache mMappingCache;
108
Jeff Sharkey3f177592009-05-18 15:23:12 -0700109 private NotifyingAsyncQueryHandler mHandler;
Jeff Sharkey549aa162009-05-21 01:33:30 -0700110 private OnDismissListener mDismissListener;
Jeff Sharkey3f177592009-05-18 15:23:12 -0700111
Jeff Sharkey549aa162009-05-21 01:33:30 -0700112 private long mAggId;
113 private int mRequestedY;
114 private int mSlop;
Jeff Sharkey3f177592009-05-18 15:23:12 -0700115
Jeff Sharkey549aa162009-05-21 01:33:30 -0700116 private boolean mHasProfile = false;
117 private boolean mHasActions = false;
Jeff Sharkey3f177592009-05-18 15:23:12 -0700118
Jeff Sharkey549aa162009-05-21 01:33:30 -0700119 private ImageView mPhoto;
120 private ImageView mPresence;
121 private TextView mDisplayName;
122 private TextView mStatus;
123 private ViewGroup mTrack;
124
125 // TODO: read from a resource somewhere
126 private static final int mHeight = 150;
127 private static final int mAnchorHeight = 20;
128
129 /**
130 * Set of {@link ActionInfo} that are associated with the aggregate
131 * currently displayed by this fast-track window.
132 */
133 private ActionSet mActions = new ActionSet();
134
135 /**
136 * Specific mime-type for {@link Phone#CONTENT_ITEM_TYPE} entries that
137 * distinguishes actions that should initiate a text message.
138 */
139 public static final String MIME_SMS_ADDRESS = "vnd.android.cursor.item/sms-address";
140
141 /**
142 * Specific mime-types that should be bumped to the front of the fast-track.
143 * Other mime-types not appearing in this list follow in alphabetic order.
144 */
145 private static final String[] ORDERED_MIMETYPES = new String[] {
146 Aggregates.CONTENT_ITEM_TYPE,
147 Phones.CONTENT_ITEM_TYPE,
148 MIME_SMS_ADDRESS,
149 Email.CONTENT_ITEM_TYPE,
150 };
151
152// public static final int ICON_SIZE = 42;
153// public static final int ICON_PADDING = 3;
154
155 // TODO: read this status from actual query
156 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";
157
158 private static final boolean INCLUDE_PROFILE_ACTION = true;
Jeff Sharkey3f177592009-05-18 15:23:12 -0700159
Jeff Sharkeyecedf752009-05-18 22:07:40 -0700160 private static final int TOKEN_DISPLAY_NAME = 1;
161 private static final int TOKEN_DATA = 2;
Jeff Sharkey3f177592009-05-18 15:23:12 -0700162
Jeff Sharkey3f177592009-05-18 15:23:12 -0700163 /** Message to show when no activity is found to perform an action */
164 // TODO: move this value into a resources string
165 private static final String NOT_FOUND = "Couldn't find an app to handle this action";
166
Jeff Sharkey549aa162009-05-21 01:33:30 -0700167 /**
168 * Prepare a fast-track window to show in the given {@link Context}.
169 */
170 public FastTrackWindow(Context context) {
171 mContext = new ContextThemeWrapper(context, R.style.FastTrack);
172 mInflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
173 mWindowManager = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);
Jeff Sharkey3f177592009-05-18 15:23:12 -0700174
Jeff Sharkey549aa162009-05-21 01:33:30 -0700175 mSlop = ViewConfiguration.get(mContext).getScaledWindowTouchSlop();
Jeff Sharkey3f177592009-05-18 15:23:12 -0700176
Jeff Sharkey549aa162009-05-21 01:33:30 -0700177 mWindow = PolicyManager.makeNewWindow(mContext);
178 mWindow.setCallback(this);
179 mWindow.setWindowManager(mWindowManager, null, null);
Jeff Sharkey3f177592009-05-18 15:23:12 -0700180
Jeff Sharkey549aa162009-05-21 01:33:30 -0700181 mWindow.setContentView(R.layout.fasttrack);
182
183 mPhoto = (ImageView)mWindow.findViewById(R.id.photo);
184 mPresence = (ImageView)mWindow.findViewById(R.id.presence);
185 mDisplayName = (TextView)mWindow.findViewById(R.id.displayname);
186 mStatus = (TextView)mWindow.findViewById(R.id.status);
187 mTrack = (ViewGroup)mWindow.findViewById(R.id.fasttrack);
188
189 // TODO: move generation of mime-type cache to more-efficient place
190 generateMappingCache();
191
Jeff Sharkey3f177592009-05-18 15:23:12 -0700192 }
193
194 /**
Jeff Sharkey549aa162009-05-21 01:33:30 -0700195 * Prepare a fast-track window to show in the given {@link Context}, and
196 * notify the given {@link OnDismissListener} each time this dialog is
197 * dismissed.
Jeff Sharkey3f177592009-05-18 15:23:12 -0700198 */
Jeff Sharkey549aa162009-05-21 01:33:30 -0700199 public FastTrackWindow(Context context, OnDismissListener dismissListener) {
200 this(context);
201 mDismissListener = dismissListener;
202 }
Jeff Sharkey3f177592009-05-18 15:23:12 -0700203
Jeff Sharkey549aa162009-05-21 01:33:30 -0700204 /**
205 * Generate {@link MappingCache} specifically for fast-track windows. This
206 * cache knows how to display {@link CommonDataKinds#PACKAGE_COMMON} data
207 * types using generic icons.
208 */
209 private void generateMappingCache() {
210 mMappingCache = MappingCache.createAndFill(mContext);
Jeff Sharkey3f177592009-05-18 15:23:12 -0700211
Jeff Sharkey549aa162009-05-21 01:33:30 -0700212 Resources res = mContext.getResources();
213 Mapping mapping;
Jeff Sharkey3f177592009-05-18 15:23:12 -0700214
Jeff Sharkey549aa162009-05-21 01:33:30 -0700215 mapping = new Mapping(CommonDataKinds.PACKAGE_COMMON, Aggregates.CONTENT_ITEM_TYPE);
216 mapping.icon = BitmapFactory.decodeResource(res, R.drawable.ic_contacts_details);
217 mMappingCache.addMapping(mapping);
Jeff Sharkey3f177592009-05-18 15:23:12 -0700218
Jeff Sharkey549aa162009-05-21 01:33:30 -0700219 mapping = new Mapping(CommonDataKinds.PACKAGE_COMMON, Phone.CONTENT_ITEM_TYPE);
220 mapping.summaryColumn = Phone.NUMBER;
221 mapping.icon = BitmapFactory.decodeResource(res, android.R.drawable.sym_action_call);
222 mMappingCache.addMapping(mapping);
Jeff Sharkey3f177592009-05-18 15:23:12 -0700223
Jeff Sharkey549aa162009-05-21 01:33:30 -0700224 mapping = new Mapping(CommonDataKinds.PACKAGE_COMMON, MIME_SMS_ADDRESS);
225 mapping.summaryColumn = Phone.NUMBER;
226 mapping.icon = BitmapFactory.decodeResource(res, R.drawable.sym_action_sms);
227 mMappingCache.addMapping(mapping);
Jeff Sharkey3f177592009-05-18 15:23:12 -0700228
Jeff Sharkey549aa162009-05-21 01:33:30 -0700229 mapping = new Mapping(CommonDataKinds.PACKAGE_COMMON, Email.CONTENT_ITEM_TYPE);
230 mapping.summaryColumn = Email.DATA;
231 mapping.icon = BitmapFactory.decodeResource(res, android.R.drawable.sym_action_email);
232 mMappingCache.addMapping(mapping);
Jeff Sharkey3f177592009-05-18 15:23:12 -0700233
Jeff Sharkey549aa162009-05-21 01:33:30 -0700234 }
Jeff Sharkey3f177592009-05-18 15:23:12 -0700235
Jeff Sharkey549aa162009-05-21 01:33:30 -0700236 /**
237 * Start showing a fast-track window for the given {@link Aggregate#_ID}
238 * pointing towards the given location.
239 */
240 public void show(Uri aggUri, int y) {
241 if (mShowing || mQuerying) {
242 Log.w(TAG, "already in process of showing");
243 return;
244 }
245
246 mAggId = ContentUris.parseId(aggUri);
247 mRequestedY = y;
248 mQuerying = true;
Jeff Sharkey3f177592009-05-18 15:23:12 -0700249
250 // Start data query in background
Jeff Sharkey549aa162009-05-21 01:33:30 -0700251 Uri dataUri = Uri.withAppendedPath(aggUri,
252 ContactsContract.Aggregates.Data.CONTENT_DIRECTORY);
Jeff Sharkey3f177592009-05-18 15:23:12 -0700253
Jeff Sharkey549aa162009-05-21 01:33:30 -0700254 // TODO: also query for latest status message
255 mHandler = new NotifyingAsyncQueryHandler(mContext, this);
Jeff Sharkeyecedf752009-05-18 22:07:40 -0700256 mHandler.startQuery(TOKEN_DISPLAY_NAME, null, aggUri, null, null, null, null);
Jeff Sharkey549aa162009-05-21 01:33:30 -0700257 mHandler.startQuery(TOKEN_DATA, null, dataUri, null, null, null, null);
Jeff Sharkey3f177592009-05-18 15:23:12 -0700258
259 }
260
261 /**
Jeff Sharkey549aa162009-05-21 01:33:30 -0700262 * Actual internal method to show this fast-track window. Called only by
263 * {@link #considerShowing()} when all data requirements have been met.
Jeff Sharkey3f177592009-05-18 15:23:12 -0700264 */
Jeff Sharkey549aa162009-05-21 01:33:30 -0700265 private void showInternal() {
266 mDecor = mWindow.getDecorView();
267 WindowManager.LayoutParams l = mWindow.getAttributes();
268
269 l.gravity = Gravity.TOP | Gravity.LEFT;
270 l.x = 0;
271 l.y = mRequestedY - mHeight;
272
273 l.width = WindowManager.LayoutParams.FILL_PARENT;
274 l.height = mHeight + mAnchorHeight;
275
276 l.dimAmount = 0.6f;
277 l.flags = WindowManager.LayoutParams.FLAG_DIM_BEHIND
278 | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
279 | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
280 | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
281 | WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR;
282
283 mWindowManager.addView(mDecor, l);
284 mShowing = true;
285 mQuerying = false;
286 }
287
288 /**
289 * Dismiss this fast-track window if showing.
290 */
291 public void dismiss() {
292 if (!mQuerying && !mShowing) {
293 Log.d(TAG, "not visible, ignore");
294 return;
295 }
296
297 // Cancel any pending queries
298 mHandler.cancelOperation(TOKEN_DISPLAY_NAME);
299 mHandler.cancelOperation(TOKEN_DATA);
300
301 // Reset all views to prepare for possible recycling
302 mPhoto.setImageResource(R.drawable.ic_contact_list_picture);
303 mPresence.setImageDrawable(null);
304 mDisplayName.setText(null);
305 mStatus.setText(null);
306
307 mActions.clear();
308 mTrack.removeAllViews();
309
310 mHasProfile = false;
311 mHasActions = false;
312
313 if (mDecor == null || !mShowing) {
314 Log.d(TAG, "not showing, ignore");
315 return;
316 }
317
318 mWindowManager.removeView(mDecor);
319 mDecor = null;
320 mWindow.closeAllPanels();
321 mShowing = false;
322
323 // Notify any listeners that we've been dismissed
324 if (mDismissListener != null) {
325 mDismissListener.onDismiss(this);
Jeff Sharkey3f177592009-05-18 15:23:12 -0700326 }
327 }
328
Jeff Sharkey549aa162009-05-21 01:33:30 -0700329 /**
330 * Returns true if this fast-track window is showing or querying.
331 */
332 public boolean isShowing() {
333 return mShowing || mQuerying;
334 }
Jeff Sharkey3f177592009-05-18 15:23:12 -0700335
Jeff Sharkey549aa162009-05-21 01:33:30 -0700336 /**
337 * Consider showing this window, which will only call through to
338 * {@link #showInternal()} when all data items are present.
339 */
340 private synchronized void considerShowing() {
341 if (mHasActions && mHasProfile && !mShowing) {
342 showInternal();
Jeff Sharkey3f177592009-05-18 15:23:12 -0700343 }
344 }
345
346 /** {@inheritDoc} */
347 public void onQueryComplete(int token, Object cookie, Cursor cursor) {
Jeff Sharkeyecedf752009-05-18 22:07:40 -0700348 if (cursor == null) {
349 return;
Jeff Sharkey549aa162009-05-21 01:33:30 -0700350 } else if (token == TOKEN_DISPLAY_NAME) {
351 handleDisplayName(cursor);
352 } else if (token == TOKEN_DATA) {
353 handleData(cursor);
Jeff Sharkeyecedf752009-05-18 22:07:40 -0700354 }
Jeff Sharkeyecedf752009-05-18 22:07:40 -0700355 }
356
357 /**
358 * Handle the result from the {@link TOKEN_DISPLAY_NAME} query.
359 */
360 private void handleDisplayName(Cursor cursor) {
Jeff Sharkeyecedf752009-05-18 22:07:40 -0700361 final int COL_DISPLAY_NAME = cursor.getColumnIndex(Aggregates.DISPLAY_NAME);
362
363 if (cursor.moveToNext()) {
364 String foundName = cursor.getString(COL_DISPLAY_NAME);
Jeff Sharkey549aa162009-05-21 01:33:30 -0700365 mDisplayName.setText(foundName);
366 mStatus.setText(STUB_STATUS);
Jeff Sharkeyecedf752009-05-18 22:07:40 -0700367 }
368
Jeff Sharkey549aa162009-05-21 01:33:30 -0700369 mHasProfile = true;
370 considerShowing();
371 }
372
373 /**
374 * Description of a specific, actionable {@link Data#_ID} item. May have a
375 * {@link Mapping} associated with it to find {@link RemoteViews} or icon,
376 * and may have built a summary of itself for UI display.
377 */
378 private class ActionInfo {
379 long dataId;
380 String packageName;
381 String mimeType;
382
383 Mapping mapping;
384 String summaryValue;
385
386 /**
387 * Create an action from common {@link Data} elements.
388 */
389 public ActionInfo(long dataId, String packageName, String mimeType) {
390 this.dataId = dataId;
391 this.packageName = packageName;
392 this.mimeType = mimeType;
393 }
394
395 /**
396 * Attempt to find a {@link Mapping} for the package and mime-type
397 * defined by this action. Returns true if one was found.
398 */
399 public boolean findMapping(MappingCache cache) {
400 mapping = cache.findMapping(packageName, mimeType);
401 return (mapping != null);
402 }
403
404 /**
405 * Given a {@link Cursor} pointed at the {@link Data} row associated
406 * with this action, use the {@link Mapping} to build a text summary.
407 */
408 public void buildSummary(Cursor cursor) {
409 if (mapping == null || mapping.summaryColumn == null) return;
410 int index = cursor.getColumnIndex(mapping.summaryColumn);
411 if (index != -1) {
412 summaryValue = cursor.getString(index);
413 }
414 }
415
416 /**
417 * Build an {@link Intent} that will perform this action.
418 */
419 public Intent buildIntent() {
420 // Handle well-known mime-types with special care
421 if (CommonDataKinds.Phone.CONTENT_ITEM_TYPE.equals(mimeType)) {
422 Uri callUri = Uri.parse("tel:" + Uri.encode(summaryValue));
423 return new Intent(Intent.ACTION_DIAL, callUri);
424
425 } else if (MIME_SMS_ADDRESS.equals(mimeType)) {
426 Uri smsUri = Uri.fromParts("smsto", summaryValue, null);
427 return new Intent(Intent.ACTION_SENDTO, smsUri);
428
429 } else if (CommonDataKinds.Email.CONTENT_ITEM_TYPE.equals(mimeType)) {
430 Uri mailUri = Uri.fromParts("mailto", summaryValue, null);
431 return new Intent(Intent.ACTION_SENDTO, mailUri);
432
433 }
434
435 // Otherwise fall back to default VIEW action
436 Uri dataUri = ContentUris.withAppendedId(ContactsContract.Data.CONTENT_URI, dataId);
437
438 Intent intent = new Intent(Intent.ACTION_VIEW);
439 intent.setData(dataUri);
440
441 return intent;
442 }
443 }
444
445 /**
446 * Provide a simple way of collecting one or more {@link ActionInfo} objects
447 * under a mime-type key.
448 */
449 private class ActionSet extends HashMap<String, LinkedList<ActionInfo>> {
450 private void collect(String mimeType, ActionInfo info) {
451 // Create mime-type set if needed
452 if (!containsKey(mimeType)) {
453 put(mimeType, new LinkedList<ActionInfo>());
454 }
455 LinkedList<ActionInfo> collectList = get(mimeType);
456 collectList.add(info);
457 }
Jeff Sharkeyecedf752009-05-18 22:07:40 -0700458 }
459
460 /**
461 * Handle the result from the {@link TOKEN_DATA} query.
462 */
463 private void handleData(Cursor cursor) {
Jeff Sharkey3f177592009-05-18 15:23:12 -0700464 final int COL_ID = cursor.getColumnIndex(Data._ID);
465 final int COL_PACKAGE = cursor.getColumnIndex(Data.PACKAGE);
466 final int COL_MIMETYPE = cursor.getColumnIndex(Data.MIMETYPE);
Jeff Sharkeyecedf752009-05-18 22:07:40 -0700467 final int COL_PHOTO = cursor.getColumnIndex(Photo.PHOTO);
468
Jeff Sharkey549aa162009-05-21 01:33:30 -0700469 ActionInfo info;
470
471 // Add the profile shortcut action if requested
472 if (INCLUDE_PROFILE_ACTION) {
473 final String mimeType = Aggregates.CONTENT_ITEM_TYPE;
474 info = new ActionInfo(mAggId, CommonDataKinds.PACKAGE_COMMON, mimeType);
475 if (info.findMapping(mMappingCache)) {
476 mActions.collect(mimeType, info);
477 }
478 }
Jeff Sharkey3f177592009-05-18 15:23:12 -0700479
480 while (cursor.moveToNext()) {
481 final long dataId = cursor.getLong(COL_ID);
482 final String packageName = cursor.getString(COL_PACKAGE);
483 final String mimeType = cursor.getString(COL_MIMETYPE);
484
Jeff Sharkey549aa162009-05-21 01:33:30 -0700485 // Handle when a photo appears in the various data items
486 // TODO: accept a photo only if its marked as primary
487 if (Photo.CONTENT_ITEM_TYPE.equals(mimeType)) {
Jeff Sharkeyecedf752009-05-18 22:07:40 -0700488 byte[] photoBlob = cursor.getBlob(COL_PHOTO);
489 Bitmap photoBitmap = BitmapFactory.decodeByteArray(photoBlob, 0, photoBlob.length);
Jeff Sharkey549aa162009-05-21 01:33:30 -0700490 mPhoto.setImageBitmap(photoBitmap);
Jeff Sharkey69126682009-05-18 21:33:41 -0700491 continue;
Jeff Sharkey3f177592009-05-18 15:23:12 -0700492 }
493
Jeff Sharkey549aa162009-05-21 01:33:30 -0700494 // Build an action for this data entry, find a mapping to a UI
495 // element, build its summary from the cursor, and collect it along
496 // with all others of this mime-type.
497 info = new ActionInfo(dataId, packageName, mimeType);
498 if (info.findMapping(mMappingCache)) {
499 info.buildSummary(cursor);
500 mActions.collect(info.mimeType, info);
Jeff Sharkey3f177592009-05-18 15:23:12 -0700501 }
502
Jeff Sharkey549aa162009-05-21 01:33:30 -0700503 // If phone number, also insert as text message action
504 if (Phones.CONTENT_ITEM_TYPE.equals(mimeType)) {
505 info = new ActionInfo(dataId, packageName, MIME_SMS_ADDRESS);
506 if (info.findMapping(mMappingCache)) {
507 info.buildSummary(cursor);
508 mActions.collect(info.mimeType, info);
Jeff Sharkey3f177592009-05-18 15:23:12 -0700509 }
Jeff Sharkey549aa162009-05-21 01:33:30 -0700510 }
Jeff Sharkey3f177592009-05-18 15:23:12 -0700511 }
512
513 cursor.close();
514
Jeff Sharkey549aa162009-05-21 01:33:30 -0700515 // Turn our list of actions into UI elements, starting with common types
516 Set<String> containedTypes = mActions.keySet();
517 for (String mimeType : ORDERED_MIMETYPES) {
518 if (containedTypes.contains(mimeType)) {
519 mTrack.addView(inflateAction(mimeType));
520 containedTypes.remove(mimeType);
Jeff Sharkey3f177592009-05-18 15:23:12 -0700521 }
Jeff Sharkey3f177592009-05-18 15:23:12 -0700522 }
523
Jeff Sharkey549aa162009-05-21 01:33:30 -0700524 // Then continue with remaining mime-types in alphabetical order
525 String[] remainingTypes = containedTypes.toArray(new String[containedTypes.size()]);
526 Arrays.sort(remainingTypes);
527 for (String mimeType : remainingTypes) {
528 mTrack.addView(inflateAction(mimeType));
529 }
530
531 mHasActions = true;
532 considerShowing();
Jeff Sharkey3f177592009-05-18 15:23:12 -0700533 }
534
535 /**
Jeff Sharkey549aa162009-05-21 01:33:30 -0700536 * Inflate the in-track view for the action of the given mime-type. Will use
537 * the icon provided by the {@link Mapping}.
Jeff Sharkey3f177592009-05-18 15:23:12 -0700538 */
Jeff Sharkey549aa162009-05-21 01:33:30 -0700539 private View inflateAction(String mimeType) {
540 ImageView view = (ImageView)mInflater.inflate(R.layout.fasttrack_item, mTrack, false);
Jeff Sharkey3f177592009-05-18 15:23:12 -0700541
Jeff Sharkey549aa162009-05-21 01:33:30 -0700542 // Add direct intent if single child, otherwise flag for multiple
543 LinkedList<ActionInfo> children = mActions.get(mimeType);
544 ActionInfo firstInfo = children.get(0);
545 if (children.size() == 1) {
546 view.setTag(firstInfo.buildIntent());
547 } else {
548 view.setTag(mimeType);
Jeff Sharkey3f177592009-05-18 15:23:12 -0700549 }
550
Jeff Sharkey549aa162009-05-21 01:33:30 -0700551 // Set icon and listen for clicks
552 view.setImageBitmap(firstInfo.mapping.icon);
553 view.setOnClickListener(this);
554 return view;
555 }
Jeff Sharkey3f177592009-05-18 15:23:12 -0700556
Jeff Sharkey549aa162009-05-21 01:33:30 -0700557 /** {@inheritDoc} */
558 public void onClick(View v) {
559 final Object tag = v.getTag();
560 if (tag instanceof Intent) {
561 // Incoming tag is concrete intent, so launch
562 try {
563 mContext.startActivity((Intent)tag);
564 } catch (ActivityNotFoundException e) {
565 Log.w(TAG, NOT_FOUND);
566 Toast.makeText(mContext, NOT_FOUND, Toast.LENGTH_SHORT).show();
567 }
568 } else if (tag instanceof String) {
569 // Incoming tag is a mime-type, so show resolution list
570 LinkedList<ActionInfo> children = mActions.get(tag);
Jeff Sharkey3f177592009-05-18 15:23:12 -0700571
Jeff Sharkey549aa162009-05-21 01:33:30 -0700572 // TODO: show drop-down resolution list
573 Log.d(TAG, "would show list between several options here");
574
575 }
576 }
577
578 /** {@inheritDoc} */
579 public boolean dispatchKeyEvent(KeyEvent event) {
580 return mWindow.superDispatchKeyEvent(event);
581 }
582
583 /** {@inheritDoc} */
584 public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
585 // TODO: make this window accessible
586 return false;
587 }
588
589 /** {@inheritDoc} */
590 public boolean dispatchTouchEvent(MotionEvent event) {
591 if (event.getAction() == MotionEvent.ACTION_OUTSIDE) {
592 dismiss();
593 return true;
594 }
595 return mWindow.superDispatchTouchEvent(event);
596 }
597
598 /** {@inheritDoc} */
599 public boolean dispatchTrackballEvent(MotionEvent event) {
600 return mWindow.superDispatchTrackballEvent(event);
601 }
602
603 /** {@inheritDoc} */
604 public void onContentChanged() {
605 }
606
607 /** {@inheritDoc} */
608 public boolean onCreatePanelMenu(int featureId, Menu menu) {
609 return false;
610 }
611
612 /** {@inheritDoc} */
613 public View onCreatePanelView(int featureId) {
614 return null;
615 }
616
617 /** {@inheritDoc} */
618 public boolean onMenuItemSelected(int featureId, MenuItem item) {
619 return false;
620 }
621
622 /** {@inheritDoc} */
623 public boolean onMenuOpened(int featureId, Menu menu) {
624 return false;
625 }
626
627 /** {@inheritDoc} */
628 public void onPanelClosed(int featureId, Menu menu) {
629 }
630
631 /** {@inheritDoc} */
632 public boolean onPreparePanel(int featureId, View view, Menu menu) {
633 return false;
634 }
635
636 /** {@inheritDoc} */
637 public boolean onSearchRequested() {
638 return false;
639 }
640
641 /** {@inheritDoc} */
642 public void onWindowAttributesChanged(android.view.WindowManager.LayoutParams attrs) {
643 if (mDecor != null) {
644 mWindowManager.updateViewLayout(mDecor, attrs);
645 }
646 }
647
648 /** {@inheritDoc} */
649 public void onWindowFocusChanged(boolean hasFocus) {
Jeff Sharkey3f177592009-05-18 15:23:12 -0700650 }
651}