android13#launcher3#未读消息

334 阅读7分钟

1.简介

先看ui,再看数据来源

  • dot是4.4画出来的,颜色是2.3读取的
  • 小节7,8,9主要学习下launcher里通知服务的启动,数据的刷新问题。

2.BubbleTextView.java

桌面用到的图标基本都是这个控件或者它的子类

public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver,
        IconLabelDotView, DraggableView, Reorderable {

2.1.onDraw

    public void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        drawDotIfNecessary(canvas);
    }

    //在右上角画通知未读提示
    protected void drawDotIfNecessary(Canvas canvas) {
        if (!mForceHideDot && (hasDot() || mDotParams.scale > 0)) {
            //见补充2,根据iconSize和view的宽,获取dot需要的bounds
            getIconBounds(mDotParams.iconBounds);
            //3.1,bounds进行缩小处理
            Utilities.scaleRectAboutCenter(mDotParams.iconBounds,
                    IconShape.getNormalizationScale());//0.92f
            final int scrollX = getScrollX();
            final int scrollY = getScrollY();
            canvas.translate(scrollX, scrollY);
            //见4.4
            mDotRenderer.draw(canvas, mDotParams);
            canvas.translate(-scrollX, -scrollY);
        }
    }

>1.hasDot

    private boolean hasDot() {
        return mDotInfo != null;
    }

>2.getIconBounds

    public void getIconBounds(Rect outBounds) {
        getIconBounds(mIconSize, outBounds);
    }

    public void getIconBounds(int iconSize, Rect outBounds) {
        outBounds.set(0, 0, iconSize, iconSize);
        if (mLayoutHorizontal) {
            int top = (getHeight() - iconSize) / 2;
            if (mIsRtl) {
                outBounds.offsetTo(getWidth() - iconSize - getPaddingRight(), top);
            } else {
                outBounds.offsetTo(getPaddingLeft(), top);
            }
        } else {
        //水平方向居中,顶部偏移paddingTop
            outBounds.offset((getWidth() - iconSize) / 2, getPaddingTop());
        }
    }

2.2.applyDotState

    public void applyDotState(ItemInfo itemInfo, boolean animate) {
        if (mIcon instanceof FastBitmapDrawable) {
            boolean wasDotted = mDotInfo != null;
            //见补充2
            mDotInfo = mActivity.getDotInfoForItem(itemInfo);
            boolean isDotted = mDotInfo != null;
            float newDotScale = isDotted ? 1f : 0;
            //两种render,见补充1
            if (mDisplay == DISPLAY_ALL_APPS) {
                mDotRenderer = mActivity.getDeviceProfile().mDotRendererAllApps;
            } else {
                mDotRenderer = mActivity.getDeviceProfile().mDotRendererWorkSpace;
            }
            if (wasDotted || isDotted) {
                // Animate when a dot is first added or when it is removed.
                if (animate && (wasDotted ^ isDotted) && isShown()) {
                    animateDotScale(newDotScale);
                } else {
                    cancelDotScaleAnim();
                    mDotParams.scale = newDotScale;
                    invalidate();
                }
            }
            if (!TextUtils.isEmpty(itemInfo.contentDescription)) {
                if (itemInfo.isDisabled()) {
                    setContentDescription(getContext().getString(R.string.disabled_app_label,
                            itemInfo.contentDescription));
                } else if (hasDot()) {
                    int count = mDotInfo.getNotificationCount();
                    setContentDescription(
                            getAppLabelPluralString(itemInfo.contentDescription.toString(), count));
                } else {
                    setContentDescription(itemInfo.contentDescription);
                }
            }
        }
    }

>1.DeviceProfile.java

    private static final int DEFAULT_DOT_SIZE = 100;
    
        //可以看到,就是大小不一样
        mDotRendererWorkSpace = createDotRenderer(iconSizePx, dotRendererCache);
        mDotRendererAllApps = createDotRenderer(allAppsIconSizePx, dotRendererCache);
    private static DotRenderer createDotRenderer(
            int size, @NonNull SparseArray<DotRenderer> cache) {
        DotRenderer renderer = cache.get(size);
        if (renderer == null) {
        //见小节4
            renderer = new DotRenderer(size, getShapePath(DEFAULT_DOT_SIZE), DEFAULT_DOT_SIZE);
            cache.put(size, renderer);
        }
        return renderer;
    }

