android13#launcher3#分屏相关

685 阅读22分钟

1.简介

历史原因,我们用的是android13最早的一版代码,里边各种坑。有时候发现最新的代码是好的,我们的就有问题,想对比下代码,差别太大了,无从下手,只能理清老代码逻辑找原因

2..分屏异常

2.1.描述:

桌面长按一个快捷方式,选择split left或者right都行,完事再随便选个历史应用,会发现这个快捷方式对应的分屏黑一下就没了,分屏失败

2.2.日志

  • 我的快捷方式声明的action指向的是一个MyJavaActivity的页面,没看到对应的信息,只看到demo的启动页信息。
  • 第一行日志提示Intent不匹配,然后看下打印的intent,这个好像是快捷方式默认的intent

>1.分屏日志

PackageManager W  Intent does not match component's intent filter: Intent { act=android.intent.action.MAIN cat=[com.android.launcher3.DEEP_SHORTCUT] flg=0x10200000 pkg=com.example.myapp2023 cmp=com.example.myapp2023/.test.MyActivity (has extras) }
PackageManager W  Access blocked: ComponentInfo{com.example.myapp2023/com.example.myapp2023.test.MyActivity}
ActivitivityManager I  START u0 {act=android.intent.action.MAIN cat=[com.android.launcher3.DEEP_SHORTCUT] flg=0x10200000 pkg=com.example.myapp2023 cmp=com.example.myapp2023/.test.MyActivity (has extras)} from uid 10152

>2.正常日志

这个是直接点击快捷方式的日志

START u0 {act=com.test.myjava flg=0x1000c000 cmp=com.example.myapp2023/.java.MyJavaActivity bnds=[672,169][792,275]} from uid 10189

2.3.思路

>1.直接点击快捷方式

  • 见小节3启动流程,最终启动逻辑见3.7分析,可以看到是传递了包名以及shortcutId处理的

>2.分屏点击的快捷方式

  • 流程见小节4,小节4.10.4里是启动分屏的逻辑
  • 可以看到,区分是否有shortcut,而旧代码是没有这块逻辑的,直接传的Intent,而快捷方式的intent是需要特殊处理的,不处理的话这个intent查找不到对应的activity也就无法启动。
  • 最后再看日志相关的代码

3.ItemClickHandler

桌面图标的点击事件处理类

3.1.onClick

    private static void onClick(View v) {
        if (v.getWindowToken() == null) return;

        Launcher launcher = Launcher.getLauncher(v.getContext());
        //桌面状态切换中,不做处理,补充1
        if (!launcher.getWorkspace().isFinishedSwitchingState()) return;

        Object tag = v.getTag();
        //根据数据类型做不同的处理
        //桌面的应用图标,或者快捷方式走这里
        if (tag instanceof WorkspaceItemInfo) {
            onClickAppShortcut(v, (WorkspaceItemInfo) tag, launcher);//见3.2
        } else if (tag instanceof FolderInfo) {
            if (v instanceof FolderIcon) {
                onClickFolderIcon(v);//补充2,文件夹展开
            }
        } else if (tag instanceof AppInfo) {
        //allapps页面的走这里
            startAppShortcutOrInfoActivity(v, (AppInfo) tag, launcher);//见3.3
        } else if (tag instanceof LauncherAppWidgetInfo) {
            if (v instanceof PendingAppWidgetHostView) {//见3.4
                onClickPendingWidget((PendingAppWidgetHostView) v, launcher);
            }
        } else if (tag instanceof ItemClickProxy) {//这个是自定义的点击事件
            ((ItemClickProxy) tag).onItemClicked(v);
        }
    }

>1.isFinishedSwitchingState

是否完成了状态切换,也就是非状态切换中或者切换进度大于50%

    public boolean isFinishedSwitchingState() {
        return !mIsSwitchingState
                || (mTransitionProgress > FINISHED_SWITCHING_STATE_TRANSITION_PROGRESS);
    }

>2.onClickFolderIcon

    private static void onClickFolderIcon(View v) {
        Folder folder = ((FolderIcon) v).getFolder();
        if (!folder.isOpen() && !folder.isDestroyed()) {
            // 打开文件夹
            folder.animateOpen();
        }
    }

3.2.onClickAppShortcut

    public static void onClickAppShortcut(View v, WorkspaceItemInfo shortcut, Launcher launcher) {
        if (shortcut.isDisabled() && handleDisabledItemClicked(shortcut, launcher)) {
        //参考3.5
            return;
        }

        // Check for abandoned promise
        if ((v instanceof BubbleTextView) && shortcut.hasPromiseIconUi()) {
            String packageName = shortcut.getIntent().getComponent() != null
                    ? shortcut.getIntent().getComponent().getPackageName()
                    : shortcut.getIntent().getPackage();
            if (!TextUtils.isEmpty(packageName)) {
            //补充2,跳转到应用市场
                onClickPendingAppItem(
                        v,
                        launcher,
                        packageName,
                        (shortcut.runtimeStatusFlags
                                & ItemInfoWithIcon.FLAG_INSTALL_SESSION_ACTIVE) != 0);
                return;
            }
        }

        // Start activities,见3.6
        startAppShortcutOrInfoActivity(v, shortcut, launcher);
    }

>1.hasPromiseIconUi

app暂时不可用,并且不支持web ui

    public boolean hasPromiseIconUi() {
        return isPromise() && !hasStatusFlag(FLAG_SUPPORTS_WEB_UI);
    }
//可以简单理解为app暂时还不可用,比如恢复中,自动安装中。
    public final boolean isPromise() {
        return hasStatusFlag(FLAG_RESTORED_ICON | FLAG_AUTOINSTALL_ICON);
    }
    

>2.onClickPendingAppItem

    private static void onClickPendingAppItem(View v, Launcher launcher, String packageName,
            boolean downloadStarted) {
        if (downloadStarted) {
            //已经开始下载了,跳转到市场应用
            startMarketIntentForPackage(v, launcher, packageName);
            return;
        }
        UserHandle user = v.getTag() instanceof ItemInfo
                ? ((ItemInfo) v.getTag()).user : Process.myUserHandle();
            //过期提示对话框
        new AlertDialog.Builder(launcher)
                .setTitle(R.string.abandoned_promises_title)
                .setMessage(R.string.abandoned_promise_explanation)
                .setPositiveButton(R.string.abandoned_search,
                        (d, i) -> startMarketIntentForPackage(v, launcher, packageName))
                .setNeutralButton(R.string.abandoned_clean_this,
                        (d, i) -> launcher.getWorkspace()
                                .persistRemoveItemsByMatcher(ItemInfoMatcher.ofPackages(
                                        Collections.singleton(packageName), user),
                                        "user explicitly removes the promise app icon"))
                .create().show();
    }

>3.startMarketIntentForPackage

根据应用包名跳转到应用市场页面

    private static void startMarketIntentForPackage(View v, Launcher launcher, String packageName) {
        ItemInfo item = (ItemInfo) v.getTag();
        if (Utilities.ATLEAST_Q) {
            SessionInfo sessionInfo = InstallSessionHelper.INSTANCE.get(launcher)
                    .getActiveSessionInfo(item.user, packageName);
            if (sessionInfo != null) {
                LauncherApps launcherApps = launcher.getSystemService(LauncherApps.class);
                try {
                    launcherApps.startPackageInstallerSessionDetailsActivity(sessionInfo, null,
                            launcher.getActivityLaunchOptions(v, item).toBundle());
                    return;
                } 
            }
        }

        // Fallback to using custom market intent.
        Intent intent = new PackageManagerHelper(launcher).getMarketIntent(packageName);
        launcher.startActivitySafely(v, intent, item);
    }

>4.startAppShortcutOrInfoActivity

    private static void startAppShortcutOrInfoActivity(View v, ItemInfo item, Launcher launcher) {
        Intent intent;
        if (item instanceof ItemInfoWithIcon
                && (((ItemInfoWithIcon) item).runtimeStatusFlags
                & ItemInfoWithIcon.FLAG_INSTALL_SESSION_ACTIVE) != 0) {
                //安装进行中。
            ItemInfoWithIcon appInfo = (ItemInfoWithIcon) item;
            //应用市场的intent
            intent = new PackageManagerHelper(launcher)
                    .getMarketIntent(appInfo.getTargetComponent().getPackageName());
        } else {
            intent = item.getIntent();
        }
        if (intent == null) {
            throw new IllegalArgumentException("Input must have a valid intent");
        }
        if (item instanceof WorkspaceItemInfo) {
            WorkspaceItemInfo si = (WorkspaceItemInfo) item;
            //支持web ui,并且是ACTION_VIEW
            if (si.hasStatusFlag(WorkspaceItemInfo.FLAG_SUPPORTS_WEB_UI)
                    && Intent.ACTION_VIEW.equals(intent.getAction())) {
                //copy一份,设置package为空
                intent = new Intent(intent);
                intent.setPackage(null);
            }
            if ((si.options & WorkspaceItemInfo.FLAG_START_FOR_RESULT) != 0) {
            //需要返回结果的
                launcher.startActivityForResult(item.getIntent(), 0);
                InstanceId instanceId = new InstanceIdSequence().newInstanceId();
                return;
            }
        }
        if (v != null && launcher.supportsAdaptiveIconAnimation(v)) {
            // Preload the icon to reduce latency b/w swapping the floating view with the original.
            FloatingIconView.fetchIcon(launcher, v, item, true /* isOpening */);
        }
        launcher.startActivitySafely(v, intent, item);
    }

