blob: 02dc54c11b986d38dab303d750243c6514f007c2 [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;
20import com.android.contacts.FloatyListView.FloatyWindow;
21import com.android.contacts.SocialStreamActivity.MappingCache;
22import com.android.contacts.SocialStreamActivity.MappingCache.Mapping;
23import com.android.providers.contacts2.ContactsContract;
24import com.android.providers.contacts2.ContactsContract.CommonDataKinds;
25import com.android.providers.contacts2.ContactsContract.Data;
26import com.android.providers.contacts2.ContactsContract.CommonDataKinds.Email;
27import com.android.providers.contacts2.ContactsContract.CommonDataKinds.Im;
28import com.android.providers.contacts2.ContactsContract.CommonDataKinds.Phone;
29import com.android.providers.contacts2.ContactsContract.CommonDataKinds.Postal;
30
31import android.content.ActivityNotFoundException;
32import android.content.ContentUris;
33import android.content.Context;
34import android.content.Intent;
35import android.content.res.Resources;
36import android.database.Cursor;
37import android.net.Uri;
38import android.util.Log;
39import android.view.Gravity;
40import android.view.LayoutInflater;
41import android.view.View;
42import android.view.ViewGroup;
43import android.view.ViewTreeObserver;
44import android.view.View.OnClickListener;
45import android.view.ViewTreeObserver.OnScrollChangedListener;
46import android.widget.AbsListView;
47import android.widget.ImageView;
48import android.widget.LinearLayout;
49import android.widget.ListView;
50import android.widget.PopupWindow;
51import android.widget.TextView;
52import android.widget.Toast;
53import android.widget.AbsListView.OnScrollListener;
54import android.widget.Gallery.LayoutParams;
55
56import java.lang.ref.WeakReference;
57import java.util.ArrayList;
58import java.util.Collections;
59import java.util.Comparator;
60import java.util.HashMap;
61import java.util.Iterator;
62import java.util.PriorityQueue;
63
64/**
65 * {@link PopupWindow} that shows fast-track details for a specific aggregate.
66 * This window implements {@link FloatyWindow} so that it can be quickly
67 * repositioned by someone like {@link FloatyListView}.
68 */
69public class FastTrackWindow extends PopupWindow implements QueryCompleteListener, FloatyWindow {
70 private static final String TAG = "FastTrackWindow";
71
72 private Context mContext;
73 private View mParent;
74
75 /** Mapping cache from mime-type to icons and actions */
76 private MappingCache mMappingCache;
77
78 private ViewGroup mContent;
79
80 private Uri mDataUri;
81 private NotifyingAsyncQueryHandler mHandler;
82
83 private boolean mShowing = false;
84 private boolean mHasPosition = false;
85 private boolean mHasData = false;
86
87 public static final int ICON_SIZE = 42;
88 public static final int ICON_PADDING = 3;
89 private static final int VERTICAL_OFFSET = 74;
90
91 private int mFirstX;
92 private int mFirstY;
93
94 private static final int TOKEN = 1;
95
96 private static final int GRAVITY = Gravity.LEFT | Gravity.TOP;
97
98 /** Message to show when no activity is found to perform an action */
99 // TODO: move this value into a resources string
100 private static final String NOT_FOUND = "Couldn't find an app to handle this action";
101
102 /** List of default mime-type icons */
103 private static HashMap<String, Integer> sMimeIcons = new HashMap<String, Integer>();
104
105 /** List of mime-type sorting scores */
106 private static HashMap<String, Integer> sMimeScores = new HashMap<String, Integer>();
107
108 static {
109 sMimeIcons.put(Phone.CONTENT_ITEM_TYPE, android.R.drawable.sym_action_call);
110 sMimeIcons.put(Email.CONTENT_ITEM_TYPE, android.R.drawable.sym_action_email);
111 sMimeIcons.put(Im.CONTENT_ITEM_TYPE, android.R.drawable.sym_action_chat);
112// sMimeIcons.put(Phone.CONTENT_ITEM_TYPE, R.drawable.sym_action_sms);
113 sMimeIcons.put(Postal.CONTENT_ITEM_TYPE, R.drawable.sym_action_map);
114
115 // For scoring, put phone numbers and E-mail up front, and addresses last
116 sMimeScores.put(Phone.CONTENT_ITEM_TYPE, -200);
117 sMimeScores.put(Email.CONTENT_ITEM_TYPE, -100);
118 sMimeScores.put(Postal.CONTENT_ITEM_TYPE, 100);
119 }
120
121 /**
122 * Create a new fast-track window for the given aggregate, using the
123 * provided {@link MappingCache} for icon as needed.
124 */
125 public FastTrackWindow(Context context, View parent, Uri aggUri, MappingCache mappingCache) {
126 super(context);
127
128 final Resources resources = context.getResources();
129
130 mContext = context;
131 mParent = parent;
132
133 mMappingCache = mappingCache;
134
135 // Inflate content view
136 LayoutInflater inflater = (LayoutInflater)context
137 .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
138 mContent = (ViewGroup)inflater.inflate(R.layout.fasttrack, null, false);
139
140 setContentView(mContent);
Jeff Sharkeye913e5e2009-05-18 17:51:13 -0700141// setAnimationStyle(android.R.style.Animation_LeftEdge);
Jeff Sharkey3f177592009-05-18 15:23:12 -0700142
143 setBackgroundDrawable(resources.getDrawable(R.drawable.fasttrack));
144
145 setWidth(LayoutParams.WRAP_CONTENT);
146 setHeight(LayoutParams.WRAP_CONTENT);
147
148 setClippingEnabled(false);
149 setFocusable(false);
150
151 // Start data query in background
152 mDataUri = Uri.withAppendedPath(aggUri, ContactsContract.Aggregates.Data.CONTENT_DIRECTORY);
153
154 mHandler = new NotifyingAsyncQueryHandler(context, this);
155 mHandler.startQuery(TOKEN, null, mDataUri, null, null, null, null);
156
157 }
158
159 /**
160 * Consider showing this window, which requires both a given position and
161 * completed query results.
162 */
163 private synchronized void considerShowing() {
164 if (mHasData && mHasPosition && !mShowing) {
165 mShowing = true;
166 showAtLocation(mParent, GRAVITY, mFirstX, mFirstY);
167 }
168 }
169
170 /** {@inheritDoc} */
171 public void showAt(int x, int y) {
172 // Adjust vertical position by height
173 y -= VERTICAL_OFFSET;
174
175 // Show dialog or update existing location
176 if (!mShowing) {
177 mFirstX = x;
178 mFirstY = y;
179 mHasPosition = true;
180 considerShowing();
181 } else {
182 update(x, y, -1, -1, true);
183 }
184 }
185
186 /** {@inheritDoc} */
187 public void onQueryComplete(int token, Object cookie, Cursor cursor) {
188 final ViewGroup fastTrack = (ViewGroup)mContent.findViewById(R.id.fasttrack);
189
190 // Build list of actions for this contact, this could be done better in
191 // the future using an Adapter
192 ArrayList<ImageView> list = new ArrayList<ImageView>(cursor.getCount());
193
194 final int COL_ID = cursor.getColumnIndex(Data._ID);
195 final int COL_PACKAGE = cursor.getColumnIndex(Data.PACKAGE);
196 final int COL_MIMETYPE = cursor.getColumnIndex(Data.MIMETYPE);
197
198 while (cursor.moveToNext()) {
199 final long dataId = cursor.getLong(COL_ID);
200 final String packageName = cursor.getString(COL_PACKAGE);
201 final String mimeType = cursor.getString(COL_MIMETYPE);
202
203 ImageView action;
204
205 // First, try looking in mapping cache for possible icon match
206 Mapping mapping = mMappingCache.getMapping(packageName, mimeType);
207 if (mapping != null && mapping.icon != null) {
208 action = new ImageView(mContext);
209 action.setImageBitmap(mapping.icon);
210
211 } else if (sMimeIcons.containsKey(mimeType)) {
212 // Otherwise fall back to generic icons
213 int icon = sMimeIcons.get(mimeType);
214 action = new ImageView(mContext);
215 action.setImageResource(icon);
216
217 } else {
218 // No icon found, so don't insert any action button
219 break;
220
221 }
222
223 LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(ICON_SIZE, ICON_SIZE);
224 params.rightMargin = ICON_PADDING;
225 action.setLayoutParams(params);
226
227 // Find the sorting score for this mime-type, otherwise allocate a
228 // new one to make sure the same types are grouped together.
229 if (!sMimeScores.containsKey(mimeType)) {
230 sMimeScores.put(mimeType, sMimeScores.size());
231 }
232
233 int mimeScore = sMimeScores.get(mimeType);
234 action.setTag(mimeScore);
235
236 final Intent intent = buildIntentForMime(dataId, mimeType, cursor);
237 action.setOnClickListener(new OnClickListener() {
238 public void onClick(View v) {
239 try {
240 mContext.startActivity(intent);
241 } catch (ActivityNotFoundException e) {
242 Log.w(TAG, NOT_FOUND, e);
243 Toast.makeText(mContext, NOT_FOUND, Toast.LENGTH_SHORT).show();
244 }
245 }
246 });
247
248 list.add(action);
249 }
250
251 cursor.close();
252
253 // Sort the final list based on mime-type scores
254 Collections.sort(list, new Comparator<ImageView>() {
255 public int compare(ImageView object1, ImageView object2) {
256 return (Integer)object1.getTag() - (Integer)object2.getTag();
257 }
258 });
259
260 for (ImageView action : list) {
261 fastTrack.addView(action);
262 }
263
264 mHasData = true;
265 considerShowing();
266 }
267
268 /**
269 * Build an {@link Intent} that will trigger the action described by the
270 * given {@link Cursor} and mime-type.
271 */
272 public Intent buildIntentForMime(long dataId, String mimeType, Cursor cursor) {
273 if (CommonDataKinds.Phone.CONTENT_ITEM_TYPE.equals(mimeType)) {
274 final String data = cursor.getString(cursor.getColumnIndex(Phone.NUMBER));
275 Uri callUri = Uri.parse("tel:" + Uri.encode(data));
276 return new Intent(Intent.ACTION_DIAL, callUri);
277
278 } else if (CommonDataKinds.Email.CONTENT_ITEM_TYPE.equals(mimeType)) {
279 final String data = cursor.getString(cursor.getColumnIndex(Email.DATA));
280 return new Intent(Intent.ACTION_SENDTO, Uri.fromParts("mailto", data, null));
281
282// } else if (CommonDataKinds.Im.CONTENT_ITEM_TYPE.equals(mimeType)) {
283// return new Intent(Intent.ACTION_SENDTO, constructImToUrl(host, data));
284
285 } else if (CommonDataKinds.Postal.CONTENT_ITEM_TYPE.equals(mimeType)) {
286 final String data = cursor.getString(cursor.getColumnIndex(Postal.DATA));
287 Uri mapsUri = Uri.parse("geo:0,0?q=" + Uri.encode(data));
288 return new Intent(Intent.ACTION_VIEW, mapsUri);
289
290 }
291
292 // Otherwise fall back to default VIEW action
293 Uri dataUri = ContentUris.withAppendedId(ContactsContract.Data.CONTENT_URI, dataId);
294
295 Intent intent = new Intent(Intent.ACTION_VIEW);
296 intent.setData(dataUri);
297
298 return intent;
299 }
300}