>2.getDotInfoForItem

PopupDataProvider.java

    public @Nullable DotInfo getDotInfoForItem(@NonNull ItemInfo info) {
        if (!ShortcutUtil.supportsShortcuts(info)) {
            return null;
        }
        //先从map里读取dot数据
        DotInfo dotInfo = mPackageUserToDotInfos.get(PackageUserKey.fromItemInfo(info));
        if (dotInfo == null) {
            return null;
        }
        //dotInfo不为空的话再转化数据
        List<NotificationKeyData> notifications = getNotificationsForItem(
                info, dotInfo.getNotificationKeys());
        if (notifications.isEmpty()) {
            return null;
        }
        return dotInfo;
    }

2.3.applyIconAndLabel

    protected void applyIconAndLabel(ItemInfoWithIcon info) {
        boolean useTheme = mDisplay == DISPLAY_WORKSPACE || mDisplay == DISPLAY_FOLDER
                || mDisplay == DISPLAY_TASKBAR;
        int flags = useTheme ? FLAG_THEMED : 0;
        if (mHideBadge) {
            flags |= FLAG_NO_BADGE;
        }
        //见6.1
        FastBitmapDrawable iconDrawable = info.newIcon(getContext(), flags);
        //dot两种颜色设置
        //这个是应用图标的主色调
        mDotParams.appColor = iconDrawable.getIconColor();
        //这个是dot的颜色,读取的配置文件
        mDotParams.dotColor = getContext().getResources()
                .getColor(android.R.color.system_accent3_200, getContext().getTheme());
        setIcon(iconDrawable);
        applyLabel(info);
    }

2.4.applyFromWorkspaceItem

workspace页面的数据走这里,包括hotseat

    public void applyFromWorkspaceItem(WorkspaceItemInfo info, boolean promiseStateChanged) {
        //见2.3
        applyIconAndLabel(info);
        //补充1,就是设置tag
        setItemInfo(info);
        //进度条
        applyLoadingState(promiseStateChanged);
        //见2.2
        applyDotState(info, false /* animate */);
        setDownloadStateContentDescription(info, info.getProgressLevel());
    }

>1.setItemInfo

    protected void setItemInfo(ItemInfoWithIcon itemInfo) {
        setTag(itemInfo);
    }

2.5.applyFromApplicationInfo

allApps页面的数据走这里

    public void applyFromApplicationInfo(AppInfo info) {
        applyIconAndLabel(info);

        // We don't need to check the info since it's not a WorkspaceItemInfo
        setItemInfo(info);


        // Verify high res immediately
        verifyHighRes();

        if ((info.runtimeStatusFlags & ItemInfoWithIcon.FLAG_SHOW_DOWNLOAD_PROGRESS_MASK) != 0) {
            applyProgressLevel();
        }
        applyDotState(info, false /* animate */);
        setDownloadStateContentDescription(info, info.getProgressLevel());
    }

3.Utilities.java

3.1.scaleRectAboutCenter

从中心点开始缩小

    public static void scaleRectAboutCenter(Rect r, float scale) {
        if (scale != 1.0f) {
            int cx = r.centerX();
            int cy = r.centerY();
            r.offset(-cx, -cy);
            r.left = (int) (r.left * scale + 0.5f);
            r.top = (int) (r.top * scale + 0.5f);
            r.right = (int) (r.right * scale + 0.5f);
            r.bottom = (int) (r.bottom * scale + 0.5f);
            r.offset(cx, cy);
        }
    }

4.DotRenderer.java

4.1.构造方法

    private static final float SIZE_PERCENTAGE = 0.228f;
    
    public DotRenderer(int iconSizePx, Path iconShapePath, int pathSize) {
        //圆点的大小和icon的大小有关,百分比见上边的常量
        int size = Math.round(SIZE_PERCENTAGE * iconSizePx);
        if (size <= 0) {
            size = MIN_DOT_SIZE;
        }
        ShadowGenerator.Builder builder = new ShadowGenerator.Builder(Color.TRANSPARENT);
        builder.ambientShadowAlpha = 88;
        //见4.2,生成背景图
        mBackgroundWithShadow = builder.setupBlurForSize(size).createPill(size, size);
        mCircleRadius = builder.radius;

        mBitmapOffset = -mBackgroundWithShadow.getHeight() * 0.5f; // Same as width.

        //找到路径上最接近左上角和右上角的点
        mLeftDotPosition = getPathPoint(iconShapePath, pathSize, -1);
        mRightDotPosition = getPathPoint(iconShapePath, pathSize, 1);
    }