3.3.startAppShortcutOrInfoActivity

    private static void startAppShortcutOrInfoActivity(View v, ItemInfo item, Launcher launcher) {
        TestLogging.recordEvent(
                TestProtocol.SEQUENCE_MAIN, "start: startAppShortcutOrInfoActivity");
        Intent intent;
        if (item instanceof ItemInfoWithIcon
                && (((ItemInfoWithIcon) item).runtimeStatusFlags
                & ItemInfoWithIcon.FLAG_INSTALL_SESSION_ACTIVE) != 0) {
            ItemInfoWithIcon appInfo = (ItemInfoWithIcon) item;
            intent = new PackageManagerHelper(launcher)
                    .getMarketIntent(appInfo.getTargetComponent().getPackageName());
        } else {
            intent = item.getIntent();
        }
        if (intent == null) {
            throw new IllegalArgumentException("Input must have a valid intent");
        }
        if (item instanceof WorkspaceItemInfo) {
            WorkspaceItemInfo si = (WorkspaceItemInfo) item;
            if (si.hasStatusFlag(WorkspaceItemInfo.FLAG_SUPPORTS_WEB_UI)
                    && Intent.ACTION_VIEW.equals(intent.getAction())) {
                // make a copy of the intent that has the package set to null
                // we do this because the platform sometimes disables instant
                // apps temporarily (triggered by the user) and fallbacks to the
                // web ui. This only works though if the package isn't set
                intent = new Intent(intent);
                intent.setPackage(null);
            }
            if ((si.options & WorkspaceItemInfo.FLAG_START_FOR_RESULT) != 0) {
                launcher.startActivityForResult(item.getIntent(), 0);
                InstanceId instanceId = new InstanceIdSequence().newInstanceId();
                launcher.logAppLaunch(launcher.getStatsLogManager(), item, instanceId);
                return;
            }
        }
        if (v != null && launcher.supportsAdaptiveIconAnimation(v)) {
            // Preload the icon to reduce latency b/w swapping the floating view with the original.
            FloatingIconView.fetchIcon(launcher, v, item, true /* isOpening */);
        }
        launcher.startActivitySafely(v, intent, item);
    }

3.4.onClickPendingWidget

    private static void onClickPendingWidget(PendingAppWidgetHostView v, Launcher launcher) {
        if (launcher.getPackageManager().isSafeMode()) {
            Toast.makeText(launcher, R.string.safemode_widget_error, Toast.LENGTH_SHORT).show();
            return;
        }

        final LauncherAppWidgetInfo info = (LauncherAppWidgetInfo) v.getTag();
        if (v.isReadyForClickSetup()) {
            LauncherAppWidgetProviderInfo appWidgetInfo = new WidgetManagerHelper(launcher)
                    .findProvider(info.providerName, info.user);
            if (appWidgetInfo == null) {
                return;
            }
            WidgetAddFlowHandler addFlowHandler = new WidgetAddFlowHandler(appWidgetInfo);

            if (info.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_ID_NOT_VALID)) {
                if (!info.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_ID_ALLOCATED)) {
                    // This should not happen, as we make sure that an Id is allocated during bind.
                    return;
                }
                addFlowHandler.startBindFlow(launcher, info.appWidgetId, info,
                        REQUEST_BIND_PENDING_APPWIDGET);
            } else {
                addFlowHandler.startConfigActivity(launcher, info, REQUEST_RECONFIGURE_APPWIDGET);
            }
        } else {
            final String packageName = info.providerName.getPackageName();
            onClickPendingAppItem(v, launcher, packageName, info.installProgress >= 0);
        }
    }

3.5.handleDisabledItemClicked

判断item是否可以点击

    public static boolean handleDisabledItemClicked(WorkspaceItemInfo shortcut, Context context) {
    //解析flag
        final int disabledFlags = shortcut.runtimeStatusFlags
                & WorkspaceItemInfo.FLAG_DISABLED_MASK;
        //见补充1,处理app版本太低的情况,为true表示已弹框提示,为false表示未处理
        if (maybeCreateAlertDialogForShortcut(shortcut, context)) {
            return true;
        }

        if ((disabledFlags
                & ~FLAG_DISABLED_SUSPENDED
                & ~FLAG_DISABLED_QUIET_USER) == 0) {
            // 如果应用程序只是因为上述标志而被禁用,那么继续启动活动
            // Framework 将告诉用户应用程序暂停的原因。
            return false;
        } else {
            if (!TextUtils.isEmpty(shortcut.disabledMessage)) {
                // 快捷方式有声明不可用的提示文字,那就直接用
                Toast.makeText(context, shortcut.disabledMessage, Toast.LENGTH_SHORT).show();
                return true;
            }
            //那根据flag显示不同的提示文字,默认文字,“应用不可用”
            int error = R.string.activity_not_available;
            if ((shortcut.runtimeStatusFlags & FLAG_DISABLED_SAFEMODE) != 0) {
            //安全模式下无法使用已下载的应用
                error = R.string.safemode_shortcut_error;
            } else if ((shortcut.runtimeStatusFlags & FLAG_DISABLED_BY_PUBLISHER) != 0
                    || (shortcut.runtimeStatusFlags & FLAG_DISABLED_LOCKED_USER) != 0) {
            //被禁用或者用户还没unlock,提示 快捷方式不可用
                error = R.string.shortcut_not_available;
            }
            Toast.makeText(context, error, Toast.LENGTH_SHORT).show();
            return true;
        }
    }

>1.maybeCreateAlertDialogForShortcut

返回true表示已处理

    private static boolean maybeCreateAlertDialogForShortcut(final WorkspaceItemInfo shortcut,
            Context context) {
        try {
            final Launcher launcher = Launcher.getLauncher(context);
            //快捷方式,它的应用程序版本太低
            if (shortcut.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT
                    && shortcut.isDisabledVersionLower()) {
                    //应用市场intent
                final Intent marketIntent = shortcut.getMarketIntent(context);
                if (marketIntent == null) {
                    return false;
                }
//弹框提示用户更新或者删除图标
                new AlertDialog.Builder(context)
                        .setTitle(R.string.dialog_update_title)
                        .setMessage(R.string.dialog_update_message)
                        .setPositiveButton(R.string.dialog_update, (d, i) -> {
                            // 跳转到应用市场更新应用
                            context.startActivity(marketIntent);
                        })
                        .setNeutralButton(R.string.dialog_remove, (d, i) -> {
                            // 移除图标
                            launcher.getWorkspace().persistRemoveItemsByMatcher(ItemInfoMatcher
                                    .ofShortcutKeys(Collections.singleton(ShortcutKey
                                            .fromItemInfo(shortcut))),
                                    "user explicitly removes disabled shortcut");
                        })
                        .create()
                        .show();
                return true;
            }
        }

        return false;
    }

3.6.startActivitySafely

>1.QuickstepLauncher.java

    public boolean startActivitySafely(View v, Intent intent, ItemInfo item) {
        // 只有taskbar controller为空的时候才暂停
        mHotseatPredictionController.setPauseUIUpdate(getTaskbarUIController() == null);
        //补充2
        boolean started = super.startActivitySafely(v, intent, item);
        if (getTaskbarUIController() == null && !started) {
            mHotseatPredictionController.setPauseUIUpdate(false);
        }
        return started;
    }

>2.父类Launcher.java

    public boolean startActivitySafely(View v, Intent intent, ItemInfo item) {
        if (!hasBeenResumed()) {
            //launcher状态是没resume的时候,在resume以后再次调用这个方法
            addOnResumeCallback(() -> startActivitySafely(v, intent, item));
            if (mOnDeferredActivityLaunchCallback != null) {
                mOnDeferredActivityLaunchCallback.run();
                mOnDeferredActivityLaunchCallback = null;
            }
            return true;
        }
    //补充3
        boolean success = super.startActivitySafely(v, intent, item);
        if (success && v instanceof BubbleTextView) {
            //保持press状态
            BubbleTextView btv = (BubbleTextView) v;
            btv.setStayPressed(true);
            //桌面resume的时候取消press
            addOnResumeCallback(() -> btv.setStayPressed(false));
        }
        return success;
    }

>3.父类ActivityContext.java

    default boolean startActivitySafely(
            View v, Intent intent, @Nullable ItemInfo item) {

        Context context = (Context) this;
        //安全模式下,非系统应用
        if (isAppBlockedForSafeMode() && !PackageManagerHelper.isSystemApp(context, intent)) {
            Toast.makeText(context, R.string.safemode_shortcut_error, Toast.LENGTH_SHORT).show();
            return false;
        }

        Bundle optsBundle = (v != null) ? getActivityLaunchOptions(v, item).toBundle() : null;
        UserHandle user = item == null ? null : item.user;

        // Prepare intent
        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        if (v != null) {
            intent.setSourceBounds(Utilities.getViewBounds(v));
        }
        try {
            boolean isShortcut = (item instanceof WorkspaceItemInfo)
                    && (item.itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT
                    || item.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT)
                    && !((WorkspaceItemInfo) item).isPromise();
            if (isShortcut) {
                //补充4,快捷方式需要特殊处理
                startShortcutIntentSafely(intent, optsBundle, item);
            } else if (user == null || user.equals(Process.myUserHandle())) {
                // Could be launching some bookkeeping activity
                context.startActivity(intent, optsBundle);
            } else {
                context.getSystemService(LauncherApps.class).startMainActivity(
                        intent.getComponent(), user, intent.getSourceBounds(), optsBundle);
            }
            return true;
        } catch (NullPointerException | ActivityNotFoundException | SecurityException e) {
            Toast.makeText(context, R.string.activity_not_found, Toast.LENGTH_SHORT).show();
        }
        return false;
    }

>4.startShortcutIntentSafely

    default void startShortcutIntentSafely(Intent intent, Bundle optsBundle, ItemInfo info) {
        try {
            try {
                if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT) {
                    String id = ((WorkspaceItemInfo) info).getDeepShortcutId();
                    String packageName = intent.getPackage();
                    //补充5
                    startShortcut(packageName, id, intent.getSourceBounds(), optsBundle, info.user);
                } else {
                    // Could be launching some bookkeeping activity
                    ((Context) this).startActivity(intent, optsBundle);
                }
            } 
        }
    }

