summaryrefslogtreecommitdiff
path: root/packages/SettingsLib/src/com/android/settingslib/notification/ConversationIconFactory.java
blob: ebdfbeade99d21560228e545c9d95f00c1452269 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
/*
 * Copyright (C) 2020 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.android.settingslib.notification;

import android.annotation.ColorInt;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.LauncherApps;
import android.content.pm.PackageManager;
import android.content.pm.ShortcutInfo;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.ColorFilter;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.os.UserHandle;
import android.util.IconDrawableFactory;
import android.util.Log;

import com.android.launcher3.icons.BaseIconFactory;
import com.android.settingslib.R;
import com.android.settingslib.Utils;

/**
 * Factory for creating normalized conversation icons.
 * We are not using Launcher's IconFactory because conversation rendering only runs on the UI
 * thread, so there is no need to manage a pool across multiple threads. Launcher's rendering
 * also includes shadows, which are only appropriate on top of wallpaper, not embedded in UI.
 */
public class ConversationIconFactory extends BaseIconFactory {
    // Geometry of the various parts of the design. All values are 1dp on a 56x56dp icon grid.
    // Space is left around the "head" (main avatar) for
    // ........
    // .HHHHHH.
    // .HHHrrrr
    // .HHHrBBr
    // ....rrrr
    // This is trying to recreate the view layout in notification_template_material_conversation.xml

    private static final float HEAD_SIZE = 52f;
    private static final float BADGE_SIZE = 12f;
    private static final float BADGE_CENTER = 46f;
    private static final float CIRCLE_MARGIN = 36f;
    private static final float BADGE_ORIGIN = HEAD_SIZE - BADGE_SIZE; // 40f
    private static final float BASE_ICON_SIZE = 56f;

    private static final float OUT_CIRCLE_DIA = (BASE_ICON_SIZE - CIRCLE_MARGIN); // 20f
    private static final float INN_CIRCLE_DIA = (float) Math.sqrt(2 * BADGE_SIZE * BADGE_SIZE) ;
    private static final float OUT_CIRCLE_RAD = OUT_CIRCLE_DIA / 2;
    private static final float INN_CIRCLE_RAD = INN_CIRCLE_DIA / 2;
    // Android draws strokes centered on the radius, so our actual radius is an avg of the outside
    // and inside of the ring stroke
    private static final float CIRCLE_RADIUS =
            INN_CIRCLE_RAD + ((OUT_CIRCLE_RAD - INN_CIRCLE_RAD) / 2);
    private static final float RING_STROKE_WIDTH = (OUT_CIRCLE_DIA - INN_CIRCLE_DIA) / 2;

    final LauncherApps mLauncherApps;
    final PackageManager mPackageManager;
    final IconDrawableFactory mIconDrawableFactory;
    private int mImportantConversationColor;

    public ConversationIconFactory(Context context, LauncherApps la, PackageManager pm,
            IconDrawableFactory iconDrawableFactory, int iconSizePx) {
        super(context, context.getResources().getConfiguration().densityDpi,
                iconSizePx);
        mLauncherApps = la;
        mPackageManager = pm;
        mIconDrawableFactory = iconDrawableFactory;
        mImportantConversationColor = context.getResources().getColor(
                R.color.important_conversation, null);
    }

    /**
     * Returns the conversation info drawable
     */
    public Drawable getBaseIconDrawable(ShortcutInfo shortcutInfo) {
        return mLauncherApps.getShortcutIconDrawable(shortcutInfo, mFillResIconDpi);
    }

    /**
     * Get the {@link Drawable} that represents the app icon, badged with the work profile icon
     * if appropriate.
     */
    public Drawable getAppBadge(String packageName, int userId) {
        Drawable badge = null;
        try {
            final ApplicationInfo appInfo = mPackageManager.getApplicationInfoAsUser(
                    packageName, PackageManager.GET_META_DATA, userId);
            badge = Utils.getBadgedIcon(mContext, appInfo);
        } catch (PackageManager.NameNotFoundException e) {
            badge = mPackageManager.getDefaultActivityIcon();
        }
        return badge;
    }