4.2.ShadowGenerator

>1.setupBlurForSize

        public Builder setupBlurForSize(int height) {
            if (ENABLE_SHADOWS) {//走这里
                shadowBlur = height * 1f / 24;
                keyShadowDistance = height * 1f / 16;
            } else {
                shadowBlur = 0;
                keyShadowDistance = 0;
            }
            return this;
        }

>2.createPill

创建圆点bitmap

        public Bitmap createPill(int width, int height) {
            return createPill(width, height, height / 2f);
        }

        public Bitmap createPill(int width, int height, float r) {
            radius = r;

            int centerX = Math.round(width / 2f + shadowBlur);
            int centerY = Math.round(radius + shadowBlur + keyShadowDistance);
            int center = Math.max(centerX, centerY);
            bounds.set(0, 0, width, height);
            bounds.offsetTo(center - width / 2f, center - height / 2f);

            int size = center * 2;
            //见4.3.1,核心就是回调方法,也就是补充3
            return BitmapRenderer.createHardwareBitmap(size, size, this::drawShadow);
        }

>3.drawShadow

圆点背景图就是这里画出来的

        public void drawShadow(Canvas c) {
            Paint p = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
            p.setColor(color);

            if (ENABLE_SHADOWS) {
                // Key shadow
                p.setShadowLayer(shadowBlur, 0, keyShadowDistance,
                        setColorAlphaBound(Color.BLACK, keyShadowAlpha));//见5.2
                c.drawRoundRect(bounds, radius, radius, p);

                // Ambient shadow
                p.setShadowLayer(shadowBlur, 0, 0,
                        setColorAlphaBound(Color.BLACK, ambientShadowAlpha));
                c.drawRoundRect(bounds, radius, radius, p);
            }

            if (Color.alpha(color) < 255) {
                // Clear any content inside the pill-rect for translucent fill.
                p.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
                p.clearShadowLayer();
                p.setColor(Color.BLACK);
                c.drawRoundRect(bounds, radius, radius, p);

                p.setXfermode(null);
                p.setColor(color);
                c.drawRoundRect(bounds, radius, radius, p);
            }
        }

4.3.BitmapRenderer.java

>1.createHardwareBitmap

    static Bitmap createHardwareBitmap(int width, int height, BitmapRenderer renderer) {
        if (!USE_HARDWARE_BITMAP) {
            return createSoftwareBitmap(width, height, renderer);
        }
        //走这里
        GraphicsUtils.noteNewBitmapCreated();
        Picture picture = new Picture();
        renderer.draw(picture.beginRecording(width, height));
        picture.endRecording();
        return Bitmap.createBitmap(picture);
    }

4.4.draw

    public void draw(Canvas canvas, DrawParams params) {
        if (params == null) {
            return;
        }
        canvas.save();
        //获取dot中心点位置,数据见2.1
        Rect iconBounds = params.iconBounds;
        float[] dotPosition = params.leftAlign ? mLeftDotPosition : mRightDotPosition;
        float dotCenterX = iconBounds.left + iconBounds.width() * dotPosition[0];
        float dotCenterY = iconBounds.top + iconBounds.height() * dotPosition[1];

        // Ensure dot fits entirely in canvas clip bounds.
        Rect canvasBounds = canvas.getClipBounds();
        float offsetX = params.leftAlign
                ? Math.max(0, canvasBounds.left - (dotCenterX + mBitmapOffset))
                : Math.min(0, canvasBounds.right - (dotCenterX - mBitmapOffset));
        float offsetY = Math.max(0, canvasBounds.top - (dotCenterY + mBitmapOffset));

        // We draw the dot relative to its center.
        canvas.translate(dotCenterX + offsetX, dotCenterY + offsetY);
        canvas.scale(params.scale, params.scale);
        //画背景
        mCirclePaint.setColor(Color.BLACK);
        //mBackgroundWithShadow是4.1构造方法里初始化的
        canvas.drawBitmap(mBackgroundWithShadow, mBitmapOffset, mBitmapOffset, mCirclePaint);
        //画上层圆点
        mCirclePaint.setColor(params.dotColor);//颜色是2.3里设置的
        canvas.drawCircle(0, 0, mCircleRadius, mCirclePaint);
        canvas.restore();
    }