>5.startShortcut

    default void startShortcut(String packageName, String id, Rect sourceBounds,
            Bundle startActivityOptions, UserHandle user) {
        if (GO_DISABLE_WIDGETS) {//重写了,为false
            return;
        }
        try {
            ((Context) this).getSystemService(LauncherApps.class).startShortcut(packageName, id,
                    sourceBounds, startActivityOptions, user);
        } 
    }

3.7.总结

  • 快捷方式相关的(ITEM_TYPE_DEEP_SHORTCUT),流程见3.1》3.2》3.6.4》3.6.5
  • 参考3.6.5,快捷方式点击后,最终调用的LauncherApps的startShortcut方法进行操作的,具体下篇分析。

4.ItemLongClickListener

  • 桌面图标的长按事件处理类,分workspace和allApps
  • 两个逻辑差不多,这里就研究一种即可,只看弹框相关的
    public static final OnLongClickListener INSTANCE_WORKSPACE =
            ItemLongClickListener::onWorkspaceItemLongClick;

    public static final OnLongClickListener INSTANCE_ALL_APPS =
            ItemLongClickListener::onAllAppsItemLongClick;

4.1.onWorkspaceItemLongClick

    private static boolean onWorkspaceItemLongClick(View v) {
        Launcher launcher = Launcher.getLauncher(v.getContext());
        if (!canStartDrag(launcher)) return false;
        if (!launcher.isInState(NORMAL) && !launcher.isInState(OVERVIEW)) return false;
        if (!(v.getTag() instanceof ItemInfo)) return false;

        launcher.setWaitingForResult(null);
        beginDrag(v, launcher, (ItemInfo) v.getTag(), launcher.getDefaultWorkspaceDragOptions());
        return true;
    }

    public static void beginDrag(View v, Launcher launcher, ItemInfo info,
            DragOptions dragOptions) {
        if (info.container >= 0) {
            Folder folder = Folder.getOpen(launcher);
            if (folder != null) {
                if (!folder.getIconsInReadingOrder().contains(v)) {
                    folder.close(true);
                } else {
                    folder.startDrag(v, dragOptions);
                    return;
                }
            }
        }

        CellLayout.CellInfo longClickCellInfo = new CellLayout.CellInfo(v, info);
        //见4.2
        launcher.getWorkspace().startDrag(longClickCellInfo, dragOptions);
    }

4.2.startDrag

    public void startDrag(CellLayout.CellInfo cellInfo, DragOptions options) {
        View child = cellInfo.cell;

        mDragInfo = cellInfo;
        child.setVisibility(INVISIBLE);
//..

        beginDragShared(child, this, options);
    }

    public DragView beginDragShared(View child, DraggableView draggableView, DragSource source,
            ItemInfo dragObject, DragPreviewProvider previewProvider, DragOptions dragOptions) {
        //..

        if (child instanceof BubbleTextView) {
            BubbleTextView btv = (BubbleTextView) child;
            //非拖动中
            if (!dragOptions.isAccessibleDrag) {
            //长按操作,见补充1
                dragOptions.preDragCondition = btv.startLongPressAction();
            }
            if (btv.isDisplaySearchResult()) {
                dragOptions.preDragEndScale = (float) mAllAppsIconSize / btv.getIconSize();
            }
        }

        final DragView dv;
        if (contentView instanceof View) {
            if (contentView instanceof LauncherAppWidgetHostView) {
                mDragController.addDragListener(new AppWidgetHostViewDragListener(mLauncher));
            }
            dv = mDragController.startDrag(
                    contentView,
                    draggableView,
                    dragLayerX,
                    dragLayerY,
                    source,
                    dragObject,
                    dragVisualizeOffset,
                    dragRect,
                    scale * iconScale,
                    scale,
                    dragOptions);
        } else {
            dv = mDragController.startDrag(
                    drawable,
                    draggableView,
                    dragLayerX,
                    dragLayerY,
                    source,
                    dragObject,
                    dragVisualizeOffset,
                    dragRect,
                    scale * iconScale,
                    scale,
                    dragOptions);
        }
        return dv;
    }

>1.startLongPressAction

//BubbleTextView.java
    public PreDragCondition startLongPressAction() {
    //见4.3
        PopupContainerWithArrow popup = PopupContainerWithArrow.showForIcon(this);
        return popup != null ? popup.createPreDragCondition(true) : null;
    }

4.3.PopupContainerWithArrow.java

>1.showForIcon

    public static PopupContainerWithArrow<Launcher> showForIcon(BubbleTextView icon) {
        Launcher launcher = Launcher.getLauncher(icon.getContext());
        if (getOpen(launcher) != null) {
            // There is already an items container open, so don't open this one.
            icon.clearFocus();
            return null;
        }
        ItemInfo item = (ItemInfo) icon.getTag();
        if (!ShortcutUtil.supportsShortcuts(item)) {
            return null;
        }
//布局见补充2
        final PopupContainerWithArrow<Launcher> container =
                (PopupContainerWithArrow) launcher.getLayoutInflater().inflate(
                        R.layout.popup_container, launcher.getDragLayer(), false);
        container.configureForLauncher(launcher);

        PopupDataProvider popupDataProvider = launcher.getPopupDataProvider();
        //见4.4.1
        container.populateAndShow(icon,
                popupDataProvider.getShortcutCountForItem(item),//见4.5.1
                popupDataProvider.getNotificationKeysForItem(item),//见4.5.2
                launcher.getSupportedShortcuts()//见4.6.1,返回的是Factory集合
                    //把Factory转化为shortcut,具体参考对应的factory,为空不支持
                        .map(s -> s.getShortcut(launcher, item, icon))
                        .filter(Objects::nonNull)//过滤掉为空的,也就是不支持对应的快捷方式
                        .collect(Collectors.toList()));
                        //根据包名查找支持的小部件
        launcher.refreshAndBindWidgetsForPackageUser(PackageUserKey.fromItemInfo(item));
        container.requestFocus();
        return container;
    }

>2.popup_container.xml

里边有2个child,一个是快捷方式容器,一个是通知容器

<!--自定义的悬浮容器,线性布局-->
<com.android.launcher3.popup.PopupContainerWithArrow
    android:id="@+id/popup_container"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:clipToPadding="false"
    android:clipChildren="false"
    android:orientation="vertical">

    <LinearLayout
        android:id="@+id/deep_shortcuts_container"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:tag="@string/popup_container_iterate_children"
        android:elevation="@dimen/deep_shortcuts_elevation"
        android:orientation="vertical"/>

    <com.android.launcher3.notification.NotificationContainer
        android:id="@+id/notification_container"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:visibility="gone"/>
</com.android.launcher3.popup.PopupContainerWithArrow>

4.4.PopupContainerWithArrow.java

自定义的线程布局