    /**
     * Returns a {@link Drawable} for the entire conversation. The shortcut icon will be badged
     * with the launcher icon of the app specified by packageName.
     */
    public Drawable getConversationDrawable(ShortcutInfo info, String packageName, int uid,
            boolean important) {
        return getConversationDrawable(getBaseIconDrawable(info), packageName, uid, important);
    }

    /**
     * Returns a {@link Drawable} for the entire conversation. The drawable will be badged
     * with the launcher icon of the app specified by packageName.
     */
    public Drawable getConversationDrawable(Drawable baseIcon, String packageName, int uid,
            boolean important) {
        return new ConversationIconDrawable(baseIcon,
                getAppBadge(packageName, UserHandle.getUserId(uid)),
                mIconBitmapSize,
                mImportantConversationColor,
                important);
    }

    /**
     * Custom Drawable that overlays a badge drawable (e.g. notification small icon or app icon) on
     * a base icon (conversation/person avatar), plus decorations indicating conversation
     * importance.
     */
    public static class ConversationIconDrawable extends Drawable {
        private Drawable mBaseIcon;
        private Drawable mBadgeIcon;
        private int mIconSize;
        private Paint mRingPaint;
        private boolean mShowRing;
        private Paint mPaddingPaint;

        public ConversationIconDrawable(Drawable baseIcon,
                Drawable badgeIcon,
                int iconSize,
                @ColorInt int ringColor,
                boolean showImportanceRing) {
            mBaseIcon = baseIcon;
            mBadgeIcon = badgeIcon;
            mIconSize = iconSize;
            mShowRing = showImportanceRing;
            mRingPaint = new Paint();
            mRingPaint.setStyle(Paint.Style.STROKE);
            mRingPaint.setColor(ringColor);
            mPaddingPaint = new Paint();
            mPaddingPaint.setStyle(Paint.Style.FILL_AND_STROKE);
            mPaddingPaint.setColor(Color.WHITE);
        }

        /**
         * Show or hide the importance ring.
         */
        public void setImportant(boolean important) {
            if (important != mShowRing) {
                mShowRing = important;
                invalidateSelf();
            }
        }

        @Override
        public int getIntrinsicWidth() {
            return mIconSize;
        }

        @Override
        public int getIntrinsicHeight() {
            return mIconSize;
        }

        // Similar to badgeWithDrawable, but relying on the bounds of each underlying drawable
        @Override
        public void draw(Canvas canvas) {
            final Rect bounds = getBounds();

            // scale to our internal grid
            final float scale = bounds.width() / BASE_ICON_SIZE;
            final int ringStrokeWidth = (int) (RING_STROKE_WIDTH * scale);
            final int headSize = (int) (HEAD_SIZE * scale);
            final int badgePadding = (int) (BADGE_ORIGIN * scale);
            final int badgeCenter = (int) (BADGE_CENTER * scale);

            mPaddingPaint.setStrokeWidth(ringStrokeWidth);
            final float radius = (int) (CIRCLE_RADIUS * scale); // stroke outside
            if (mBaseIcon != null) {
                mBaseIcon.setBounds(0,
                        0,
                        headSize ,
                        headSize);
                mBaseIcon.draw(canvas);
            } else {
                Log.w("ConversationIconFactory", "ConversationIconDrawable has null base icon");
            }
            if (mBadgeIcon != null) {
                canvas.drawCircle(badgeCenter, badgeCenter, radius, mPaddingPaint);
                mBadgeIcon.setBounds(
                        badgePadding,
                        badgePadding,
                        headSize,
                        headSize);
                mBadgeIcon.draw(canvas);
            } else {
                Log.w("ConversationIconFactory", "ConversationIconDrawable has null badge icon");
            }
            if (mShowRing) {
                mRingPaint.setStrokeWidth(ringStrokeWidth);
                canvas.drawCircle(badgeCenter, badgeCenter, radius, mRingPaint);
            }
        }

        @Override
        public void setAlpha(int alpha) {
            // unimplemented
        }

        @Override
        public void setColorFilter(ColorFilter colorFilter) {
            // unimplemented
        }

        @Override
        public int getOpacity() {
            return 0;
        }
    }
}