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