5.GraphicsUtils.java

5.1.getShapePath

    public static Path getShapePath(int size) {
        //前景色和背景色
        AdaptiveIconDrawable drawable = new AdaptiveIconDrawable(
                new ColorDrawable(Color.BLACK), new ColorDrawable(Color.BLACK));
        //设置大小
        drawable.setBounds(0, 0, size, size);
        //获取路径
        return new Path(drawable.getIconMask());
    }

5.2.setColorAlphaBound

移除color里原本的透明度,用参数里的alpha

    public static int setColorAlphaBound(int color, int alpha) {
        if (alpha < 0) {
            alpha = 0;
        } else if (alpha > 255) {
            alpha = 255;
        }
        return (color & 0x00ffffff) | (alpha << 24);
    }

6.ItemInfoWithIcon.java

    public BitmapInfo bitmap = BitmapInfo.LOW_RES_INFO;

6.1.newIcon

    public FastBitmapDrawable newIcon(Context context) {
        return newIcon(context, 0);
    }

    public FastBitmapDrawable newIcon(Context context, @DrawableCreationFlags int creationFlags) {
        FastBitmapDrawable drawable = bitmap.newIcon(context, creationFlags);
        drawable.setIsDisabled(isDisabled());
        return drawable;
    }

6.2.BitmapInfo.java

>1.LOW_RES_INFO

    public static final Bitmap LOW_RES_ICON = Bitmap.createBitmap(1, 1, Config.ALPHA_8);
    public static final BitmapInfo LOW_RES_INFO = fromBitmap(LOW_RES_ICON);//参考补充2

>2.fromBitmap

    public static BitmapInfo fromBitmap(@NonNull Bitmap bitmap) {
        return of(bitmap, 0);
    }

    public static BitmapInfo of(@NonNull Bitmap bitmap, int color) {
        return new BitmapInfo(bitmap, color);
    }

>3.newIcon

    public FastBitmapDrawable newIcon(Context context) {
        return newIcon(context, 0);
    }

    public FastBitmapDrawable newIcon(Context context, @DrawableCreationFlags int creationFlags) {
        FastBitmapDrawable drawable;
        if (isLowRes()) {
            drawable = new PlaceHolderIconDrawable(this, context);
        } else  if ((creationFlags & FLAG_THEMED) != 0 && mMono != null) {
            drawable = ThemedIconDrawable.newDrawable(this, context);
        } else {
            drawable = new FastBitmapDrawable(this);
        }
        applyFlags(context, drawable, creationFlags);
        return drawable;
    }

7.PopupDataProvider.java

这个就是通知改变的监听回调,见8.5.1里添加的回调。

public class PopupDataProvider implements NotificationListener.NotificationsChangedListener {

7.1.onNotificationPosted

新发送的通知

    public void onNotificationPosted(PackageUserKey postedPackageUserKey,
            NotificationKeyData notificationKey) {
        DotInfo dotInfo = mPackageUserToDotInfos.get(postedPackageUserKey);
        if (dotInfo == null) {
            dotInfo = new DotInfo();
            mPackageUserToDotInfos.put(postedPackageUserKey, dotInfo);
        }
        if (dotInfo.addOrUpdateNotificationKey(notificationKey)) {
            updateNotificationDots(postedPackageUserKey::equals);
        }
    }

7.2.onNotificationRemoved

通知被删除了

    public void onNotificationRemoved(PackageUserKey removedPackageUserKey,
            NotificationKeyData notificationKey) {
        DotInfo oldDotInfo = mPackageUserToDotInfos.get(removedPackageUserKey);
        if (oldDotInfo != null && oldDotInfo.removeNotificationKey(notificationKey)) {
            if (oldDotInfo.getNotificationKeys().size() == 0) {
                mPackageUserToDotInfos.remove(removedPackageUserKey);
            }
            updateNotificationDots(removedPackageUserKey::equals);
            trimNotifications(mPackageUserToDotInfos);
        }
    }

7.3.onNotificationFullRefresh

通知数据完整刷新