public class PopupContainerWithArrow<T extends Context & ActivityContext>
        extends ArrowPopup<T> implements DragSource, DragController.DragListener {
public abstract class ArrowPopup<T extends Context & ActivityContext>
        extends AbstractFloatingView {
public abstract class AbstractFloatingView extends LinearLayout implements TouchController {

>1.populateAndShow

  • shortcutCount:这个是指app清单文件里配置的shortcut或者动态添加的那种。
    public void populateAndShow(final BubbleTextView originalIcon, int shortcutCount,
            final List<NotificationKeyData> notificationKeys, List<SystemShortcut> shortcuts) {
        mNumNotifications = notificationKeys.size();
        mOriginalIcon = originalIcon;

        boolean hasDeepShortcuts = shortcutCount > 0;
        mContainerWidth = getResources().getDimensionPixelSize(R.dimen.bg_popup_item_width);

        //有通知的话,给通知容器里添加数据
        if (mNumNotifications > 0) {
            // Add notification entries
            if (mNotificationContainer == null) {
                mNotificationContainer = findViewById(R.id.notification_container);
                mNotificationContainer.setVisibility(VISIBLE);
                mNotificationContainer.setPopupView(this);
            } else {
                mNotificationContainer.setVisibility(GONE);
            }
            updateNotificationHeader();
        }
        int viewsToFlip = getChildCount();
        mSystemShortcutContainer = this;
        if (mDeepShortcutContainer == null) {
            mDeepShortcutContainer = findViewById(R.id.deep_shortcuts_container);
        }
        if (hasDeepShortcuts) {
            //非小部件快捷方式,除了4.8.1那种的都算
            List<SystemShortcut> systemShortcuts = shortcuts
                    .stream()
                    .filter(shortcut -> !(shortcut instanceof SystemShortcut.Widgets))
                    .collect(Collectors.toList());
          //小部件快捷方式,见4.8.1        
            Optional<SystemShortcut.Widgets> widgetShortcutOpt = shortcuts
                    .stream()
                    .filter(shortcut -> shortcut instanceof SystemShortcut.Widgets)
                    .map(SystemShortcut.Widgets.class::cast)
                    .findFirst();

            mContainerWidth = Math.max(mContainerWidth,
                    systemShortcuts.size() * getResources()
                            .getDimensionPixelSize(R.dimen.system_shortcut_header_icon_touch_size)
            );

            mDeepShortcutContainer.setVisibility(View.VISIBLE);
            //先加载app自带的shortcut
            for (int i = shortcutCount; i > 0; i--) {
            //布局见补充2
                DeepShortcutView v = inflateAndAdd(R.layout.deep_shortcut, mDeepShortcutContainer);
                v.getLayoutParams().width = mContainerWidth;
                mShortcuts.add(v);
            }
            //快捷方式是否隐藏,补充3
            updateHiddenShortcuts();
        //小部件快捷方式
            if (widgetShortcutOpt.isPresent()) {
                if (mWidgetContainer == null) {
                //线性布局
                    mWidgetContainer = inflateAndAdd(R.layout.widget_shortcut_container,
                            this);
                }
                //最终效果见4.8.1
                initializeWidgetShortcut(mWidgetContainer, widgetShortcutOpt.get());
            }
//补充4,小部件以外的其他快捷方式
            initializeSystemShortcuts(systemShortcuts);
        } else {//应用没有定义快捷方式的情况下,
            mDeepShortcutContainer.setVisibility(View.GONE);
            if (!shortcuts.isEmpty()) {
                for (SystemShortcut shortcut : shortcuts) {
                    initializeSystemShortcut(R.layout.system_shortcut, this, shortcut);
                }
            }
        }
//这个可能根据需求,child进行空翻,比如显示在icon下的是ABC,显示在icon上顺序就是CBA
        reorderAndShow(viewsToFlip);

        ItemInfo originalItemInfo = (ItemInfo) originalIcon.getTag();
        if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
            setAccessibilityPaneTitle(getTitleForAccessibility());
        }

        mOriginalIcon.setForceHideDot(true);

        //进行动画
        setLayoutTransition(new LayoutTransition());

        // Load the shortcuts on a background thread and update the container as it animates.
        MODEL_EXECUTOR.getHandler().postAtFrontOfQueue(PopupPopulator.createUpdateRunnable(
                mActivityContext, originalItemInfo, new Handler(Looper.getMainLooper()),
                this, mShortcuts, notificationKeys));
    }

>2.deep_shortcut.xml

image.png

<com.android.launcher3.shortcuts.DeepShortcutView
    android:layout_width="@dimen/bg_popup_item_width"
    android:layout_height="@dimen/bg_popup_item_height"
    android:elevation="@dimen/deep_shortcuts_elevation"
    android:background="@drawable/middle_item_primary"
    android:theme="@style/PopupItem" >
<!--drawableEnd两条横岗的图标,代码这个条目可以长按拖动到桌面-->
    <com.android.launcher3.shortcuts.DeepShortcutTextView
        style="@style/BaseIcon"
        android:id="@+id/bubble_text"
        android:background="?android:attr/selectableItemBackground"
        android:gravity="start|center_vertical"
        android:textAlignment="viewStart"
        android:paddingStart="@dimen/deep_shortcuts_text_padding_start"
        android:paddingEnd="@dimen/popup_padding_end"
        android:drawableEnd="@drawable/ic_drag_handle"//末尾两条横岗的图片
        android:drawablePadding="@dimen/deep_shortcut_drawable_padding"
        android:singleLine="true"
        android:ellipsize="end"
        android:textSize="14sp"
        android:textColor="?android:attr/textColorPrimary"
        launcher:layoutHorizontal="true"
        launcher:iconDisplay="shortcut_popup"
        launcher:iconSizeOverride="@dimen/deep_shortcut_icon_size" />
<!--快捷方式图标-->
    <View
        android:id="@+id/icon"
        android:layout_width="@dimen/deep_shortcut_icon_size"
        android:layout_height="@dimen/deep_shortcut_icon_size"
        android:layout_marginStart="@dimen/popup_padding_start"
        android:layout_gravity="start|center_vertical"
        android:background="@drawable/ic_deepshortcut_placeholder"/>

</com.android.launcher3.shortcuts.DeepShortcutView>

>3.updateHiddenShortcuts

超过最大数的shortcut隐藏

    protected void updateHiddenShortcuts() {
    //显示通知的话,最大是2,否则是4
        int allowedCount = mNotificationContainer != null
                ? MAX_SHORTCUTS_IF_NOTIFICATIONS : MAX_SHORTCUTS;

        int total = mShortcuts.size();
        for (int i = 0; i < total; i++) {
            DeepShortcutView view = mShortcuts.get(i);
            view.setVisibility(i >= allowedCount ? GONE : VISIBLE);
        }
    }

>4.initializeSystemShortcuts

    private void initializeSystemShortcuts(List<SystemShortcut> shortcuts) {
        if (shortcuts.isEmpty()) {
            return;
        }
        //只有一个,显示完成的条目
        if (shortcuts.size() == 1) {
            initializeSystemShortcut(R.layout.system_shortcut, this, shortcuts.get(0));
            return;
        }
        //先加载一个容器
        mSystemShortcutContainer = inflateAndAdd(R.layout.system_shortcut_icons, this);
        for (SystemShortcut shortcut : shortcuts) {
        //完事其他shorcut只加载icon,加入上边的容器里
            initializeSystemShortcut(
                    R.layout.system_shortcut_icon_only, mSystemShortcutContainer,
                    shortcut);
        }
    }

4.5.PopupDataProvider.java

>1.getShortcutCountForItem

其实就是应用自定义的shortuct的个数

    public int getShortcutCountForItem(ItemInfo info) {
    //应用不可用或者不支持快捷方式
        if (!ShortcutUtil.supportsDeepShortcuts(info)) {
            return 0;
        }
        ComponentName component = info.getTargetComponent();
        if (component == null) {
            return 0;
        }

        Integer count = mDeepShortcutMap.get(new ComponentKey(component, info.user));
        return count == null ? 0 : count;
    }

>2.getNotificationKeysForItem

获取通知数据

    public @NonNull List<NotificationKeyData> getNotificationKeysForItem(ItemInfo info) {
        DotInfo dotInfo = getDotInfoForItem(info);
        return dotInfo == null ? Collections.EMPTY_LIST
                : getNotificationsForItem(info, dotInfo.getNotificationKeys());
    }

4.6.QuickstepLauncher.java

>1.getSupportedShortcuts

这里是系统默认的一些,比如app info, widgets ,split left

    public Stream<SystemShortcut.Factory> getSupportedShortcuts() {
        Stream<SystemShortcut.Factory> base = Stream.of(WellbeingModel.SHORTCUT_FACTORY);
        if (ENABLE_SPLIT_FROM_WORKSPACE.get() && mDeviceProfile.isTablet) {
            RecentsView recentsView = getOverviewPanel();
            
            //获取分屏相关的选项
            List<SplitPositionOption> positions =
                    recentsView.getPagedOrientationHandler().getSplitPositionOptions(
                            mDeviceProfile);
            List<SystemShortcut.Factory<QuickstepLauncher>> splitShortcuts = new ArrayList<>();
            for (SplitPositionOption position : positions) {
            //数据见4.7.1
                splitShortcuts.add(getSplitSelectShortcutByPosition(position));
            }
            //加入base
            base = Stream.concat(base, splitShortcuts.stream());
        }
        继续加入父类默认的,以及hotseat推荐控制器里的
        return Stream.concat(
                Stream.of(mHotseatPredictionController),
                Stream.concat(base, super.getSupportedShortcuts()));
    }

父类

    public Stream<SystemShortcut.Factory> getSupportedShortcuts() {
        return Stream.of(APP_INFO, WIDGETS, INSTALL);
    }

4.7.QuickstepSystemShortcut

>1.getSplitSelectShortcutByPosition

    static SystemShortcut.Factory<QuickstepLauncher> getSplitSelectShortcutByPosition(
            SplitPositionOption position) {
          //交给SplitSelectSystemShortcut处理,见补充2
        return (activity, itemInfo, originalView) ->
                new QuickstepSystemShortcut.SplitSelectSystemShortcut(activity, itemInfo,
                        originalView, position);
    }

>2.SplitSelectSystemShortcut

分屏用到的shortuct,效果图

image.png

        public SplitSelectSystemShortcut(QuickstepLauncher launcher, ItemInfo itemInfo,
                View originalView, SplitPositionOption position) {
            super(position.iconResId, position.textResId, launcher, itemInfo, originalView);
            //..
            
        public void onClick(View view) {
            // Initiate splitscreen from the Home screen or Home All Apps
            Bitmap bitmap;
            Intent intent;
            //根据数据类型获取intent
            if (mItemInfo instanceof WorkspaceItemInfo) {
                final WorkspaceItemInfo workspaceItemInfo = (WorkspaceItemInfo) mItemInfo;
                bitmap = workspaceItemInfo.bitmap.icon;
                intent = workspaceItemInfo.intent;
            } else if (mItemInfo instanceof com.android.launcher3.model.data.AppInfo) {
                final com.android.launcher3.model.data.AppInfo appInfo =
                        (com.android.launcher3.model.data.AppInfo) mItemInfo;
                bitmap = appInfo.bitmap.icon;
                intent = appInfo.intent;
            } else {
                return;
            }

            RecentsView recentsView = mTarget.getOverviewPanel();
            //查看是否有app运行实例,有的话直接用,见6.2
            recentsView.findLastActiveTaskAndDoSplitOperation(
                    intent.getComponent(),
                    (Consumer<Task>) foundTask -> {
                        SplitSelectSource source = new SplitSelectSource(mOriginalView,
                                new BitmapDrawable(bitmap), intent, mPosition, mItemInfo,
                                splitEvent, foundTask);
                        if (ENABLE_SPLIT_FROM_WORKSPACE_TO_WORKSPACE.get()) {
                            startSplitToHome(source);
                        } else {
                            recentsView.initiateSplitSelect(source);//见4.9
                        }
                    }
            );
        }

4.8.SystemShortcut.java

public abstract class SystemShortcut<T extends Context & ActivityContext> extends ItemInfo
        implements View.OnClickListener {
  • 子类有如下三种,4.7.2也是

>1.Widgets

image.png

    public static final Factory<Launcher> WIDGETS = (launcher, itemInfo, originalView) -> {
        if (itemInfo.getTargetComponent() == null) return null;
        //看是否有支持的小部件
        final List<WidgetItem> widgets =
                launcher.getPopupDataProvider().getWidgetsForPackageUser(new PackageUserKey(
                        itemInfo.getTargetComponent().getPackageName(), itemInfo.user));
        if (widgets.isEmpty()) {
            return null;
        }
        return new Widgets(launcher, itemInfo, originalView);
    };

    public static class Widgets extends SystemShortcut<Launcher> {
        public Widgets(Launcher target, ItemInfo itemInfo, View originalView) {
            super(R.drawable.ic_widget, R.string.widget_button_text, target, itemInfo,
                    originalView);
        }

        @Override
        public void onClick(View view) {
            AbstractFloatingView.closeAllOpenViews(mTarget);
            WidgetsBottomSheet widgetsBottomSheet =
                    (WidgetsBottomSheet) mTarget.getLayoutInflater().inflate(
                            R.layout.widgets_bottom_sheet, mTarget.getDragLayer(), false);
            widgetsBottomSheet.populateAndShow(mItemInfo);
        }
    }

>2.AppInfo

下图,如果显示不下,就是补充1里弹框右上角那个感叹号。 image.png

    public static class AppInfo<T extends Context & ActivityContext> extends SystemShortcut<T> {
        public AppInfo(T target, ItemInfo itemInfo, View originalView) {
            super(R.drawable.ic_info_no_shadow, R.string.app_info_drop_target_label, target,
                    itemInfo, originalView);
        }

        public void onClick(View view) {
            dismissTaskMenuView(mTarget);
            Rect sourceBounds = Utilities.getViewBounds(view);
            new PackageManagerHelper(mTarget).startDetailsActivityForInfo(
                    mItemInfo, sourceBounds, ActivityOptions.makeBasic().toBundle());
        }

>3.Install

    public static class Install extends SystemShortcut<BaseDraggingActivity> {

        public Install(BaseDraggingActivity target, ItemInfo itemInfo, View originalView) {
            super(R.drawable.ic_install_no_shadow, R.string.install_drop_target_label,
                    target, itemInfo, originalView);
        }

        public void onClick(View view) {
            Intent intent = new PackageManagerHelper(view.getContext()).getMarketIntent(
                    mItemInfo.getTargetComponent().getPackageName());
            mTarget.startActivitySafely(view, intent, mItemInfo);
            AbstractFloatingView.closeAllOpenViews(mTarget);
        }
    }

4.9.LauncherRecentsView.java

>1.initiateSplitSelect

    public void initiateSplitSelect(QuickstepSystemShortcut.SplitSelectSource splitSelectSource) {
    //见父类6.3,这里就是设置数据
        super.initiateSplitSelect(splitSelectSource);
        //进入recent页面
        mActivity.getStateManager().goToState(LauncherState.OVERVIEW_SPLIT_SELECT);
    }

>2.父类RecentsView.java

    public void initiateSplitSelect(QuickstepSystemShortcut.SplitSelectSource splitSelectSource) {
        mSplitSelectSource = splitSelectSource;
        //见4.10
        mSplitSelectStateController.setInitialTaskSelect(splitSelectSource.intent,
                splitSelectSource.position.stagePosition, splitSelectSource.itemInfo,
                splitSelectSource.splitEvent, splitSelectSource.alreadyRunningTask);
    }

>3.canLaunchFullscreenTask

    protected boolean canLaunchFullscreenTask() {
        return !mActivity.isInState(OVERVIEW_SPLIT_SELECT);
    }

4.10.SplitSelectStateController.java

看完两个方法,发现它就设置了数据,啥也没干。得等第二个分屏数据设置完才会用到

>1.setInitialTaskSelect

    public void setInitialTaskSelect(Intent intent, @StagePosition int stagePosition,
            @NonNull ItemInfo itemInfo, StatsLogManager.EventEnum splitEvent,
            @Nullable Task alreadyRunningTask) {
        if (alreadyRunningTask != null) {
            mInitialTaskId = alreadyRunningTask.key.id;
        } else {
            mInitialTaskIntent = intent;
            mUser = itemInfo.user;
        }

        setInitialData(stagePosition, splitEvent, itemInfo);
    }

>2.setInitialData

    private void setInitialData(@StagePosition int stagePosition,
            StatsLogManager.EventEnum splitEvent, ItemInfo itemInfo) {
        mItemInfo = itemInfo;
        mStagePosition = stagePosition;
        mSplitEvent = splitEvent;
    }

>3.isBothSplitAppsConfirmed

分屏需要的两个task的数据是否都已经设置

    public boolean isBothSplitAppsConfirmed() {
        return isInitialTaskIntentSet() && isSecondTaskIntentSet();
    }

    private boolean isInitialTaskIntentSet() {
        return (mInitialTaskId != INVALID_TASK_ID || mInitialTaskIntent != null);
    }

    private boolean isSecondTaskIntentSet() {
        return (mSecondTaskId != INVALID_TASK_ID || mSecondTaskIntent != null);
    }

>4.launchSplitTasks

开始加载两个分屏task,见6.1里判断可以分屏后,动画结束会调用

    public void launchSplitTasks(Consumer<Boolean> callback) {
        Pair<InstanceId, com.android.launcher3.logging.InstanceId> instanceIds =
                LogUtils.getShellShareableInstanceId();
        launchTasks(mInitialTaskId, mInitialTaskIntent, mSecondTaskId, mSecondTaskIntent,
                mStagePosition, callback, false /* freezeTaskList */, DEFAULT_SPLIT_RATIO,
                instanceIds.first);
    }

    public void launchTasks(int taskId1, @Nullable Intent intent1, int taskId2,
            @Nullable Intent intent2, @StagePosition int stagePosition,
            Consumer<Boolean> callback, boolean freezeTaskList, float splitRatio,
            @Nullable InstanceId shellInstanceId) {

        final ActivityOptions options1 = ActivityOptions.makeBasic();
        if (freezeTaskList) {
            options1.setFreezeRecentTasksReordering();
        }
        if (TaskAnimationManager.ENABLE_SHELL_TRANSITIONS) {
    //..
        } else {
            final RemoteSplitLaunchAnimationRunner animationRunner =
                    new RemoteSplitLaunchAnimationRunner(taskId1, taskId2, callback);
            final RemoteAnimationAdapter adapter = new RemoteAnimationAdapter(
                    animationRunner, 300, 150,
                    ActivityThread.currentActivityThread().getApplicationThread());
        //说明2个task都有旧的活动
            if (intent1 == null && intent2 == null) {
                mSystemUiProxy.startTasksWithLegacyTransition(taskId1, options1.toBundle(),
                        taskId2, null /* options2 */, stagePosition, splitRatio, adapter,
                        shellInstanceId);
            } else if (intent2 == null) {
            //intent1不为空,通过intent启动
                launchIntentOrShortcutLegacy(intent1, options1, taskId2, stagePosition, splitRatio,
                        adapter, shellInstanceId);
            } else if (intent1 == null) {
            //intent2不为空,同样通过intent启动
                launchIntentOrShortcutLegacy(intent2, options1, taskId1,
                        getOppositeStagePosition(stagePosition), splitRatio, adapter,
                        shellInstanceId);
            } else {
            //2个intent都不是空
                mSystemUiProxy.startIntentsWithLegacyTransition(getPendingIntent(intent1),
                        options1.toBundle(), getPendingIntent(intent2), null /* options2 */,
                        stagePosition, splitRatio, adapter, shellInstanceId);
            }
        }
    }

4.11.总结

  • "split left/right"点击事件见4.7.2 之后走到 4.9.1设置数据,以及切换状态,数据最终给到4.10.1
  • 之后需要点击分屏的第二个task,点击后走5.1再6.1,最后回到4.10.4开始加载两个task

>1.旧代码

  • 这里看下我们的旧代码,看else,如果是快捷方式的话,就直接传递的Intent
  • shortcut的Intent可以参考2.2.1,是不能直接用的,导致通过小节8,9,10最后找不到对应的activity
            if (taskPendingIntent == null) {
                mSystemUiProxy.startTasksWithLegacyTransition(taskIds[0], mainOpts.toBundle(),
                        taskIds[1], null /* sideOptions */, STAGE_POSITION_BOTTOM_OR_RIGHT,
                        splitRatio, adapter);
            } else {
                mSystemUiProxy.startIntentAndTaskWithLegacyTransition(taskPendingIntent,
                        fillInIntent, taskId2, mainOpts.toBundle(), null /* sideOptions */,
                        stagePosition, splitRatio, adapter);
            }

5.TaskView.java

recents页面里的task列表用的就是这个布局

public class TaskView extends FrameLayout implements Reusable {

image.png

5.1.confirmSecondSplitSelectApp

    protected boolean confirmSecondSplitSelectApp() {
        int index = getLastSelectedChildTaskIndex();
        TaskIdAttributeContainer container = mTaskIdAttributeContainer[index];
        if (container != null) {//见6.1
            return getRecentsView().confirmSplitSelect(this, container.getTask(),
                    container.getIconView().getDrawable(), container.getThumbnailView(),
                    container.getThumbnailView().getThumbnail(), /* intent */ null);
        }
        return false;
    }

有如下两处调用

>1.onClick

    private void onClick(View view) {
        if (getTask() == null) {
            return;
        }
        if (confirmSecondSplitSelectApp()) {
        //如果点击的是第二个分屏的话,返回
            return;
        }
        //否则加载task
        launchTasks();
    }

>2.setIcon

    protected void setIcon(IconView iconView, @Nullable Drawable icon) {
        if (icon != null) {
            iconView.setDrawable(icon);
            iconView.setOnClickListener(v -> {
            //上边的icon点击事件也是先判断这个
                if (confirmSecondSplitSelectApp()) {
                    return;
                }
                showTaskMenu(iconView);
            });
            iconView.setOnLongClickListener(v -> {
                requestDisallowInterceptTouchEvent(true);
                return showTaskMenu(iconView);
            });
        } else {
            iconView.setDrawable(null);
            iconView.setOnClickListener(null);
            iconView.setOnLongClickListener(null);
        }
    }

6.RecentsView.java

6.1.confirmSplitSelect

    public boolean confirmSplitSelect(TaskView containerTaskView, Task task, Drawable drawable,
            View secondView, @Nullable Bitmap thumbnail, Intent intent) {
            //见4.9.3,是否第一个task设置过
        if (canLaunchFullscreenTask()) {
            return false;
        }
        //见4.10.3,分屏的2个task数据都设置过了
        if (mSplitSelectStateController.isBothSplitAppsConfirmed()) {
            return true;
        }
        // Second task is selected either as an already-running Task or an Intent
        if (task != null) {
            if (!task.isDockable) {
                // 不支持分屏
                mSplitUnsupportedToast.show();
                return true;
            }
            //设置第二个task
            mSplitSelectStateController.setSecondTask(task);
        } else {//或者设置intent
            mSplitSelectStateController.setSecondTask(intent);
        }

        RectF secondTaskStartingBounds = new RectF();
        Rect secondTaskEndingBounds = new Rect();
        
        Rect firstTaskStartingBounds = new Rect();
        Rect firstTaskEndingBounds = mTempRect;

        boolean isTablet = mActivity.getDeviceProfile().isTablet;
        SplitAnimationTimings timings = AnimUtils.getDeviceSplitToConfirmTimings(isTablet);
        PendingAnimation pendingAnimation = new PendingAnimation(timings.getDuration());

        int halfDividerSize = getResources()
                .getDimensionPixelSize(R.dimen.multi_window_task_divider_size) / 2;
        //获取两个分屏task最终的范围
        mOrientationHandler.getFinalSplitPlaceholderBounds(halfDividerSize,
                mActivity.getDeviceProfile(),
                mSplitSelectStateController.getActiveSplitStagePosition(), firstTaskEndingBounds,
                secondTaskEndingBounds);

        mFirstFloatingTaskView.getBoundsOnScreen(firstTaskStartingBounds);
        mFirstFloatingTaskView.addConfirmAnimation(pendingAnimation,
                new RectF(firstTaskStartingBounds), firstTaskEndingBounds,
                false /* fadeWithThumbnail */, true /* isStagedTask */);

        safeRemoveDragLayerView(mSecondFloatingTaskView);

        mSecondFloatingTaskView = FloatingTaskView.getFloatingTaskView(mActivity, secondView,
                thumbnail, drawable, secondTaskStartingBounds);
        mSecondFloatingTaskView.setAlpha(1);
        mSecondFloatingTaskView.addConfirmAnimation(pendingAnimation, secondTaskStartingBounds,
                secondTaskEndingBounds, true /* fadeWithThumbnail */, false /* isStagedTask */);

        pendingAnimation.setViewAlpha(mSplitInstructionsView, 0, clampToProgress(LINEAR,
                timings.getInstructionsFadeStartOffset(),
                timings.getInstructionsFadeEndOffset()));

        pendingAnimation.addEndListener(aBoolean -> {
        //动画结束后,开始加载2个分屏的task,见4.10.4
            mSplitSelectStateController.launchSplitTasks(
                    aBoolean1 -> RecentsView.this.resetFromSplitSelectionState());
            InteractionJankMonitorWrapper.end(InteractionJankMonitorWrapper.CUJ_SPLIT_SCREEN_ENTER);
        });

        mSecondSplitHiddenView = containerTaskView;
        if (mSecondSplitHiddenView != null) {
            mSecondSplitHiddenView.setThumbnailVisibility(INVISIBLE);
        }

        // Fade out all other views underneath placeholders
        ObjectAnimator tvFade = ObjectAnimator.ofFloat(this, RecentsView.CONTENT_ALPHA,1, 0);
        pendingAnimation.add(tvFade, DEACCEL_2, SpringProperty.DEFAULT);
        pendingAnimation.buildAnim().start();
        return true;
    }

6.2.findLastActiveTaskAndDoSplitOperation

    public void findLastActiveTaskAndDoSplitOperation(ComponentName componentName,
            Consumer<Task> callback) {
        //循环当前已存在的task
        mModel.getTasks(taskGroups -> {
            Task lastActiveTask = null;
            // 找到和提供的组件名一样的task
            for (int i = taskGroups.size() - 1; i >= 0; i--) {
                GroupTask groupTask = taskGroups.get(i);
                Task task1 = groupTask.task1;
                if (isInstanceOfComponent(task1, componentName)) {
                    lastActiveTask = task1;
                    break;
                }
                Task task2 = groupTask.task2;
                if (isInstanceOfComponent(task2, componentName)) {
                    lastActiveTask = task2;
                    break;
                }
            }
//返回给回调。
            callback.accept(lastActiveTask);
        });
    }

>1.isInstanceOfComponent

    public boolean isInstanceOfComponent(@Nullable Task task, ComponentName componentName) {
        if (task == null) {
            return false;
        }
        //排除已经在分屏状态的task
        if (mSplitHiddenTaskView != null && mSplitHiddenTaskView.getTask().equals(task)) {
            return false;
        }

        return task.key.baseIntent.getComponent().equals(componentName);
    }

6.3.initiateSplitSelect

第一个分屏数据的设置

    public void initiateSplitSelect(QuickstepSystemShortcut.SplitSelectSource splitSelectSource) {
        mSplitSelectSource = splitSelectSource;
        //见4.10.1
        mSplitSelectStateController.setInitialTaskSelect(splitSelectSource.intent,
                splitSelectSource.position.stagePosition, splitSelectSource.itemInfo,
                splitSelectSource.splitEvent, splitSelectSource.alreadyRunningTask);
    }

7.分屏菜单相关

  • 根据横竖屏,手机还是平板,显示的分屏选项是不一样的,反正就是上下左右四种。
public interface PagedOrientationHandler {

    PagedOrientationHandler PORTRAIT = new PortraitPagedViewHandler();
    PagedOrientationHandler LANDSCAPE = new LandscapePagedViewHandler();
    PagedOrientationHandler SEASCAPE = new SeascapePagedViewHandler();

7.1.PortraitPagedViewHandler.java

主要看这个即可,基本用的就是这个。

>1.getSplitPositionOptions

    public List<SplitPositionOption> getSplitPositionOptions(DeviceProfile dp) {
        return Utilities.getSplitPositionOptions(dp);//补充2
    }

>2.getSplitPositionOptions

    public static List<SplitPositionOption> getSplitPositionOptions(
            DeviceProfile dp) {
        List<SplitPositionOption> options = new ArrayList<>();
        // 平板横屏模式下,添加 左右或者上下
        if (dp.isTablet && dp.isLandscape) {
            options.add(new SplitPositionOption(
                    R.drawable.ic_split_left, R.string.split_screen_position_left,
                    STAGE_POSITION_TOP_OR_LEFT, STAGE_TYPE_MAIN));
            options.add(new SplitPositionOption(
                    R.drawable.ic_split_right, R.string.split_screen_position_right,
                    STAGE_POSITION_BOTTOM_OR_RIGHT, STAGE_TYPE_MAIN));
        } else {//手机或者竖屏模式
            //手机反向横屏(逆时针转90度),添加 split right
            if (dp.isSeascape()) {
                // Add left/right options
                options.add(new SplitPositionOption(
                        R.drawable.ic_split_right, R.string.split_screen_position_right,
                        STAGE_POSITION_BOTTOM_OR_RIGHT, STAGE_TYPE_MAIN));
            } else if (dp.isLandscape) {
            //手机横屏(顺时针90度) 添加 split left
                options.add(new SplitPositionOption(
                        R.drawable.ic_split_left, R.string.split_screen_position_left,
                        STAGE_POSITION_TOP_OR_LEFT, STAGE_TYPE_MAIN));
            } else {
                // 竖屏,添加 split top
                options.add(new SplitPositionOption(
                        R.drawable.ic_split_top, R.string.split_screen_position_top,
                        STAGE_POSITION_TOP_OR_LEFT, STAGE_TYPE_MAIN));
            }
        }
        return options;
    }

7.2.RecentsOrientedState.java

recent页面,点击快照上边的logo,弹框里显示的split选项

>1.updateHandler

这里根据方向不同,选用不同的handler

    private boolean updateHandler() {
    //桌面可以旋转的话,返回的就是桌面旋转的角度,否则返回的是默认角度0
        mRecentsActivityRotation = inferRecentsActivityRotation(mDisplayRotation);
        //mTouchRotation就是旋转角度
        if (mRecentsActivityRotation == mTouchRotation || isRecentsActivityRotationAllowed()) {
            mOrientationHandler = PagedOrientationHandler.PORTRAIT;
        } else if (mTouchRotation == ROTATION_90) {
            mOrientationHandler = PagedOrientationHandler.LANDSCAPE;
        } else if (mTouchRotation == ROTATION_270) {
            mOrientationHandler = PagedOrientationHandler.SEASCAPE;
        } else {
            mOrientationHandler = PagedOrientationHandler.PORTRAIT;
        }
        int oldStateId = mStateId;
        // Each SurfaceRotation value takes two bits
        mStateId = (((((mFlags << 2)
                | mDisplayRotation) << 2)
                | mTouchRotation) << 3)
                | (mRecentsRotation < 0 ? 7 : mRecentsRotation);

        return mStateId != oldStateId;
    }

8.PackageManagerServiceUtils.java

8.1.applyEnforceIntentFilterMatching

  • 2.2.1的第一行日志就在这个方法里显示的
  • 这个方法就是对于给定的resolveInfo,进行interFilter的强制过滤判定
  • 这个方法调用的地方,见小节9和小节10
    public static void applyEnforceIntentFilterMatching(
            PlatformCompat compat, ComponentResolverApi resolver,
            List<ResolveInfo> resolveInfos, boolean isReceiver,
            Intent intent, String resolvedType, int filterCallingUid) {

        for (int i = resolveInfos.size() - 1; i >= 0; --i) {
            final ComponentInfo info = resolveInfos.get(i).getComponentInfo();

            // 当调用者是系统、root或相同的应用程序时,不强制过滤器匹配
            if (ActivityManager.checkComponentPermission(null, filterCallingUid,
                    info.applicationInfo.uid, false) == PackageManager.PERMISSION_GRANTED) {
                continue;
            }

            // Only enforce filter matching if target app's target SDK >= T
            //也就是目标版本>=13的才进行判定
            if (!compat.isChangeEnabledInternal(
                    ENFORCE_INTENTS_TO_MATCH_INTENT_FILTERS, info.applicationInfo)) {
                continue;
            }

            final ParsedMainComponent comp;
            if (info instanceof ActivityInfo) {
            //根据广播和活动获取不同的组件
                if (isReceiver) {
                    comp = resolver.getReceiver(info.getComponentName());
                } else {
                    comp = resolver.getActivity(info.getComponentName());
                }
            } else if (info instanceof ServiceInfo) {
            //服务
                comp = resolver.getService(info.getComponentName());
            } else {
                //
                throw new IllegalArgumentException("Unsupported component type");
            }

            if (comp == null || comp.getIntents().isEmpty()) {
            //没找到,或者意图为空,其实就是补充1里,intent-filter组成的Intent
                continue;
            }

            boolean match = false;
            //循环组件支持的所有intent-filter,看是否有匹配的
            for (int j = 0, size = comp.getIntents().size(); j < size; ++j) {
                IntentFilter intentFilter = comp.getIntents().get(j).getIntentFilter();
                //参数里传递来的intent以及resolvedType是否和我们查找到的intentFilter匹配
                if (IntentResolver.intentMatchesFilter(intentFilter, intent, resolvedType)) {
                    match = true;
                    break;
                }
            }
            if (!match) {
                Slog.w(TAG, "Intent does not match component's intent filter: " + intent);
                Slog.w(TAG, "Access blocked: " + comp.getComponentName());
                //没有匹配的,移除数据
                resolveInfos.remove(i);
            }
        }
    }

>1.intentFilter

一个组件可以有多个intent-filter,也就是可以有多种Intent来找到这个类

<activity
    android:name=".TestActivity"
    android:exported="true"
    android:icon="@drawable/ic_homepage_display"
    android:label="3-testActivity">
    
    <intent-filter android:priority="1">
        <action android:name="com.test.my3test" />
        <category android:name="android.intent.category.DEFAULT" />
    </intent-filter>
    
    <intent-filter>
        <action android:name="android.intent.action.VIEW" />
        <category android:name="android.intent.category.DEFAULT" />
    </intent-filter>

</activity>

9.ComputerEngine.java

9.1.queryIntentActivitiesInternal

    public final @NonNull List<ResolveInfo> queryIntentActivitiesInternal(Intent intent,
            String resolvedType, @PackageManager.ResolveInfoFlagsBits long flags,
            @PackageManagerInternal.PrivateResolveFlags long privateResolveFlags,
            int filterCallingUid, int userId, boolean resolveForStart,
            boolean allowDynamicSplits) {
        //userId不存在
        if (!mUserManager.exists(userId)) return Collections.emptyList();
        final String instantAppPkgName = getInstantAppPackageName(filterCallingUid);
        enforceCrossUserPermission(Binder.getCallingUid(), userId,
                false /* requireFullPermission */, false /* checkShell */,
                "query intent activities");
        final String pkgName = intent.getPackage();
        Intent originalIntent = null;
        //获取组件名
        ComponentName comp = intent.getComponent();
        if (comp == null) {
            if (intent.getSelector() != null) {
                originalIntent = intent;
                intent = intent.getSelector();
                comp = intent.getComponent();
            }
        }

        flags = updateFlagsForResolve(flags, userId, filterCallingUid, resolveForStart,
                comp != null || pkgName != null /*onlyExposedExplicitly*/,
                isImplicitImageCaptureIntentAndNotSetByDpc(intent, userId, resolvedType,
                        flags));
        List<ResolveInfo> list = Collections.emptyList();
        boolean skipPostResolution = false;
        if (comp != null) {
        //组件名不为空,说明是显示的声明
            final ActivityInfo ai = getActivityInfo(comp, flags, userId);
            if (ai != null) {
                // When specifying an explicit component, we prevent the activity from being
                // used when either 1) the calling package is normal and the activity is within
                // an ephemeral application or 2) the calling package is ephemeral and the
                // activity is not visible to ephemeral applications.
                //两种阻止意图的情况:
                //1 > 调用包正常,启动的activity是在instant app里
                //2 > 调用包是intant app,要启动的activity对instant app不可见。
                final boolean matchInstantApp =
                        (flags & PackageManager.MATCH_INSTANT) != 0;
                final boolean matchVisibleToInstantAppOnly =
                        (flags & PackageManager.MATCH_VISIBLE_TO_INSTANT_APP_ONLY) != 0;
                final boolean matchExplicitlyVisibleOnly =
                        (flags & PackageManager.MATCH_EXPLICITLY_VISIBLE_ONLY) != 0;
                final boolean isCallerInstantApp =
                        instantAppPkgName != null;
                final boolean isTargetSameInstantApp =
                        comp.getPackageName().equals(instantAppPkgName);
                final boolean isTargetInstantApp =
                        (ai.applicationInfo.privateFlags
                                & ApplicationInfo.PRIVATE_FLAG_INSTANT) != 0;
                final boolean isTargetVisibleToInstantApp =
                        (ai.flags & ActivityInfo.FLAG_VISIBLE_TO_INSTANT_APP) != 0;
                final boolean isTargetExplicitlyVisibleToInstantApp =
                        isTargetVisibleToInstantApp
                                && (ai.flags & ActivityInfo.FLAG_IMPLICITLY_VISIBLE_TO_INSTANT_APP)
                                == 0;
                final boolean isTargetHiddenFromInstantApp =
                        !isTargetVisibleToInstantApp
                                || (matchExplicitlyVisibleOnly
                                && !isTargetExplicitlyVisibleToInstantApp);
                  //阻止instant的,情况1
                final boolean blockInstantResolution =
                        !isTargetSameInstantApp
                                && ((!matchInstantApp && !isCallerInstantApp && isTargetInstantApp)
                                || (matchVisibleToInstantAppOnly && isCallerInstantApp
                                && isTargetHiddenFromInstantApp));
                //阻止正常的,情况2
                final boolean blockNormalResolution =
                        !resolveForStart && !isTargetInstantApp && !isCallerInstantApp
                                && shouldFilterApplication(
                                getPackageStateInternal(ai.applicationInfo.packageName,
                                        Process.SYSTEM_UID), filterCallingUid, userId);
                 //排除情况1和2的,继续处理                       
                if (!blockInstantResolution && !blockNormalResolution) {
                    final ResolveInfo ri = new ResolveInfo();
                    ri.activityInfo = ai;
                    list = new ArrayList<>(1);
                    list.add(ri);
                    //见3.1
                    PackageManagerServiceUtils.applyEnforceIntentFilterMatching(
                            mInjector.getCompatibility(), mComponentResolver,
                            list, false, intent, resolvedType, filterCallingUid);
                }
            }
        } else {//隐式的意图,没有组件名的
        //通过intent查找
            QueryIntentActivitiesResult lockedResult =
                    queryIntentActivitiesInternalBody(
                            intent, resolvedType, flags, filterCallingUid, userId,
                            resolveForStart, allowDynamicSplits, pkgName, instantAppPkgName);
                //结果有两种
            if (lockedResult.answer != null) {
                skipPostResolution = true;
                list = lockedResult.answer;
            } else {
        //
                list = lockedResult.result;
            }
        }

        if (originalIntent != null) {
            // We also have to ensure all components match the original intent
            //见3.1
            PackageManagerServiceUtils.applyEnforceIntentFilterMatching(
                    mInjector.getCompatibility(), mComponentResolver,
                    list, false, originalIntent, resolvedType, filterCallingUid);
        }
    //为true,直接使用list,为false,则过滤短暂活动的activity
        return skipPostResolution ? list : applyPostResolutionFilter(
                list, instantAppPkgName, allowDynamicSplits, filterCallingUid,
                resolveForStart, userId, intent);
    }

9.2.queryIntentServicesInternal

和9.1差不多,这个是处理service的

    public final @NonNull List<ResolveInfo> queryIntentServicesInternal(Intent intent,
            String resolvedType, @PackageManager.ResolveInfoFlagsBits long flags, int userId,
            int callingUid, boolean includeInstantApps) {
        if (!mUserManager.exists(userId)) return Collections.emptyList();
        enforceCrossUserOrProfilePermission(callingUid,
                userId,
                false /*requireFullPermission*/,
                false /*checkShell*/,
                "query intent receivers");
        final String instantAppPkgName = getInstantAppPackageName(callingUid);
        flags = updateFlagsForResolve(flags, userId, callingUid, includeInstantApps,
                false /* isImplicitImageCaptureIntentAndNotSetByDpc */);
        Intent originalIntent = null;
        ComponentName comp = intent.getComponent();
        if (comp == null) {
            if (intent.getSelector() != null) {
                originalIntent = intent;
                intent = intent.getSelector();
                comp = intent.getComponent();
            }
        }
        List<ResolveInfo> list = Collections.emptyList();
        if (comp != null) {
            final ServiceInfo si = getServiceInfo(comp, flags, userId);
            if (si != null) {
                // When specifying an explicit component, we prevent the service from being
                // used when either 1) the service is in an instant application and the
                // caller is not the same instant application or 2) the calling package is
                // ephemeral and the activity is not visible to ephemeral applications.
                final boolean matchInstantApp =
                        (flags & PackageManager.MATCH_INSTANT) != 0;
                final boolean matchVisibleToInstantAppOnly =
                        (flags & PackageManager.MATCH_VISIBLE_TO_INSTANT_APP_ONLY) != 0;
                final boolean isCallerInstantApp =
                        instantAppPkgName != null;
                final boolean isTargetSameInstantApp =
                        comp.getPackageName().equals(instantAppPkgName);
                final boolean isTargetInstantApp =
                        (si.applicationInfo.privateFlags
                                & ApplicationInfo.PRIVATE_FLAG_INSTANT) != 0;
                final boolean isTargetHiddenFromInstantApp =
                        (si.flags & ServiceInfo.FLAG_VISIBLE_TO_INSTANT_APP) == 0;
                final boolean blockInstantResolution =
                        !isTargetSameInstantApp
                                && ((!matchInstantApp && !isCallerInstantApp && isTargetInstantApp)
                                || (matchVisibleToInstantAppOnly && isCallerInstantApp
                                && isTargetHiddenFromInstantApp));

                final boolean blockNormalResolution = !isTargetInstantApp && !isCallerInstantApp
                        && shouldFilterApplication(
                        getPackageStateInternal(si.applicationInfo.packageName,
                                Process.SYSTEM_UID), callingUid, userId);
                if (!blockInstantResolution && !blockNormalResolution) {
                    final ResolveInfo ri = new ResolveInfo();
                    ri.serviceInfo = si;
                    list = new ArrayList<>(1);
                    list.add(ri);
                    //见3.1
                    PackageManagerServiceUtils.applyEnforceIntentFilterMatching(
                            mInjector.getCompatibility(), mComponentResolver,
                            list, false, intent, resolvedType, callingUid);
                }
            }
        } else {
            list = queryIntentServicesInternalBody(intent, resolvedType, flags,
                    userId, callingUid, instantAppPkgName);
        }

        if (originalIntent != null) {
            // We also have to ensure all components match the original intent
            //见3.1
            PackageManagerServiceUtils.applyEnforceIntentFilterMatching(
                    mInjector.getCompatibility(), mComponentResolver,
                    list, false, originalIntent, resolvedType, callingUid);
        }

        return list;
    }

10.ResolveIntentHelper.java

10.1.queryIntentReceiversInternal

这个里和9.1的差不多

    public List<ResolveInfo> queryIntentReceiversInternal(Computer computer, Intent intent,
            String resolvedType, @PackageManager.ResolveInfoFlagsBits long flags, int userId,
            int filterCallingUid, boolean forSend) {
        if (!mUserManager.exists(userId)) return Collections.emptyList();
        // The identity used to filter the receiver components
        final int queryingUid = forSend ? Process.SYSTEM_UID : filterCallingUid;
        computer.enforceCrossUserPermission(queryingUid, userId,
                false /*requireFullPermission*/, false /*checkShell*/, "query intent receivers");
        final String instantAppPkgName = computer.getInstantAppPackageName(queryingUid);
        flags = computer.updateFlagsForResolve(flags, userId, queryingUid,
                false /*includeInstantApps*/,
                computer.isImplicitImageCaptureIntentAndNotSetByDpc(intent, userId,
                        resolvedType, flags));
        Intent originalIntent = null;
        ComponentName comp = intent.getComponent();
        if (comp == null) {
            if (intent.getSelector() != null) {
                originalIntent = intent;
                intent = intent.getSelector();
                comp = intent.getComponent();
            }
        }
        final ComponentResolverApi componentResolver = computer.getComponentResolver();
        List<ResolveInfo> list = Collections.emptyList();
        if (comp != null) {
            final ActivityInfo ai = computer.getReceiverInfo(comp, flags, userId);
            if (ai != null) {
                // When specifying an explicit component, we prevent the activity from being
                // used when either 1) the calling package is normal and the activity is within
                // an instant application or 2) the calling package is ephemeral and the
                // activity is not visible to instant applications.
                final boolean matchInstantApp =
                        (flags & PackageManager.MATCH_INSTANT) != 0;
                final boolean matchVisibleToInstantAppOnly =
                        (flags & PackageManager.MATCH_VISIBLE_TO_INSTANT_APP_ONLY) != 0;
                final boolean matchExplicitlyVisibleOnly =
                        (flags & PackageManager.MATCH_EXPLICITLY_VISIBLE_ONLY) != 0;
                final boolean isCallerInstantApp =
                        instantAppPkgName != null;
                final boolean isTargetSameInstantApp =
                        comp.getPackageName().equals(instantAppPkgName);
                final boolean isTargetInstantApp =
                        (ai.applicationInfo.privateFlags
                                & ApplicationInfo.PRIVATE_FLAG_INSTANT) != 0;
                final boolean isTargetVisibleToInstantApp =
                        (ai.flags & ActivityInfo.FLAG_VISIBLE_TO_INSTANT_APP) != 0;
                final boolean isTargetExplicitlyVisibleToInstantApp = isTargetVisibleToInstantApp
                        && (ai.flags & ActivityInfo.FLAG_IMPLICITLY_VISIBLE_TO_INSTANT_APP) == 0;
                final boolean isTargetHiddenFromInstantApp = !isTargetVisibleToInstantApp
                        || (matchExplicitlyVisibleOnly && !isTargetExplicitlyVisibleToInstantApp);
                final boolean blockResolution =
                        !isTargetSameInstantApp
                                && ((!matchInstantApp && !isCallerInstantApp && isTargetInstantApp)
                                || (matchVisibleToInstantAppOnly && isCallerInstantApp
                                && isTargetHiddenFromInstantApp));
                if (!blockResolution) {
                    ResolveInfo ri = new ResolveInfo();
                    ri.activityInfo = ai;
                    list = new ArrayList<>(1);
                    list.add(ri);
                    //见8.1
                    PackageManagerServiceUtils.applyEnforceIntentFilterMatching(
                            mPlatformCompat, componentResolver, list, true, intent,
                            resolvedType, filterCallingUid);
                }
            }
        } else {
            String pkgName = intent.getPackage();
            if (pkgName == null) {
                final List<ResolveInfo> result = componentResolver
                        .queryReceivers(computer, intent, resolvedType, flags, userId);
                if (result != null) {
                    list = result;
                }
            }
            final AndroidPackage pkg = computer.getPackage(pkgName);
            if (pkg != null) {
                final List<ResolveInfo> result = componentResolver.queryReceivers(computer,
                        intent, resolvedType, flags, pkg.getReceivers(), userId);
                if (result != null) {
                    list = result;
                }
            }
        }

        if (originalIntent != null) {
            // We also have to ensure all components match the original intent
            //见8.1
            PackageManagerServiceUtils.applyEnforceIntentFilterMatching(
                    mPlatformCompat, componentResolver,
                    list, true, originalIntent, resolvedType, filterCallingUid);
        }

        return computer.applyPostResolutionFilter(list, instantAppPkgName, false, queryingUid,
                false, userId, intent);
    }

11.StageCoordinator.java

最终分屏的逻辑会走到这个类里,里边有很多start方法

12.问题

合并了StageCoordinator里startShortcut相关的方法后,确实可以让快捷方式正常的显示分屏,可还是会时不时的出问题,

12.1.无法启动后台应用

>1.log

Background activity start [callingPackage: com.google.android.contacts; callingUid: 10127; appSwitchState: 2; isCallingUidForeground: false; 
callingUidHasAnyVisibleWindow: false; callingUidProcState: CACHED_ACTIVITY; isCallingUidPersistentSystemProcess: false; realCallingUid: 1000; 
isRealCallingUidForeground: false; realCallingUidHasAnyVisibleWindow: false; realCallingUidProcState: PERSISTENT; isRealCallingUidPersistentSystemProcess: true; 
originatingPendingIntent: null; allowBackgroundActivityStart: false; intent: 
Intent { act=android.intent.action.INSERT dat=content://com.android.contacts/... flg=0x1000c000 
cmp=com.google.android.contacts/com.google.android.apps.contacts.editor.EditorShortcutSpringBoardActivity }; callerApp: null; inVisibleTask: false]

>2.shouldAbortBackgroundActivityStart

ActivityStarter.java

  • 正常应该返回false的,从日志看,有时候不知道为啥不满足条件,走到了最后,返回个true,后台活动启动被中断了,分屏自然无法正常显示
  • 弄了个暴力的方法,无视这个方法,都返回false,有点可怕,没敢提交代码。。不过确实基本碰不到无法分屏的情况
    boolean shouldAbortBackgroundActivityStart(int callingUid, int callingPid,
            final String callingPackage, int realCallingUid, int realCallingPid,
            WindowProcessController callerApp, PendingIntentRecord originatingPendingIntent,
            boolean allowBackgroundActivityStart, Intent intent, ActivityOptions checkedOptions) {


>3.解决

  • 最终还是建议禁掉桌面shortuct显示分屏选项,这样问题少点,要不合并这些代码问题太多。毕竟我们的代码太老了。
  • 就是4.4.1里的代码,对参数shortcut集合数据进行过滤
  • filter里增加条件 shortcut.mItemInfo instanceof WorkspaceItemInfo这种类型的就是shortuct
List<SystemShortcut> systemShortcuts = shortcuts .stream() 
            .filter(shortcut -> !(shortcut instanceof SystemShortcut.Widgets)) 
            .collect(Collectors.toList());