1ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian/*
2ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian * Copyright (C) 2016 The Android Open Source Project
3ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian *
4ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian * Licensed under the Apache License, Version 2.0 (the "License");
5ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian * you may not use this file except in compliance with the License.
6ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian * You may obtain a copy of the License at
7ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian *
8ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian *      http://www.apache.org/licenses/LICENSE-2.0
9ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian *
10ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian * Unless required by applicable law or agreed to in writing, software
11ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian * distributed under the License is distributed on an "AS IS" BASIS,
12ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian * See the License for the specific language governing permissions and
14ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian * limitations under the License.
15ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian */
16ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian
17ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanianpackage com.android.dialer.shortcuts;
18ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian
19ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanianimport android.content.Context;
20ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanianimport android.content.pm.ShortcutInfo;
21ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanianimport android.graphics.Bitmap;
22ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanianimport android.graphics.BitmapFactory;
23d53c83faca6b8b501d961528f917314d0c817969keyboardrimport android.graphics.drawable.AdaptiveIconDrawable;
24ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanianimport android.graphics.drawable.Drawable;
25ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanianimport android.graphics.drawable.Icon;
26ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanianimport android.net.Uri;
27d53c83faca6b8b501d961528f917314d0c817969keyboardrimport android.os.Build.VERSION;
28d53c83faca6b8b501d961528f917314d0c817969keyboardrimport android.os.Build.VERSION_CODES;
29ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanianimport android.provider.ContactsContract;
30ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanianimport android.support.annotation.NonNull;
31d53c83faca6b8b501d961528f917314d0c817969keyboardrimport android.support.annotation.Nullable;
32d53c83faca6b8b501d961528f917314d0c817969keyboardrimport android.support.annotation.RequiresApi;
33ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanianimport android.support.annotation.WorkerThread;
34ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanianimport android.support.v4.graphics.drawable.RoundedBitmapDrawable;
35ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanianimport android.support.v4.graphics.drawable.RoundedBitmapDrawableFactory;
36ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanianimport com.android.contacts.common.lettertiles.LetterTileDrawable;
37ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanianimport com.android.dialer.common.Assert;
38ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanianimport com.android.dialer.util.DrawableConverter;
39ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanianimport java.io.InputStream;
40ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian
41ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian/** Constructs the icons for dialer shortcuts. */
42ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanianclass IconFactory {
43ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian
44ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian  private final Context context;
45ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian
46ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian  IconFactory(@NonNull Context context) {
47ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    this.context = context;
48ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian  }
49ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian
50ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian  /**
51ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian   * Creates an icon for the provided {@link DialerShortcut}.
52ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian   *
53ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian   * <p>The icon is a circle which contains a photo of the contact associated with the shortcut, if
54ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian   * available. If a photo is not available, a circular colored icon with a single letter is instead
55ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian   * created, where the letter is the first letter of the contact's name. If the contact has no
56ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian   * name, a default colored "anonymous" avatar is used.
57ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian   *
58ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian   * <p>These icons should match exactly the favorites tiles in the starred tab of the dialer
59ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian   * application, except that they are circular instead of rectangular.
60ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian   */
61ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian  @WorkerThread
62ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian  @NonNull
63ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian  public Icon create(@NonNull DialerShortcut shortcut) {
64ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    Assert.isWorkerThread();
65ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian
66ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    return create(shortcut.getLookupUri(), shortcut.getDisplayName(), shortcut.getLookupKey());
67ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian  }
68ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian
69ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian  /** Same as {@link #create(DialerShortcut)}, but accepts a {@link ShortcutInfo}. */
70ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian  @WorkerThread
71ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian  @NonNull
72ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian  public Icon create(@NonNull ShortcutInfo shortcutInfo) {
73ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    Assert.isWorkerThread();
74ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    return create(
75ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian        DialerShortcut.getLookupUriFromShortcutInfo(shortcutInfo),
76ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian        DialerShortcut.getDisplayNameFromShortcutInfo(shortcutInfo),
77ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian        DialerShortcut.getLookupKeyFromShortcutInfo(shortcutInfo));
78ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian  }
79ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian
80ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian  @WorkerThread
81ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian  @NonNull
82ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian  private Icon create(
83ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian      @NonNull Uri lookupUri, @NonNull String displayName, @NonNull String lookupKey) {
84ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    Assert.isWorkerThread();
85ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian
86ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    // In testing, there was no difference between high-res and thumbnail.
87ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    InputStream inputStream =
88ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian        ContactsContract.Contacts.openContactPhotoInputStream(
89ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian            context.getContentResolver(), lookupUri, false /* preferHighres */);
90ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian
91d53c83faca6b8b501d961528f917314d0c817969keyboardr    return VERSION.SDK_INT >= VERSION_CODES.O
92d53c83faca6b8b501d961528f917314d0c817969keyboardr        ? createAdaptiveIcon(displayName, lookupKey, inputStream)
93d53c83faca6b8b501d961528f917314d0c817969keyboardr        : createFlatIcon(displayName, lookupKey, inputStream);
94d53c83faca6b8b501d961528f917314d0c817969keyboardr  }
95d53c83faca6b8b501d961528f917314d0c817969keyboardr
96d53c83faca6b8b501d961528f917314d0c817969keyboardr  @RequiresApi(VERSION_CODES.O)
97d53c83faca6b8b501d961528f917314d0c817969keyboardr  private Icon createAdaptiveIcon(
98d53c83faca6b8b501d961528f917314d0c817969keyboardr      @NonNull String displayName, @NonNull String lookupKey, @Nullable InputStream inputStream) {
99d53c83faca6b8b501d961528f917314d0c817969keyboardr    if (inputStream == null) {
100d53c83faca6b8b501d961528f917314d0c817969keyboardr      LetterTileDrawable letterTileDrawable = new LetterTileDrawable(context.getResources());
101d53c83faca6b8b501d961528f917314d0c817969keyboardr      // The adaptive icons clip the drawable to a safe area inside the drawable. Scale the letter
102d53c83faca6b8b501d961528f917314d0c817969keyboardr      // so it fits inside the safe area.
103d53c83faca6b8b501d961528f917314d0c817969keyboardr      letterTileDrawable.setScale(1f / (1f + AdaptiveIconDrawable.getExtraInsetFraction()));
104d53c83faca6b8b501d961528f917314d0c817969keyboardr      letterTileDrawable.setCanonicalDialerLetterTileDetails(
105d53c83faca6b8b501d961528f917314d0c817969keyboardr          displayName,
106d53c83faca6b8b501d961528f917314d0c817969keyboardr          lookupKey,
107d53c83faca6b8b501d961528f917314d0c817969keyboardr          LetterTileDrawable.SHAPE_RECTANGLE,
108d53c83faca6b8b501d961528f917314d0c817969keyboardr          LetterTileDrawable.TYPE_DEFAULT);
109d53c83faca6b8b501d961528f917314d0c817969keyboardr
110d53c83faca6b8b501d961528f917314d0c817969keyboardr      int iconSize =
111d53c83faca6b8b501d961528f917314d0c817969keyboardr          context
112d53c83faca6b8b501d961528f917314d0c817969keyboardr              .getResources()
113d53c83faca6b8b501d961528f917314d0c817969keyboardr              .getDimensionPixelSize(R.dimen.launcher_shortcut_adaptive_icon_size);
114d53c83faca6b8b501d961528f917314d0c817969keyboardr      return Icon.createWithAdaptiveBitmap(
115d53c83faca6b8b501d961528f917314d0c817969keyboardr          DrawableConverter.drawableToBitmap(letterTileDrawable, iconSize, iconSize));
116d53c83faca6b8b501d961528f917314d0c817969keyboardr    }
117d53c83faca6b8b501d961528f917314d0c817969keyboardr    Bitmap bitmap = BitmapFactory.decodeStream(inputStream);
118d53c83faca6b8b501d961528f917314d0c817969keyboardr    return Icon.createWithAdaptiveBitmap(bitmap);
119d53c83faca6b8b501d961528f917314d0c817969keyboardr  }
120d53c83faca6b8b501d961528f917314d0c817969keyboardr
121d53c83faca6b8b501d961528f917314d0c817969keyboardr  private Icon createFlatIcon(
122d53c83faca6b8b501d961528f917314d0c817969keyboardr      @NonNull String displayName, @NonNull String lookupKey, @Nullable InputStream inputStream) {
123ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    Drawable drawable;
124ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    if (inputStream == null) {
125ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian      // No photo for contact; use a letter tile.
126ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian      LetterTileDrawable letterTileDrawable = new LetterTileDrawable(context.getResources());
127ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian      letterTileDrawable.setCanonicalDialerLetterTileDetails(
128ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian          displayName, lookupKey, LetterTileDrawable.SHAPE_CIRCLE, LetterTileDrawable.TYPE_DEFAULT);
129ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian      drawable = letterTileDrawable;
130ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    } else {
131ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian      // There's a photo, create a circular drawable from it.
132ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian      Bitmap bitmap = BitmapFactory.decodeStream(inputStream);
133ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian      drawable = createCircularDrawable(bitmap);
134ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    }
135ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    int iconSize =
136ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian        context.getResources().getDimensionPixelSize(R.dimen.launcher_shortcut_icon_size);
137ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    return Icon.createWithBitmap(
138ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian        DrawableConverter.drawableToBitmap(drawable, iconSize /* width */, iconSize /* height */));
139ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian  }
140ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian
141ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian  @NonNull
142ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian  private Drawable createCircularDrawable(@NonNull Bitmap bitmap) {
143ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    RoundedBitmapDrawable roundedBitmapDrawable =
144ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian        RoundedBitmapDrawableFactory.create(context.getResources(), bitmap);
145ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    roundedBitmapDrawable.setCircular(true);
146ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    roundedBitmapDrawable.setAntiAlias(true);
147ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian    return roundedBitmapDrawable;
148ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian  }
149ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian}
150