blob: e2309962be3ccec508910a0ee383681446e2a419 [file] [log] [blame]
Chiao Chengbeca8562012-11-28 18:06:44 -08001/*
2 * Copyright (C) 2010 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 */
16package com.android.contacts.common.list;
17
18import android.app.ActivityManager;
19import android.content.ContentResolver;
20import android.content.Context;
21import android.content.Intent;
22import android.content.res.Resources;
23import android.database.Cursor;
24import android.graphics.Bitmap;
25import android.graphics.BitmapFactory;
26import android.graphics.Canvas;
27import android.graphics.Paint;
28import android.graphics.Paint.FontMetricsInt;
29import android.graphics.Rect;
30import android.graphics.drawable.BitmapDrawable;
31import android.graphics.drawable.Drawable;
32import android.net.Uri;
33import android.os.AsyncTask;
34import android.provider.ContactsContract;
35import android.provider.ContactsContract.CommonDataKinds.Phone;
36import android.provider.ContactsContract.CommonDataKinds.Photo;
37import android.provider.ContactsContract.Contacts;
38import android.provider.ContactsContract.Data;
Brian Attwell5b53b8d2014-07-07 22:22:51 -070039import android.support.v4.graphics.drawable.RoundedBitmapDrawable;
Chris Craik93fb4352014-08-05 18:51:49 -070040import android.support.v4.graphics.drawable.RoundedBitmapDrawableFactory;
Tyler Gunn5cf7f012014-09-10 15:20:21 -070041import android.telecom.PhoneAccount;
Chiao Chengbeca8562012-11-28 18:06:44 -080042import android.text.TextPaint;
43import android.text.TextUtils;
44import android.text.TextUtils.TruncateAt;
45
Jay Shraunered1a3b22014-09-05 15:37:27 -070046import com.android.contacts.common.ContactsUtils;
Yorke Lee8baea882014-03-03 11:08:17 -080047import com.android.contacts.common.ContactPhotoManager;
48import com.android.contacts.common.ContactPhotoManager.DefaultImageRequest;
Chiao Chengbeca8562012-11-28 18:06:44 -080049import com.android.contacts.common.R;
Marcus Hagerottc5083f92016-09-14 08:34:29 -070050import com.android.contacts.common.util.ImplicitIntentsUtil;
Chiao Chengbeca8562012-11-28 18:06:44 -080051
52/**
53 * Constructs shortcut intents.
54 */
55public class ShortcutIntentBuilder {
56
57 private static final String[] CONTACT_COLUMNS = {
58 Contacts.DISPLAY_NAME,
59 Contacts.PHOTO_ID,
Yorke Lee8baea882014-03-03 11:08:17 -080060 Contacts.LOOKUP_KEY
Chiao Chengbeca8562012-11-28 18:06:44 -080061 };
62
63 private static final int CONTACT_DISPLAY_NAME_COLUMN_INDEX = 0;
64 private static final int CONTACT_PHOTO_ID_COLUMN_INDEX = 1;
Yorke Lee8baea882014-03-03 11:08:17 -080065 private static final int CONTACT_LOOKUP_KEY_COLUMN_INDEX = 2;
Chiao Chengbeca8562012-11-28 18:06:44 -080066
67 private static final String[] PHONE_COLUMNS = {
68 Phone.DISPLAY_NAME,
69 Phone.PHOTO_ID,
70 Phone.NUMBER,
71 Phone.TYPE,
Yorke Lee8baea882014-03-03 11:08:17 -080072 Phone.LABEL,
73 Phone.LOOKUP_KEY
Chiao Chengbeca8562012-11-28 18:06:44 -080074 };
75
76 private static final int PHONE_DISPLAY_NAME_COLUMN_INDEX = 0;
77 private static final int PHONE_PHOTO_ID_COLUMN_INDEX = 1;
78 private static final int PHONE_NUMBER_COLUMN_INDEX = 2;
79 private static final int PHONE_TYPE_COLUMN_INDEX = 3;
80 private static final int PHONE_LABEL_COLUMN_INDEX = 4;
Yorke Lee8baea882014-03-03 11:08:17 -080081 private static final int PHONE_LOOKUP_KEY_COLUMN_INDEX = 5;
Chiao Chengbeca8562012-11-28 18:06:44 -080082
83 private static final String[] PHOTO_COLUMNS = {
84 Photo.PHOTO,
85 };
86
87 private static final int PHOTO_PHOTO_COLUMN_INDEX = 0;
88
89 private static final String PHOTO_SELECTION = Photo._ID + "=?";
90
91 private final OnShortcutIntentCreatedListener mListener;
92 private final Context mContext;
93 private int mIconSize;
94 private final int mIconDensity;
Brian Attwell98771ce2014-09-02 12:54:53 -070095 private final int mOverlayTextBackgroundColor;
Brian Attwell5b53b8d2014-07-07 22:22:51 -070096 private final Resources mResources;
Chiao Chengbeca8562012-11-28 18:06:44 -080097
98 /**
99 * This is a hidden API of the launcher in JellyBean that allows us to disable the animation
Brian Attwell8a0cbec2014-07-07 16:39:01 -0700100 * that it would usually do, because it interferes with our own animation for QuickContact.
101 * This is needed since some versions of the launcher override the intent flags and therefore
102 * ignore Intent.FLAG_ACTIVITY_NO_ANIMATION.
Chiao Chengbeca8562012-11-28 18:06:44 -0800103 */
104 public static final String INTENT_EXTRA_IGNORE_LAUNCH_ANIMATION =
105 "com.android.launcher.intent.extra.shortcut.INGORE_LAUNCH_ANIMATION";
106
107 /**
108 * Listener interface.
109 */
110 public interface OnShortcutIntentCreatedListener {
111
112 /**
113 * Callback for shortcut intent creation.
114 *
115 * @param uri the original URI for which the shortcut intent has been
116 * created.
117 * @param shortcutIntent resulting shortcut intent.
118 */
119 void onShortcutIntentCreated(Uri uri, Intent shortcutIntent);
120 }
121
122 public ShortcutIntentBuilder(Context context, OnShortcutIntentCreatedListener listener) {
123 mContext = context;
124 mListener = listener;
125
Brian Attwell5b53b8d2014-07-07 22:22:51 -0700126 mResources = context.getResources();
Chiao Chengbeca8562012-11-28 18:06:44 -0800127 final ActivityManager am = (ActivityManager) context
128 .getSystemService(Context.ACTIVITY_SERVICE);
Brian Attwell5b53b8d2014-07-07 22:22:51 -0700129 mIconSize = mResources.getDimensionPixelSize(R.dimen.shortcut_icon_size);
Chiao Chengbeca8562012-11-28 18:06:44 -0800130 if (mIconSize == 0) {
131 mIconSize = am.getLauncherLargeIconSize();
132 }
133 mIconDensity = am.getLauncherLargeIconDensity();
Brian Attwell98771ce2014-09-02 12:54:53 -0700134 mOverlayTextBackgroundColor = mResources.getColor(R.color.shortcut_overlay_text_background);
Chiao Chengbeca8562012-11-28 18:06:44 -0800135 }
136
137 public void createContactShortcutIntent(Uri contactUri) {
138 new ContactLoadingAsyncTask(contactUri).execute();
139 }
140
141 public void createPhoneNumberShortcutIntent(Uri dataUri, String shortcutAction) {
142 new PhoneNumberLoadingAsyncTask(dataUri, shortcutAction).execute();
143 }
144
145 /**
146 * An asynchronous task that loads name, photo and other data from the database.
147 */
148 private abstract class LoadingAsyncTask extends AsyncTask<Void, Void, Void> {
149 protected Uri mUri;
150 protected String mContentType;
151 protected String mDisplayName;
Yorke Lee8baea882014-03-03 11:08:17 -0800152 protected String mLookupKey;
Chiao Chengbeca8562012-11-28 18:06:44 -0800153 protected byte[] mBitmapData;
154 protected long mPhotoId;
155
156 public LoadingAsyncTask(Uri uri) {
157 mUri = uri;
158 }
159
160 @Override
161 protected Void doInBackground(Void... params) {
162 mContentType = mContext.getContentResolver().getType(mUri);
163 loadData();
164 loadPhoto();
165 return null;
166 }
167
168 protected abstract void loadData();
169
170 private void loadPhoto() {
171 if (mPhotoId == 0) {
172 return;
173 }
174
175 ContentResolver resolver = mContext.getContentResolver();
176 Cursor cursor = resolver.query(Data.CONTENT_URI, PHOTO_COLUMNS, PHOTO_SELECTION,
177 new String[] { String.valueOf(mPhotoId) }, null);
178 if (cursor != null) {
179 try {
180 if (cursor.moveToFirst()) {
181 mBitmapData = cursor.getBlob(PHOTO_PHOTO_COLUMN_INDEX);
182 }
183 } finally {
184 cursor.close();
185 }
186 }
187 }
188 }
189
190 private final class ContactLoadingAsyncTask extends LoadingAsyncTask {
191 public ContactLoadingAsyncTask(Uri uri) {
192 super(uri);
193 }
194
195 @Override
196 protected void loadData() {
197 ContentResolver resolver = mContext.getContentResolver();
198 Cursor cursor = resolver.query(mUri, CONTACT_COLUMNS, null, null, null);
199 if (cursor != null) {
200 try {
201 if (cursor.moveToFirst()) {
202 mDisplayName = cursor.getString(CONTACT_DISPLAY_NAME_COLUMN_INDEX);
203 mPhotoId = cursor.getLong(CONTACT_PHOTO_ID_COLUMN_INDEX);
Yorke Lee8baea882014-03-03 11:08:17 -0800204 mLookupKey = cursor.getString(CONTACT_LOOKUP_KEY_COLUMN_INDEX);
Chiao Chengbeca8562012-11-28 18:06:44 -0800205 }
206 } finally {
207 cursor.close();
208 }
209 }
210 }
211 @Override
212 protected void onPostExecute(Void result) {
Yorke Lee8baea882014-03-03 11:08:17 -0800213 createContactShortcutIntent(mUri, mContentType, mDisplayName, mLookupKey, mBitmapData);
Chiao Chengbeca8562012-11-28 18:06:44 -0800214 }
215 }
216
217 private final class PhoneNumberLoadingAsyncTask extends LoadingAsyncTask {
218 private final String mShortcutAction;
219 private String mPhoneNumber;
220 private int mPhoneType;
221 private String mPhoneLabel;
222
223 public PhoneNumberLoadingAsyncTask(Uri uri, String shortcutAction) {
224 super(uri);
225 mShortcutAction = shortcutAction;
226 }
227
228 @Override
229 protected void loadData() {
230 ContentResolver resolver = mContext.getContentResolver();
231 Cursor cursor = resolver.query(mUri, PHONE_COLUMNS, null, null, null);
232 if (cursor != null) {
233 try {
234 if (cursor.moveToFirst()) {
235 mDisplayName = cursor.getString(PHONE_DISPLAY_NAME_COLUMN_INDEX);
236 mPhotoId = cursor.getLong(PHONE_PHOTO_ID_COLUMN_INDEX);
237 mPhoneNumber = cursor.getString(PHONE_NUMBER_COLUMN_INDEX);
238 mPhoneType = cursor.getInt(PHONE_TYPE_COLUMN_INDEX);
239 mPhoneLabel = cursor.getString(PHONE_LABEL_COLUMN_INDEX);
Yorke Lee8baea882014-03-03 11:08:17 -0800240 mLookupKey = cursor.getString(PHONE_LOOKUP_KEY_COLUMN_INDEX);
Chiao Chengbeca8562012-11-28 18:06:44 -0800241 }
242 } finally {
243 cursor.close();
244 }
245 }
246 }
247
248 @Override
249 protected void onPostExecute(Void result) {
Yorke Lee8baea882014-03-03 11:08:17 -0800250 createPhoneNumberShortcutIntent(mUri, mDisplayName, mLookupKey, mBitmapData,
251 mPhoneNumber, mPhoneType, mPhoneLabel, mShortcutAction);
Chiao Chengbeca8562012-11-28 18:06:44 -0800252 }
253 }
254
Yorke Lee8baea882014-03-03 11:08:17 -0800255 private Drawable getPhotoDrawable(byte[] bitmapData, String displayName, String lookupKey) {
Chiao Chengbeca8562012-11-28 18:06:44 -0800256 if (bitmapData != null) {
Yorke Lee8baea882014-03-03 11:08:17 -0800257 Bitmap bitmap = BitmapFactory.decodeByteArray(bitmapData, 0, bitmapData.length, null);
258 return new BitmapDrawable(mContext.getResources(), bitmap);
Chiao Chengbeca8562012-11-28 18:06:44 -0800259 } else {
Yorke Lee8baea882014-03-03 11:08:17 -0800260 return ContactPhotoManager.getDefaultAvatarDrawableForContact(mContext.getResources(),
Yorke Leec4a2a232014-04-28 17:53:42 -0700261 false, new DefaultImageRequest(displayName, lookupKey, false));
Chiao Chengbeca8562012-11-28 18:06:44 -0800262 }
Chiao Chengbeca8562012-11-28 18:06:44 -0800263 }
264
265 private void createContactShortcutIntent(Uri contactUri, String contentType, String displayName,
Yorke Lee8baea882014-03-03 11:08:17 -0800266 String lookupKey, byte[] bitmapData) {
267 Drawable drawable = getPhotoDrawable(bitmapData, displayName, lookupKey);
Chiao Chengbeca8562012-11-28 18:06:44 -0800268
Marcus Hagerottc5083f92016-09-14 08:34:29 -0700269 final Intent shortcutIntent = ImplicitIntentsUtil.getIntentForQuickContactLauncherShortcut(
270 mContext, contactUri);
Chiao Chengbeca8562012-11-28 18:06:44 -0800271
Yorke Lee8baea882014-03-03 11:08:17 -0800272 final Bitmap icon = generateQuickContactIcon(drawable);
Chiao Chengbeca8562012-11-28 18:06:44 -0800273
274 Intent intent = new Intent();
275 intent.putExtra(Intent.EXTRA_SHORTCUT_ICON, icon);
276 intent.putExtra(Intent.EXTRA_SHORTCUT_INTENT, shortcutIntent);
277 if (TextUtils.isEmpty(displayName)) {
278 intent.putExtra(Intent.EXTRA_SHORTCUT_NAME, mContext.getResources().getString(
279 R.string.missing_name));
280 } else {
281 intent.putExtra(Intent.EXTRA_SHORTCUT_NAME, displayName);
282 }
283
284 mListener.onShortcutIntentCreated(contactUri, intent);
285 }
286
Yorke Lee8baea882014-03-03 11:08:17 -0800287 private void createPhoneNumberShortcutIntent(Uri uri, String displayName, String lookupKey,
288 byte[] bitmapData, String phoneNumber, int phoneType, String phoneLabel,
289 String shortcutAction) {
290 Drawable drawable = getPhotoDrawable(bitmapData, displayName, lookupKey);
Chiao Chengbeca8562012-11-28 18:06:44 -0800291
Yorke Lee8baea882014-03-03 11:08:17 -0800292 Bitmap bitmap;
Chiao Chengbeca8562012-11-28 18:06:44 -0800293 Uri phoneUri;
294 if (Intent.ACTION_CALL.equals(shortcutAction)) {
295 // Make the URI a direct tel: URI so that it will always continue to work
Jay Shraunered1a3b22014-09-05 15:37:27 -0700296 phoneUri = Uri.fromParts(PhoneAccount.SCHEME_TEL, phoneNumber, null);
Yorke Lee8baea882014-03-03 11:08:17 -0800297 bitmap = generatePhoneNumberIcon(drawable, phoneType, phoneLabel,
Andrew Lee2b231912015-03-27 15:38:39 -0700298 R.drawable.ic_call);
Chiao Chengbeca8562012-11-28 18:06:44 -0800299 } else {
Jay Shraunered1a3b22014-09-05 15:37:27 -0700300 phoneUri = Uri.fromParts(ContactsUtils.SCHEME_SMSTO, phoneNumber, null);
Yorke Lee8baea882014-03-03 11:08:17 -0800301 bitmap = generatePhoneNumberIcon(drawable, phoneType, phoneLabel,
Walter Jang353570e2016-05-04 17:43:56 -0700302 R.drawable.ic_message_24dp_mirrored);
Chiao Chengbeca8562012-11-28 18:06:44 -0800303 }
304
305 Intent shortcutIntent = new Intent(shortcutAction, phoneUri);
306 shortcutIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
307
308 Intent intent = new Intent();
309 intent.putExtra(Intent.EXTRA_SHORTCUT_ICON, bitmap);
310 intent.putExtra(Intent.EXTRA_SHORTCUT_INTENT, shortcutIntent);
guanxiongliu4f715ff2016-03-10 14:14:54 -0800311
312 if (TextUtils.isEmpty(displayName)) {
313 displayName = mContext.getResources().getString(R.string.missing_name);
314 }
315 if (TextUtils.equals(shortcutAction, Intent.ACTION_CALL)) {
316 intent.putExtra(Intent.EXTRA_SHORTCUT_NAME,
317 mContext.getResources().getString(R.string.call_by_shortcut, displayName));
318 } else if (TextUtils.equals(shortcutAction, Intent.ACTION_SENDTO)) {
319 intent.putExtra(Intent.EXTRA_SHORTCUT_NAME,
320 mContext.getResources().getString(R.string.sms_by_shortcut, displayName));
321 }
Chiao Chengbeca8562012-11-28 18:06:44 -0800322
323 mListener.onShortcutIntentCreated(uri, intent);
324 }
325
Yorke Lee8baea882014-03-03 11:08:17 -0800326 private Bitmap generateQuickContactIcon(Drawable photo) {
Chiao Chengbeca8562012-11-28 18:06:44 -0800327
328 // Setup the drawing classes
Brian Attwell5b53b8d2014-07-07 22:22:51 -0700329 Bitmap bitmap = Bitmap.createBitmap(mIconSize, mIconSize, Bitmap.Config.ARGB_8888);
330 Canvas canvas = new Canvas(bitmap);
Chiao Chengbeca8562012-11-28 18:06:44 -0800331
332 // Copy in the photo
Chiao Chengbeca8562012-11-28 18:06:44 -0800333 Rect dst = new Rect(0,0, mIconSize, mIconSize);
Yorke Lee8baea882014-03-03 11:08:17 -0800334 photo.setBounds(dst);
335 photo.draw(canvas);
Chiao Chengbeca8562012-11-28 18:06:44 -0800336
Brian Attwell5b53b8d2014-07-07 22:22:51 -0700337 // Draw the icon with a rounded border
Chris Craik93fb4352014-08-05 18:51:49 -0700338 RoundedBitmapDrawable roundedDrawable =
339 RoundedBitmapDrawableFactory.create(mResources, bitmap);
Brian Attwell5b53b8d2014-07-07 22:22:51 -0700340 roundedDrawable.setAntiAlias(true);
341 roundedDrawable.setCornerRadius(mIconSize / 2);
342 Bitmap roundedBitmap = Bitmap.createBitmap(mIconSize, mIconSize, Bitmap.Config.ARGB_8888);
343 canvas.setBitmap(roundedBitmap);
344 roundedDrawable.setBounds(dst);
345 roundedDrawable.draw(canvas);
Chiao Chengbeca8562012-11-28 18:06:44 -0800346 canvas.setBitmap(null);
347
Brian Attwell5b53b8d2014-07-07 22:22:51 -0700348 return roundedBitmap;
Chiao Chengbeca8562012-11-28 18:06:44 -0800349 }
350
351 /**
352 * Generates a phone number shortcut icon. Adds an overlay describing the type of the phone
353 * number, and if there is a photo also adds the call action icon.
354 */
Yorke Lee8baea882014-03-03 11:08:17 -0800355 private Bitmap generatePhoneNumberIcon(Drawable photo, int phoneType, String phoneLabel,
Chiao Chengbeca8562012-11-28 18:06:44 -0800356 int actionResId) {
357 final Resources r = mContext.getResources();
358 final float density = r.getDisplayMetrics().density;
359
360 Bitmap phoneIcon = ((BitmapDrawable) r.getDrawableForDensity(actionResId, mIconDensity))
361 .getBitmap();
362
Brian Attwell98771ce2014-09-02 12:54:53 -0700363 Bitmap icon = generateQuickContactIcon(photo);
Chiao Chengbeca8562012-11-28 18:06:44 -0800364 Canvas canvas = new Canvas(icon);
365
366 // Copy in the photo
367 Paint photoPaint = new Paint();
368 photoPaint.setDither(true);
369 photoPaint.setFilterBitmap(true);
Chiao Chengbeca8562012-11-28 18:06:44 -0800370 Rect dst = new Rect(0, 0, mIconSize, mIconSize);
Yorke Lee8baea882014-03-03 11:08:17 -0800371
Chiao Chengbeca8562012-11-28 18:06:44 -0800372 // Create an overlay for the phone number type
373 CharSequence overlay = Phone.getTypeLabel(r, phoneType, phoneLabel);
374
375 if (overlay != null) {
376 TextPaint textPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG | Paint.DEV_KERN_TEXT_FLAG);
377 textPaint.setTextSize(r.getDimension(R.dimen.shortcut_overlay_text_size));
378 textPaint.setColor(r.getColor(R.color.textColorIconOverlay));
379 textPaint.setShadowLayer(4f, 0, 2f, r.getColor(R.color.textColorIconOverlayShadow));
380
381 final FontMetricsInt fmi = textPaint.getFontMetricsInt();
382
383 // First fill in a darker background around the text to be drawn
384 final Paint workPaint = new Paint();
Brian Attwell98771ce2014-09-02 12:54:53 -0700385 workPaint.setColor(mOverlayTextBackgroundColor);
Chiao Chengbeca8562012-11-28 18:06:44 -0800386 workPaint.setStyle(Paint.Style.FILL);
387 final int textPadding = r
388 .getDimensionPixelOffset(R.dimen.shortcut_overlay_text_background_padding);
389 final int textBandHeight = (fmi.descent - fmi.ascent) + textPadding * 2;
Brian Attwell98771ce2014-09-02 12:54:53 -0700390 dst.set(0, mIconSize - textBandHeight, mIconSize, mIconSize);
Chiao Chengbeca8562012-11-28 18:06:44 -0800391 canvas.drawRect(dst, workPaint);
392
Brian Attwell98771ce2014-09-02 12:54:53 -0700393 overlay = TextUtils.ellipsize(overlay, textPaint, mIconSize, TruncateAt.END);
Chiao Chengbeca8562012-11-28 18:06:44 -0800394 final float textWidth = textPaint.measureText(overlay, 0, overlay.length());
395 canvas.drawText(overlay, 0, overlay.length(), (mIconSize - textWidth) / 2, mIconSize
396 - fmi.descent - textPadding, textPaint);
397 }
398
399 // Draw the phone action icon as an overlay
Yorke Lee8baea882014-03-03 11:08:17 -0800400 Rect src = new Rect(0, 0, phoneIcon.getWidth(), phoneIcon.getHeight());
Chiao Chengbeca8562012-11-28 18:06:44 -0800401 int iconWidth = icon.getWidth();
402 dst.set(iconWidth - ((int) (20 * density)), -1,
403 iconWidth, ((int) (19 * density)));
Chiao Chengbeca8562012-11-28 18:06:44 -0800404 canvas.drawBitmap(phoneIcon, src, dst, photoPaint);
405
406 canvas.setBitmap(null);
407
408 return icon;
409 }
410}