    public void onNotificationFullRefresh(List<StatusBarNotification> activeNotifications) {
        if (activeNotifications == null) return;
        // This will contain the PackageUserKeys which have updated dots.
        HashMap<PackageUserKey, DotInfo> updatedDots = new HashMap<>(mPackageUserToDotInfos);
        mPackageUserToDotInfos.clear();
        for (StatusBarNotification notification : activeNotifications) {
            PackageUserKey packageUserKey = PackageUserKey.fromNotification(notification);
            DotInfo dotInfo = mPackageUserToDotInfos.get(packageUserKey);
            if (dotInfo == null) {
                dotInfo = new DotInfo();
                mPackageUserToDotInfos.put(packageUserKey, dotInfo);
            }
            dotInfo.addOrUpdateNotificationKey(NotificationKeyData.fromNotification(notification));
        }

        // Add and remove from updatedDots so it contains the PackageUserKeys of updated dots.
        for (PackageUserKey packageUserKey : mPackageUserToDotInfos.keySet()) {
            DotInfo prevDot = updatedDots.get(packageUserKey);
            DotInfo newDot = mPackageUserToDotInfos.get(packageUserKey);
            if (prevDot == null
                    || prevDot.getNotificationCount() != newDot.getNotificationCount()) {
                updatedDots.put(packageUserKey, newDot);
            } else {
                // No need to update the dot if it already existed (no visual change).
                // Note that if the dot was removed entirely, we wouldn't reach this point because
                // this loop only includes active notifications added above.
                updatedDots.remove(packageUserKey);
            }
        }

        if (!updatedDots.isEmpty()) {
            updateNotificationDots(updatedDots::containsKey);
        }
        trimNotifications(updatedDots);
    }

8.NotificationListener.java

public class NotificationListener extends NotificationListenerService {

这个服务的启动逻辑见上篇分析

8.1.服务注册

        <service
            android:name="com.android.launcher3.notification.NotificationListener"
            android:label="@string/notification_dots_service_title"
            android:exported="true"
            android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE">
            <intent-filter>
                <action android:name="android.service.notification.NotificationListenerService" />
            </intent-filter>
        </service>

8.2.onNotificationXXX

实现通知的改变移除等处理

    public void onNotificationPosted(final StatusBarNotification sbn) {
        if (sbn != null) {
        //见补充1
            mWorkerHandler.obtainMessage(MSG_NOTIFICATION_POSTED, sbn).sendToTarget();
        }
    }

    public void onNotificationRemoved(final StatusBarNotification sbn) {
        if (sbn != null) {
            mWorkerHandler.obtainMessage(MSG_NOTIFICATION_REMOVED, sbn).sendToTarget();
        }
    }

>1.handleWorkerMessage

