blob: d7ac53a0d5defe784118200a7213abc48c86796f [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 */
Gary Mai69c182a2016-12-05 13:07:03 -080016package com.android.contacts.list;
Chiao Chengbeca8562012-11-28 18:06:44 -080017
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;
Chiao Chengbeca8562012-11-28 18:06:44 -080034import android.provider.ContactsContract.CommonDataKinds.Phone;
35import android.provider.ContactsContract.CommonDataKinds.Photo;
36import android.provider.ContactsContract.Contacts;
37import android.provider.ContactsContract.Data;
Brian Attwell5b53b8d2014-07-07 22:22:51 -070038import android.support.v4.graphics.drawable.RoundedBitmapDrawable;
Chris Craik93fb4352014-08-05 18:51:49 -070039import android.support.v4.graphics.drawable.RoundedBitmapDrawableFactory;
Tyler Gunn5cf7f012014-09-10 15:20:21 -070040import android.telecom.PhoneAccount;
Chiao Chengbeca8562012-11-28 18:06:44 -080041import android.text.TextPaint;
42import android.text.TextUtils;
43import android.text.TextUtils.TruncateAt;
44
Gary Mai0a49afa2016-12-05 15:53:58 -080045import com.android.contacts.ContactPhotoManager;
46import com.android.contacts.ContactPhotoManager.DefaultImageRequest;
47import com.android.contacts.ContactsUtils;
Arthur Wang3f6a2442016-12-05 14:51:59 -080048import com.android.contacts.R;
Gary Mai69c182a2016-12-05 13:07:03 -080049import com.android.contacts.util.ImplicitIntentsUtil;
Chiao Chengbeca8562012-11-28 18:06:44 -080050
51/**
52 * Constructs shortcut intents.
53 */
54public class ShortcutIntentBuilder {
55
56 private static final String[] CONTACT_COLUMNS = {
57 Contacts.DISPLAY_NAME,
58 Contacts.PHOTO_ID,
Yorke Lee8baea882014-03-03 11:08:17 -080059 Contacts.LOOKUP_KEY
Chiao Chengbeca8562012-11-28 18:06:44 -080060 };
61
62 private static final int CONTACT_DISPLAY_NAME_COLUMN_INDEX = 0;
63 private static final int CONTACT_PHOTO_ID_COLUMN_INDEX = 1;
Yorke Lee8baea882014-03-03 11:08:17 -080064 private static final int CONTACT_LOOKUP_KEY_COLUMN_INDEX = 2;
Chiao Chengbeca8562012-11-28 18:06:44 -080065
66 private static final String[] PHONE_COLUMNS = {
67 Phone.DISPLAY_NAME,
68 Phone.PHOTO_ID,
69 Phone.NUMBER,
70 Phone.TYPE,
Yorke Lee8baea882014-03-03 11:08:17 -080071 Phone.LABEL,
72 Phone.LOOKUP_KEY
Chiao Chengbeca8562012-11-28 18:06:44 -080073 };
74
75 private static final int PHONE_DISPLAY_NAME_COLUMN_INDEX = 0;
76 private static final int PHONE_PHOTO_ID_COLUMN_INDEX = 1;
77 private static final int PHONE_NUMBER_COLUMN_INDEX = 2;
78 private static final int PHONE_TYPE_COLUMN_INDEX = 3;
79 private static final int PHONE_LABEL_COLUMN_INDEX = 4;
Yorke Lee8baea882014-03-03 11:08:17 -080080 private static final int PHONE_LOOKUP_KEY_COLUMN_INDEX = 5;
Chiao Chengbeca8562012-11-28 18:06:44 -080081
82 private static final String[] PHOTO_COLUMNS = {
83 Photo.PHOTO,
84 };
85
86 private static final int PHOTO_PHOTO_COLUMN_INDEX = 0;
87
88 private static final String PHOTO_SELECTION = Photo._ID + "=?";
89
90 private final OnShortcutIntentCreatedListener mListener;
91 private final Context mContext;
92 private int mIconSize;
93 private final int mIconDensity;
Brian Attwell98771ce2014-09-02 12:54:53 -070094 private final int mOverlayTextBackgroundColor;
Brian Attwell5b53b8d2014-07-07 22:22:51 -070095 private final Resources mResources;
Chiao Chengbeca8562012-11-28 18:06:44 -080096
97 /**
98 * This is a hidden API of the launcher in JellyBean that allows us to disable the animation
Brian Attwell8a0cbec2014-07-07 16:39:01 -070099 * that it would usually do, because it interferes with our own animation for QuickContact.
100 * This is needed since some versions of the launcher override the intent flags and therefore
101 * ignore Intent.FLAG_ACTIVITY_NO_ANIMATION.
Chiao Chengbeca8562012-11-28 18:06:44 -0800102 */
103 public static final String INTENT_EXTRA_IGNORE_LAUNCH_ANIMATION =
104 "com.android.launcher.intent.extra.shortcut.INGORE_LAUNCH_ANIMATION";
105
106 /**
107 * Listener interface.
108 */
109 public interface OnShortcutIntentCreatedListener {
110
111 /**
112 * Callback for shortcut intent creation.
113 *
114 * @param uri the original URI for which the shortcut intent has been
115 * created.
116 * @param shortcutIntent resulting shortcut intent.
117 */
118 void onShortcutIntentCreated(Uri uri, Intent shortcutIntent);
119 }
120
121 public ShortcutIntentBuilder(Context context, OnShortcutIntentCreatedListener listener) {
122 mContext = context;
123 mListener = listener;
124
Brian Attwell5b53b8d2014-07-07 22:22:51 -0700125 mResources = context.getResources();
Chiao Chengbeca8562012-11-28 18:06:44 -0800126 final ActivityManager am = (ActivityManager) context
127 .getSystemService(Context.ACTIVITY_SERVICE);
Brian Attwell5b53b8d2014-07-07 22:22:51 -0700128 mIconSize = mResources.getDimensionPixelSize(R.dimen.shortcut_icon_size);
Chiao Chengbeca8562012-11-28 18:06:44 -0800129 if (mIconSize == 0) {
130 mIconSize = am.getLauncherLargeIconSize();
131 }
132 mIconDensity = am.getLauncherLargeIconDensity();
Brian Attwell98771ce2014-09-02 12:54:53 -0700133 mOverlayTextBackgroundColor = mResources.getColor(R.color.shortcut_overlay_text_background);
Chiao Chengbeca8562012-11-28 18:06:44 -0800134 }
135
136 public void createContactShortcutIntent(Uri contactUri) {
137 new ContactLoadingAsyncTask(contactUri).execute();
138 }
139
140 public void createPhoneNumberShortcutIntent(Uri dataUri, String shortcutAction) {
141 new PhoneNumberLoadingAsyncTask(dataUri, shortcutAction).execute();
142 }
143
144 /**
145 * An asynchronous task that loads name, photo and other data from the database.
146 */
147 private abstract class LoadingAsyncTask extends AsyncTask<Void, Void, Void> {
148 protected Uri mUri;
149 protected String mContentType;
150 protected String mDisplayName;
Yorke Lee8baea882014-03-03 11:08:17 -0800151 protected String mLookupKey;
Chiao Chengbeca8562012-11-28 18:06:44 -0800152 protected byte[] mBitmapData;
153 protected long mPhotoId;
154
155 public LoadingAsyncTask(Uri uri) {
156 mUri = uri;
157 }
158
159 @Override
160 protected Void doInBackground(Void... params) {
161 mContentType = mContext.getContentResolver().getType(mUri);
162 loadData();
163 loadPhoto();
164 return null;
165 }
166
167 protected abstract void loadData();
168
169 private void loadPhoto() {
170 if (mPhotoId == 0) {
171 return;
172 }
173
174 ContentResolver resolver = mContext.getContentResolver();
175 Cursor cursor = resolver.query(Data.CONTENT_URI, PHOTO_COLUMNS, PHOTO_SELECTION,
176 new String[] { String.valueOf(mPhotoId) }, null);
177 if (cursor != null) {
178 try {
179 if (cursor.moveToFirst()) {
180 mBitmapData = cursor.getBlob(PHOTO_PHOTO_COLUMN_INDEX);
181 }
182 } finally {
183 cursor.close();
184 }
185 }
186 }
187 }
188
189 private final class ContactLoadingAsyncTask extends LoadingAsyncTask {
190 public ContactLoadingAsyncTask(Uri uri) {
191 super(uri);
192 }
193
194 @Override
195 protected void loadData() {
196 ContentResolver resolver = mContext.getContentResolver();
197 Cursor cursor = resolver.query(mUri, CONTACT_COLUMNS, null, null, null);
198 if (cursor != null) {
199 try {
200 if (cursor.moveToFirst()) {
201 mDisplayName = cursor.getString(CONTACT_DISPLAY_NAME_COLUMN_INDEX);
202 mPhotoId = cursor.getLong(CONTACT_PHOTO_ID_COLUMN_INDEX);
Yorke Lee8baea882014-03-03 11:08:17 -0800203 mLookupKey = cursor.getString(CONTACT_LOOKUP_KEY_COLUMN_INDEX);
Chiao Chengbeca8562012-11-28 18:06:44 -0800204 }
205 } finally {
206 cursor.close();
207 }
208 }
209 }
210 @Override
211 protected void onPostExecute(Void result) {
Yorke Lee8baea882014-03-03 11:08:17 -0800212 createContactShortcutIntent(mUri, mContentType, mDisplayName, mLookupKey, mBitmapData);
Chiao Chengbeca8562012-11-28 18:06:44 -0800213 }
214 }
215
216 private final class PhoneNumberLoadingAsyncTask extends LoadingAsyncTask {
217 private final String mShortcutAction;
218 private String mPhoneNumber;
219 private int mPhoneType;
220 private String mPhoneLabel;
221
222 public PhoneNumberLoadingAsyncTask(Uri uri, String shortcutAction) {
223 super(uri);
224 mShortcutAction = shortcutAction;
225 }
226
227 @Override
228 protected void loadData() {
229 ContentResolver resolver = mContext.getContentResolver();
230 Cursor cursor = resolver.query(mUri, PHONE_COLUMNS, null, null, null);
231 if (cursor != null) {
232 try {
233 if (cursor.moveToFirst()) {
234 mDisplayName = cursor.getString(PHONE_DISPLAY_NAME_COLUMN_INDEX);
235 mPhotoId = cursor.getLong(PHONE_PHOTO_ID_COLUMN_INDEX);
236 mPhoneNumber = cursor.getString(PHONE_NUMBER_COLUMN_INDEX);
237 mPhoneType = cursor.getInt(PHONE_TYPE_COLUMN_INDEX);
238 mPhoneLabel = cursor.getString(PHONE_LABEL_COLUMN_INDEX);
Yorke Lee8baea882014-03-03 11:08:17 -0800239 mLookupKey = cursor.getString(PHONE_LOOKUP_KEY_COLUMN_INDEX);
Chiao Chengbeca8562012-11-28 18:06:44 -0800240 }
241 } finally {
242 cursor.close();
243 }
244 }
245 }
246
247 @Override
248 protected void onPostExecute(Void result) {
Yorke Lee8baea882014-03-03 11:08:17 -0800249 createPhoneNumberShortcutIntent(mUri, mDisplayName, mLookupKey, mBitmapData,
250 mPhoneNumber, mPhoneType, mPhoneLabel, mShortcutAction);
Chiao Chengbeca8562012-11-28 18:06:44 -0800251 }
252 }
253
Yorke Lee8baea882014-03-03 11:08:17 -0800254 private Drawable getPhotoDrawable(byte[] bitmapData, String displayName, String lookupKey) {
Chiao Chengbeca8562012-11-28 18:06:44 -0800255 if (bitmapData != null) {
Yorke Lee8baea882014-03-03 11:08:17 -0800256 Bitmap bitmap = BitmapFactory.decodeByteArray(bitmapData, 0, bitmapData.length, null);
257 return new BitmapDrawable(mContext.getResources(), bitmap);
Chiao Chengbeca8562012-11-28 18:06:44 -0800258 } else {
Yorke Lee8baea882014-03-03 11:08:17 -0800259 return ContactPhotoManager.getDefaultAvatarDrawableForContact(mContext.getResources(),
Yorke Leec4a2a232014-04-28 17:53:42 -0700260 false, new DefaultImageRequest(displayName, lookupKey, false));
Chiao Chengbeca8562012-11-28 18:06:44 -0800261 }
Chiao Chengbeca8562012-11-28 18:06:44 -0800262 }
263
264 private void createContactShortcutIntent(Uri contactUri, String contentType, String displayName,
Yorke Lee8baea882014-03-03 11:08:17 -0800265 String lookupKey, byte[] bitmapData) {
266 Drawable drawable = getPhotoDrawable(bitmapData, displayName, lookupKey);
Chiao Chengbeca8562012-11-28 18:06:44 -0800267
Marcus Hagerottc5083f92016-09-14 08:34:29 -0700268 final Intent shortcutIntent = ImplicitIntentsUtil.getIntentForQuickContactLauncherShortcut(
269 mContext, contactUri);
Chiao Chengbeca8562012-11-28 18:06:44 -0800270
Yorke Lee8baea882014-03-03 11:08:17 -0800271 final Bitmap icon = generateQuickContactIcon(drawable);
Chiao Chengbeca8562012-11-28 18:06:44 -0800272
273 Intent intent = new Intent();
274 intent.putExtra(Intent.EXTRA_SHORTCUT_ICON, icon);
275 intent.putExtra(Intent.EXTRA_SHORTCUT_INTENT, shortcutIntent);
276 if (TextUtils.isEmpty(displayName)) {
277 intent.putExtra(Intent.EXTRA_SHORTCUT_NAME, mContext.getResources().getString(
278 R.string.missing_name));
279 } else {
280 intent.putExtra(Intent.EXTRA_SHORTCUT_NAME, displayName);
281 }
282
283 mListener.onShortcutIntentCreated(contactUri, intent);
284 }
285
Yorke Lee8baea882014-03-03 11:08:17 -0800286 private void createPhoneNumberShortcutIntent(Uri uri, String displayName, String lookupKey,
287 byte[] bitmapData, String phoneNumber, int phoneType, String phoneLabel,
288 String shortcutAction) {
289 Drawable drawable = getPhotoDrawable(bitmapData, displayName, lookupKey);
Chiao Chengbeca8562012-11-28 18:06:44 -0800290
Yorke Lee8baea882014-03-03 11:08:17 -0800291 Bitmap bitmap;
Chiao Chengbeca8562012-11-28 18:06:44 -0800292 Uri phoneUri;
293 if (Intent.ACTION_CALL.equals(shortcutAction)) {
294 // Make the URI a direct tel: URI so that it will always continue to work
Jay Shraunered1a3b22014-09-05 15:37:27 -0700295 phoneUri = Uri.fromParts(PhoneAccount.SCHEME_TEL, phoneNumber, null);
Yorke Lee8baea882014-03-03 11:08:17 -0800296 bitmap = generatePhoneNumberIcon(drawable, phoneType, phoneLabel,
John Shaobd9ef3c2016-12-15 12:42:03 -0800297 R.drawable.quantum_ic_phone_vd_theme_24);
Chiao Chengbeca8562012-11-28 18:06:44 -0800298 } else {
Jay Shraunered1a3b22014-09-05 15:37:27 -0700299 phoneUri = Uri.fromParts(ContactsUtils.SCHEME_SMSTO, phoneNumber, null);
Yorke Lee8baea882014-03-03 11:08:17 -0800300 bitmap = generatePhoneNumberIcon(drawable, phoneType, phoneLabel,
John Shaobd9ef3c2016-12-15 12:42:03 -0800301 R.drawable.quantum_ic_message_vd_theme_24);
Chiao Chengbeca8562012-11-28 18:06:44 -0800302 }
303
304 Intent shortcutIntent = new Intent(shortcutAction, phoneUri);
305 shortcutIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
306
307 Intent intent = new Intent();
308 intent.putExtra(Intent.EXTRA_SHORTCUT_ICON, bitmap);
309 intent.putExtra(Intent.EXTRA_SHORTCUT_INTENT, shortcutIntent);
guanxiongliu4f715ff2016-03-10 14:14:54 -0800310
311 if (TextUtils.isEmpty(displayName)) {
312 displayName = mContext.getResources().getString(R.string.missing_name);
313 }
314 if (TextUtils.equals(shortcutAction, Intent.ACTION_CALL)) {
315 intent.putExtra(Intent.EXTRA_SHORTCUT_NAME,
316 mContext.getResources().getString(R.string.call_by_shortcut, displayName));
317 } else if (TextUtils.equals(shortcutAction, Intent.ACTION_SENDTO)) {
318 intent.putExtra(Intent.EXTRA_SHORTCUT_NAME,
319 mContext.getResources().getString(R.string.sms_by_shortcut, displayName));
320 }
Chiao Chengbeca8562012-11-28 18:06:44 -0800321
322 mListener.onShortcutIntentCreated(uri, intent);
323 }
324
Yorke Lee8baea882014-03-03 11:08:17 -0800325 private Bitmap generateQuickContactIcon(Drawable photo) {
Chiao Chengbeca8562012-11-28 18:06:44 -0800326
327 // Setup the drawing classes
Brian Attwell5b53b8d2014-07-07 22:22:51 -0700328 Bitmap bitmap = Bitmap.createBitmap(mIconSize, mIconSize, Bitmap.Config.ARGB_8888);
329 Canvas canvas = new Canvas(bitmap);
Chiao Chengbeca8562012-11-28 18:06:44 -0800330
331 // Copy in the photo
Chiao Chengbeca8562012-11-28 18:06:44 -0800332 Rect dst = new Rect(0,0, mIconSize, mIconSize);
Yorke Lee8baea882014-03-03 11:08:17 -0800333 photo.setBounds(dst);
334 photo.draw(canvas);
Chiao Chengbeca8562012-11-28 18:06:44 -0800335
Brian Attwell5b53b8d2014-07-07 22:22:51 -0700336 // Draw the icon with a rounded border
Chris Craik93fb4352014-08-05 18:51:49 -0700337 RoundedBitmapDrawable roundedDrawable =
338 RoundedBitmapDrawableFactory.create(mResources, bitmap);
Brian Attwell5b53b8d2014-07-07 22:22:51 -0700339 roundedDrawable.setAntiAlias(true);
340 roundedDrawable.setCornerRadius(mIconSize / 2);
341 Bitmap roundedBitmap = Bitmap.createBitmap(mIconSize, mIconSize, Bitmap.Config.ARGB_8888);
342 canvas.setBitmap(roundedBitmap);
343 roundedDrawable.setBounds(dst);
344 roundedDrawable.draw(canvas);
Chiao Chengbeca8562012-11-28 18:06:44 -0800345 canvas.setBitmap(null);
346
Brian Attwell5b53b8d2014-07-07 22:22:51 -0700347 return roundedBitmap;
Chiao Chengbeca8562012-11-28 18:06:44 -0800348 }
349
350 /**
351 * Generates a phone number shortcut icon. Adds an overlay describing the type of the phone
352 * number, and if there is a photo also adds the call action icon.
353 */
Yorke Lee8baea882014-03-03 11:08:17 -0800354 private Bitmap generatePhoneNumberIcon(Drawable photo, int phoneType, String phoneLabel,
Chiao Chengbeca8562012-11-28 18:06:44 -0800355 int actionResId) {
356 final Resources r = mContext.getResources();
357 final float density = r.getDisplayMetrics().density;
358
359 Bitmap phoneIcon = ((BitmapDrawable) r.getDrawableForDensity(actionResId, mIconDensity))
360 .getBitmap();
361
Brian Attwell98771ce2014-09-02 12:54:53 -0700362 Bitmap icon = generateQuickContactIcon(photo);
Chiao Chengbeca8562012-11-28 18:06:44 -0800363 Canvas canvas = new Canvas(icon);
364
365 // Copy in the photo
366 Paint photoPaint = new Paint();
367 photoPaint.setDither(true);
368 photoPaint.setFilterBitmap(true);
Chiao Chengbeca8562012-11-28 18:06:44 -0800369 Rect dst = new Rect(0, 0, mIconSize, mIconSize);
Yorke Lee8baea882014-03-03 11:08:17 -0800370
Chiao Chengbeca8562012-11-28 18:06:44 -0800371 // Create an overlay for the phone number type
372 CharSequence overlay = Phone.getTypeLabel(r, phoneType, phoneLabel);
373
374 if (overlay != null) {
375 TextPaint textPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG | Paint.DEV_KERN_TEXT_FLAG);
376 textPaint.setTextSize(r.getDimension(R.dimen.shortcut_overlay_text_size));
377 textPaint.setColor(r.getColor(R.color.textColorIconOverlay));
378 textPaint.setShadowLayer(4f, 0, 2f, r.getColor(R.color.textColorIconOverlayShadow));
379
380 final FontMetricsInt fmi = textPaint.getFontMetricsInt();
381
382 // First fill in a darker background around the text to be drawn
383 final Paint workPaint = new Paint();
Brian Attwell98771ce2014-09-02 12:54:53 -0700384 workPaint.setColor(mOverlayTextBackgroundColor);
Chiao Chengbeca8562012-11-28 18:06:44 -0800385 workPaint.setStyle(Paint.Style.FILL);
386 final int textPadding = r
387 .getDimensionPixelOffset(R.dimen.shortcut_overlay_text_background_padding);
388 final int textBandHeight = (fmi.descent - fmi.ascent) + textPadding * 2;
Brian Attwell98771ce2014-09-02 12:54:53 -0700389 dst.set(0, mIconSize - textBandHeight, mIconSize, mIconSize);
Chiao Chengbeca8562012-11-28 18:06:44 -0800390 canvas.drawRect(dst, workPaint);
391
Brian Attwell98771ce2014-09-02 12:54:53 -0700392 overlay = TextUtils.ellipsize(overlay, textPaint, mIconSize, TruncateAt.END);
Chiao Chengbeca8562012-11-28 18:06:44 -0800393 final float textWidth = textPaint.measureText(overlay, 0, overlay.length());
394 canvas.drawText(overlay, 0, overlay.length(), (mIconSize - textWidth) / 2, mIconSize
395 - fmi.descent - textPadding, textPaint);
396 }
397
398 // Draw the phone action icon as an overlay
Yorke Lee8baea882014-03-03 11:08:17 -0800399 Rect src = new Rect(0, 0, phoneIcon.getWidth(), phoneIcon.getHeight());
Chiao Chengbeca8562012-11-28 18:06:44 -0800400 int iconWidth = icon.getWidth();
401 dst.set(iconWidth - ((int) (20 * density)), -1,
402 iconWidth, ((int) (19 * density)));
Chiao Chengbeca8562012-11-28 18:06:44 -0800403 canvas.drawBitmap(phoneIcon, src, dst, photoPaint);
404
405 canvas.setBitmap(null);
406
407 return icon;
408 }
409}