    private boolean handleWorkerMessage(Message message) {
        switch (message.what) {
            case MSG_NOTIFICATION_POSTED: {
                StatusBarNotification sbn = (StatusBarNotification) message.obj;
                //处理下数据再交给8.3处理
                mUiHandler.obtainMessage(notificationIsValidForUI(sbn)
                                ? MSG_NOTIFICATION_POSTED : MSG_NOTIFICATION_REMOVED,
                        toKeyPair(sbn)).sendToTarget();
                return true;
            }
   //其他case省略。。      
            case MSG_NOTIFICATION_FULL_REFRESH://添加listener的时候或者断开连接的时候调用
                List<StatusBarNotification> activeNotifications = null;
                if (sIsConnected) {
                //获取当前说有的通知
                    activeNotifications = Arrays.stream(getActiveNotificationsSafely(null))
                            .filter(this::notificationIsValidForUI)
                            .collect(Collectors.toList());
                } else {
                    activeNotifications = new ArrayList<>();
                }

                mUiHandler.obtainMessage(message.what, activeNotifications).sendToTarget();
                return true;   

8.3.handleUiMessage

可以看到,就是交给listener处理了,回调见8.5添加

    private boolean handleUiMessage(Message message) {
        switch (message.what) {
            case MSG_NOTIFICATION_POSTED:
                if (sNotificationsChangedListeners.size() > 0) {
                    Pair<PackageUserKey, NotificationKeyData> msg = (Pair) message.obj;
                    for (NotificationsChangedListener listener : sNotificationsChangedListeners) {
                        listener.onNotificationPosted(msg.first, msg.second);
                    }
                }
                break;
            case MSG_NOTIFICATION_REMOVED:
                if (sNotificationsChangedListeners.size() > 0) {
                    Pair<PackageUserKey, NotificationKeyData> msg = (Pair) message.obj;
                    for (NotificationsChangedListener listener : sNotificationsChangedListeners) {
                        listener.onNotificationRemoved(msg.first, msg.second);
                    }
                }
                break;
            case MSG_NOTIFICATION_FULL_REFRESH:
                if (sNotificationsChangedListeners.size() > 0) {
                    for (NotificationsChangedListener listener : sNotificationsChangedListeners) {
                        listener.onNotificationFullRefresh(
                                (List<StatusBarNotification>) message.obj);
                    }
                }
                break;
        }
        return true;
    }

8.4.onListenerConnected

监听已连接

    public void onListenerConnected() {
        super.onListenerConnected();
        sIsConnected = true;

        // Register an observer to rebind the notification listener when dots are re-enabled.
        mSettingsCache = SettingsCache.INSTANCE.get(this);
        mNotificationSettingsChangedListener = this::onNotificationSettingsChanged;
        //监听设置里是否允许通知未读徽章
        mSettingsCache.register(NOTIFICATION_BADGING_URI,
                mNotificationSettingsChangedListener);
        //见补充1
        onNotificationSettingsChanged(mSettingsCache.getValue(NOTIFICATION_BADGING_URI));

        onNotificationFullRefresh();
    }

>1.onNotificationSettingsChanged

如果不允许徽章并且已经连接的情况,那么断开连接。

    private void onNotificationSettingsChanged(boolean areNotificationDotsEnabled) {
        if (!areNotificationDotsEnabled && sIsConnected) {
            requestUnbind();
        }
    }

>2.NOTIFICATION_BADGING_URI

    //通知未读提示是否允许
    public static final Uri NOTIFICATION_BADGING_URI =
            Settings.Secure.getUriFor("notification_badging");

>3.LauncherAppState

监听改变,重新请求bind的

        SettingsCache.OnChangeListener notificationLister = this::onNotificationSettingsChanged;
        settingsCache.register(NOTIFICATION_BADGING_URI, notificationLister);
    private void onNotificationSettingsChanged(boolean areNotificationDotsEnabled) {
        if (areNotificationDotsEnabled) {
        //重新绑定
            NotificationListener.requestRebind(new ComponentName(
                    mContext, NotificationListener.class));
        }
    }

8.5.addNotificationsChangedListener

调用这个方法添加listener的地方有两处,见补充1和2

    public static void addNotificationsChangedListener(NotificationsChangedListener listener) {
        if (listener == null) {
            return;
        }
        sNotificationsChangedListeners.add(listener);

        NotificationListener notificationListener = getInstanceIfConnected();
        if (notificationListener != null) {
            notificationListener.onNotificationFullRefresh();
        } else {
            MODEL_EXECUTOR.submit(() -> MAIN_EXECUTOR.submit(() ->
                            listener.onNotificationFullRefresh(Collections.emptyList())));
        }
    }

>1.Launcher.java

    //见小节9
        mPopupDataProvider = new PopupDataProvider(this::updateNotificationDots);
        
        // Set the notification listener and fetch updated notifications when we resume
        NotificationListener.addNotificationsChangedListener(mPopupDataProvider);
    private void updateNotificationDots(Predicate<PackageUserKey> updatedDots) {
        mWorkspace.updateNotificationDots(updatedDots);
        mAppsView.getAppsStore().updateNotificationDots(updatedDots);
    }

>2.TaskbarPopupController.java

    public TaskbarPopupController(TaskbarActivityContext context) {
        mContext = context;
        //实例化对象
        mPopupDataProvider = new PopupDataProvider(this::updateNotificationDots);
    }
    public void init(TaskbarControllers controllers) {
        mControllers = controllers;
        //添加监听
        NotificationListener.addNotificationsChangedListener(mPopupDataProvider);
    }

看起来是更新的展开的folder里的图标未读消息状态的

    private void updateNotificationDots(Predicate<PackageUserKey> updatedDots) {
        final PackageUserKey packageUserKey = new PackageUserKey(null, null);
        Predicate<ItemInfo> matcher = info -> !packageUserKey.updateFromItemInfo(info)
                || updatedDots.test(packageUserKey);

        LauncherBindableItemsContainer.ItemOperator op = (info, v) -> {
            if (info instanceof WorkspaceItemInfo && v instanceof BubbleTextView) {
                if (matcher.test(info)) {
                    ((BubbleTextView) v).applyDotState(info, true /* animate */);
                }
            } else if (info instanceof FolderInfo && v instanceof FolderIcon) {
                FolderInfo fi = (FolderInfo) info;
                if (fi.contents.stream().anyMatch(matcher)) {
                    FolderDotInfo folderDotInfo = new FolderDotInfo();
                    for (WorkspaceItemInfo si : fi.contents) {
                        folderDotInfo.addDotInfo(mPopupDataProvider.getDotInfoForItem(si));
                    }
                    ((FolderIcon) v).setDotInfo(folderDotInfo);
                }
            }

            // process all the shortcuts
            return false;
        };

        mControllers.taskbarViewController.mapOverItems(op);
        Folder folder = Folder.getOpen(mContext);
        if (folder != null) {
            folder.iterateOverItems(op);
        }
    }

9.PopupDataProvider

PopupDataProvider实现了通知监听的接口

public class PopupDataProvider implements NotificationListener.NotificationsChangedListener {

9.1.onNotificationFullRefresh

数据来源见8.2.1以及8.3,完整刷新所有通知数据

    public void onNotificationFullRefresh(List<StatusBarNotification> activeNotifications) {
        if (activeNotifications == null) return;
        //保存旧数据
        HashMap<PackageUserKey, DotInfo> updatedDots = new HashMap<>(mPackageUserToDotInfos);
        //清空
        mPackageUserToDotInfos.clear();
        for (StatusBarNotification notification : activeNotifications) {
            PackageUserKey packageUserKey = PackageUserKey.fromNotification(notification);
            DotInfo dotInfo = mPackageUserToDotInfos.get(packageUserKey);
            if (dotInfo == null) {
                dotInfo = new DotInfo();
                //新加
                mPackageUserToDotInfos.put(packageUserKey, dotInfo);
            }
           //数据转化并加入集合或者更新集合里的数据 dotInfo.addOrUpdateNotificationKey(NotificationKeyData.fromNotification(notification));
        }

        // Add and remove from updatedDots so it contains the PackageUserKeys of updated dots.
        for (PackageUserKey packageUserKey : mPackageUserToDotInfos.keySet()) {
            //新旧数据
            DotInfo prevDot = updatedDots.get(packageUserKey);
            DotInfo newDot = mPackageUserToDotInfos.get(packageUserKey);
            //旧dot数据为空或者新旧通知数不一样
            if (prevDot == null
                    || prevDot.getNotificationCount() != newDot.getNotificationCount()) {
                //更新
                updatedDots.put(packageUserKey, newDot);
            } else {
                //dot数据没有变化,则移除
                updatedDots.remove(packageUserKey);
            }
        }

        if (!updatedDots.isEmpty()) {
        //需要更新的
            updateNotificationDots(updatedDots::containsKey);
        }
        trimNotifications(updatedDots);
    }

9.2.onNotificationPosted

    public void onNotificationPosted(PackageUserKey postedPackageUserKey,
            NotificationKeyData notificationKey) {
        DotInfo dotInfo = mPackageUserToDotInfos.get(postedPackageUserKey);
        if (dotInfo == null) {
            dotInfo = new DotInfo();
            mPackageUserToDotInfos.put(postedPackageUserKey, dotInfo);
        }
        //添加或者更新成功
        if (dotInfo.addOrUpdateNotificationKey(notificationKey)) {
            //更新
            updateNotificationDots(postedPackageUserKey::equals);
        }
    }

9.3.onNotificationRemoved

    public void onNotificationRemoved(PackageUserKey removedPackageUserKey,
            NotificationKeyData notificationKey) {
        DotInfo oldDotInfo = mPackageUserToDotInfos.get(removedPackageUserKey);
        if (oldDotInfo != null && oldDotInfo.removeNotificationKey(notificationKey)) {
            if (oldDotInfo.getNotificationKeys().size() == 0) {
                mPackageUserToDotInfos.remove(removedPackageUserKey);
            }
            updateNotificationDots(removedPackageUserKey::equals);
            trimNotifications(mPackageUserToDotInfos);
        }
    }