android13#settings#widgets

84 阅读10分钟

1.简介

1.1.问题

根据已有的知识,应用添加小组件需要注册一个带固定meta-data数据以及IntentFilter的广播,如下

        <receiver android:name=".ProtipWidget"
             android:label="@string/widget_name"
             android:exported="true">
            <intent-filter>
                <action android:name="android.appwidget.action.APPWIDGET_UPDATE"/>
            </intent-filter>
            <meta-data android:name="android.appwidget.provider"
                 android:resource="@xml/widget_build"/> //见补充1
        </receiver>

可我在settings的清单文件里没搜到对应的intent,那么为啥settings还会显示一个小组件?

image.png

1.2.总结

  • settings利用的是"android.intent.action.CREATE_SHORTCUT",launcher里会查找这种activity
  • 参考2.1获取数据,具体查找CREATE_SHORTCUT的是小节2.2.1的方法
  • 参考小节4.4小部件添加的方式,点击后跳转的是小节5的对象,参考5.1显示,后边就是拖拽到桌面,松手后会执行4.3的方法
  • 参考4.3可以看到跳转到CREATE_SHORTCUT对应的页面,页面数据参考小节3
  • 小节3选择一个快捷方式后点击,回到小节4.1添加数据,到此结束

2.WidgetsModel.java

2.1.update

import static com.android.launcher3.pm.ShortcutConfigActivityInfo.queryList;

    public List<ComponentWithLabelAndIcon> update(
            LauncherAppState app, @Nullable PackageUserKey packageUser) {
        Preconditions.assertWorkerThread();

        Context context = app.getContext();
        final ArrayList<WidgetItem> widgetsAndShortcuts = new ArrayList<>();
        List<ComponentWithLabelAndIcon> updatedItems = new ArrayList<>();
        try {
            InvariantDeviceProfile idp = app.getInvariantDeviceProfile();
            PackageManager pm = app.getContext().getPackageManager();

            // Widgets,这个就是正常的小组件,就是广播注册的那个
            WidgetManagerHelper widgetManager = new WidgetManagerHelper(context);
            for (AppWidgetProviderInfo widgetInfo : widgetManager.getAllProviders(packageUser)) {
                LauncherAppWidgetProviderInfo launcherWidgetInfo =
                        LauncherAppWidgetProviderInfo.fromProviderInfo(context, widgetInfo);

                widgetsAndShortcuts.add(new WidgetItem(//参考2.4.1
                        launcherWidgetInfo, idp, app.getIconCache()));
                updatedItems.add(launcherWidgetInfo);
            }

            // Shortcuts
            for (ShortcutConfigActivityInfo info :
                    queryList(context, packageUser)) {//方法参考2.2.1
                    //对象参考2.4.2,这里是添加的是快捷方式配置页面
                widgetsAndShortcuts.add(new WidgetItem(info, app.getIconCache(), pm));
                updatedItems.add(info);
            }
            //最后处理数据
            setWidgetsAndShortcuts(widgetsAndShortcuts, app, packageUser);
        }

>1.setWidgetsAndShortcuts

    private synchronized void setWidgetsAndShortcuts(ArrayList<WidgetItem> rawWidgetsShortcuts,
            LauncherAppState app, @Nullable PackageUserKey packageUser) {

        PackageItemInfoCache packageItemInfoCache = new PackageItemInfoCache();

        if (packageUser == null) {
            // Clear the list if this is an update on all widgets and shortcuts.
            mWidgetsList.clear();
        } else {
            // Otherwise, only clear the widgets and shortcuts for the changed package.
            mWidgetsList.remove(packageItemInfoCache.getOrCreate(packageUser));
        }

        //添加数据,更新数据
        mWidgetsList.putAll(rawWidgetsShortcuts.stream()
                .filter(new WidgetValidityCheck(app))
                .flatMap(widgetItem -> getPackageUserKeys(app.getContext(), widgetItem).stream()
                        .map(key -> new Pair<>(packageItemInfoCache.getOrCreate(key), widgetItem)))
                .collect(groupingBy(pair -> pair.first, mapping(pair -> pair.second, toList()))));

        // Update each package entry
        IconCache iconCache = app.getIconCache();
        for (PackageItemInfo p : packageItemInfoCache.values()) {
            iconCache.getTitleAndIconForApp(p, true /* userLowResIcon */);
        }
    }

2.2.ShortcutConfigActivityInfo

>1.queryList

    public static List<ShortcutConfigActivityInfo> queryList(
            Context context, @Nullable PackageUserKey packageUser) {
        List<ShortcutConfigActivityInfo> result = new ArrayList<>();
        UserHandle myUser = Process.myUserHandle();

        final List<UserHandle> users;
        final String packageName;
        if (packageUser == null) {
            users = UserCache.INSTANCE.get(context).getUserProfiles();
            packageName = null;
        } else {
            users = Collections.singletonList(packageUser.mUser);
            packageName = packageUser.mPackageName;
        }
        LauncherApps launcherApps = context.getSystemService(LauncherApps.class);
        for (UserHandle user : users) {
            boolean ignoreTargetSdk = myUser.equals(user);
            //就是查找"android.intent.action.CREATE_SHORTCUT"这种IntentFilter的activity
            //参考3.1
            for (LauncherActivityInfo activityInfo :
                    launcherApps.getShortcutConfigActivityList(packageName, user)) {
                if (ignoreTargetSdk || activityInfo.getApplicationInfo().targetSdkVersion
                        >= Build.VERSION_CODES.O) {
                    result.add(new ShortcutConfigActivityInfoVO(activityInfo));
                }
            }
        }
        return result;
    }
}

2.3.getWidgetsListForPicker

    public synchronized ArrayList<WidgetsListBaseEntry> getWidgetsListForPicker(Context context) {
        ArrayList<WidgetsListBaseEntry> result = new ArrayList<>();
        AlphabeticIndexCompat indexer = new AlphabeticIndexCompat(context);
//数据来源是2.1.1
        for (Map.Entry<PackageItemInfo, List<WidgetItem>> entry : mWidgetsList.entrySet()) {
            PackageItemInfo pkgItem = entry.getKey();
            List<WidgetItem> widgetItems = entry.getValue();
            String sectionName = (pkgItem.title == null) ? "" :
                    indexer.computeSectionName(pkgItem.title);
            result.add(WidgetsListHeaderEntry.create(pkgItem, sectionName, widgetItems));
            result.add(new WidgetsListContentEntry(pkgItem, sectionName, widgetItems));
        }
        return result;
    }

2.4.WidgetItem

>1.构造方法1

这种是小组件

    public WidgetItem(LauncherAppWidgetProviderInfo info,
            InvariantDeviceProfile idp, IconCache iconCache) {
        super(info.provider, info.getProfile());

        label = iconCache.getTitleNoCache(info);
        widgetInfo = info;//有小组件信息
        activityInfo = null;//没有activity信息

        spanX = Math.min(info.spanX, idp.numColumns);
        spanY = Math.min(info.spanY, idp.numRows);
    }

>2.构造方法2

这种是快捷方式配置页面

    public WidgetItem(ShortcutConfigActivityInfo info, IconCache iconCache, PackageManager pm) {
        super(info.getComponent(), info.getUser());
        label = info.isPersistable() ? iconCache.getTitleNoCache(info) :
                Utilities.trim(info.getLabel(pm));
        widgetInfo = null;
        activityInfo = info;//这个数据不为空
        spanX = spanY = 1;
    }

3.CreateShortcutPreferenceController

3.1.清单文件

这种最终看meta-data里配置的fragment即可,fragment没啥东西,数据都是controller动态添加的

        <activity android:name=".Settings$CreateShortcutActivity"
                  android:exported="true"
                  android:label="@string/settings_shortcut">
            <intent-filter>
                <action android:name="android.intent.action.CREATE_SHORTCUT" />
                <category android:name="android.intent.category.DEFAULT" />
            </intent-filter>
            <meta-data
                android:name="com.android.settings.FRAGMENT_CLASS"
                android:value="com.android.settings.shortcut.CreateShortcut" />
        </activity>

3.2.常量

public class CreateShortcutPreferenceController extends BasePreferenceController {

    static final String SHORTCUT_ID_PREFIX = "component-shortcut-";
    //数据查询用到的intent
    static final Intent SHORTCUT_PROBE = new Intent(Intent.ACTION_MAIN)
            .addCategory("com.android.settings.SHORTCUT")
            .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);

3.3.updateState

    public void updateState(Preference preference) {
        if (!(preference instanceof PreferenceGroup)) {
            return;
        }
        final PreferenceGroup group = (PreferenceGroup) preference;
        group.removeAll();
        final List<ResolveInfo> shortcuts = queryShortcuts();//数据参考补充1
        final Context uiContext = preference.getContext();
        if (shortcuts.isEmpty()) {
            return;
        }
        PreferenceCategory category = new PreferenceCategory(uiContext);
        group.addPreference(category);
        int bucket = 0;
        for (ResolveInfo info : shortcuts) {
            //按照优先级分组,除以10的值一样就是一组
            final int currentBucket = info.priority / 10;
            //新的分组
            boolean needDivider = currentBucket != bucket;
            bucket = currentBucket;
            if (needDivider) {
                // add a new Category
                category = new PreferenceCategory(uiContext);
                group.addPreference(category);
            }

//实例化选项,设置标题,key,以及点击事件
            final Preference pref = new Preference(uiContext);
            pref.setTitle(info.loadLabel(mPackageManager));
            pref.setKey(info.activityInfo.getComponentName().flattenToString());
            pref.setOnPreferenceClickListener(clickTarget -> {
                if (mHost == null) {
                    return false;
                }
                final Intent shortcutIntent = createResultIntent(//参考3.3
                        buildShortcutIntent(uiContext, info),//补充2
                        info, clickTarget.getTitle());
                        //最终交给launcher3处理了,参考4.1
                mHost.setResult(Activity.RESULT_OK, shortcutIntent);
                mHost.finish();
                return true;
            });
            //添加选项
            category.addPreference(pref);
        }
    }

>1.queryShortcuts

    List<ResolveInfo> queryShortcuts() {
        final List<ResolveInfo> shortcuts = new ArrayList<>();
        //查找对应的intent
        final List<ResolveInfo> activities = mPackageManager.queryIntentActivities(SHORTCUT_PROBE,
                PackageManager.GET_META_DATA);

        if (activities == null) {
            return null;
        }
        for (ResolveInfo info : activities) {
        //先过滤两种特殊的类
            if (info.activityInfo.name.contains(
                    Settings.OneHandedSettingsActivity.class.getSimpleName())) {
                if (!OneHandedSettingsUtils.isSupportOneHandedMode()) {
                    continue;
                }
            }
            if (info.activityInfo.name.endsWith(TetherSettingsActivity.class.getSimpleName())) {
                if (!mConnectivityManager.isTetheringSupported()) {
                    continue;
                }
            }
            if (!info.activityInfo.applicationInfo.isSystemApp()) {
                //非系统应用
                continue;
            }
            shortcuts.add(info);
        }
        Collections.sort(shortcuts, SHORTCUT_COMPARATOR);
        return shortcuts;
    }
//按优先级排序
    private static final Comparator<ResolveInfo> SHORTCUT_COMPARATOR =
            (i1, i2) -> i1.priority - i2.priority;

>2.buildShortcutIntent

构建要查找的intent

    private static Intent buildShortcutIntent(Context context, ResolveInfo info) {
        Intent intent = new Intent(SHORTCUT_PROBE)//参考3.2
                .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP)
                .setClassName(info.activityInfo.packageName, info.activityInfo.name);
        if (ActivityEmbeddingUtils.isEmbeddingActivityEnabled(context)) {
            intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
        }
        return intent;
    }

3.3.createResultIntent

    Intent createResultIntent(Intent shortcutIntent, ResolveInfo resolveInfo,
            CharSequence label) {
            //补充1,生成快捷方式对象
        ShortcutInfo info = createShortcutInfo(mContext, shortcutIntent, resolveInfo, label);
        //根据快捷方式信息获取对应的意图,参考3.4
        Intent intent = mShortcutManager.createShortcutResultIntent(info);
        if (intent == null) {
            intent = new Intent();
        }
        intent.putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE,
                Intent.ShortcutIconResource.fromContext(mContext, R.mipmap.ic_launcher_settings))
                .putExtra(Intent.EXTRA_SHORTCUT_INTENT, shortcutIntent)
                .putExtra(Intent.EXTRA_SHORTCUT_NAME, label);

        final ActivityInfo activityInfo = resolveInfo.activityInfo;
        if (activityInfo.icon != 0) {
            intent.putExtra(Intent.EXTRA_SHORTCUT_ICON, createIcon(
                    mContext,
                    activityInfo.applicationInfo,
                    activityInfo.icon,
                    R.layout.shortcut_badge,
                    mContext.getResources().getDimensionPixelSize(R.dimen.shortcut_size)));
        }
        return intent;
    }

>1.createShortcutInfo

`    private static ShortcutInfo createShortcutInfo(Context context, Intent shortcutIntent,
            ResolveInfo resolveInfo, CharSequence label) {
        final ActivityInfo activityInfo = resolveInfo.activityInfo;

        final Icon maskableIcon;
        if (activityInfo.icon != 0 && activityInfo.applicationInfo != null) {
            maskableIcon = Icon.createWithAdaptiveBitmap(createIcon(
                    context,
                    activityInfo.applicationInfo, activityInfo.icon,
                    R.layout.shortcut_badge_maskable,
                    context.getResources().getDimensionPixelSize(R.dimen.shortcut_size_maskable)));
        } else {
            maskableIcon = Icon.createWithResource(context, R.drawable.ic_launcher_settings);
        }
        //id的生成,固定的前缀加上组件名
        final String shortcutId = SHORTCUT_ID_PREFIX +
                shortcutIntent.getComponent().flattenToShortString();
        return new ShortcutInfo.Builder(context, shortcutId)
                .setShortLabel(label)
                .setIntent(shortcutIntent)
                .setIcon(maskableIcon)
                .build();
    }

3.4.获取intent

>1.createShortcutResultIntent

    public void createShortcutResultIntent(String packageName, ShortcutInfo shortcut, int userId,
            AndroidFuture<Intent> ret) throws RemoteException {
//..
        verifyShortcutInfoPackage(packageName, shortcut);
        final Intent intent;
        synchronized (mLock) {
            throwIfUserLockedL(userId);
            // Send request to the launcher, if supported.补充2
            intent = mShortcutRequestPinProcessor.createShortcutResultIntent(shortcut, userId);
        }
        verifyStates();
        ret.complete(intent);
    }

>2.createShortcutResultIntent

    public Intent createShortcutResultIntent(@NonNull ShortcutInfo inShortcut, int userId) {
        // Find the default launcher activity
        final int launcherUserId = mService.getParentOrSelfUserId(userId);
        final String defaultLauncher = mService.getDefaultLauncher(launcherUserId);
        if (defaultLauncher == null) {
            Log.e(TAG, "Default launcher not found.");
            return null;
        }

        // Make sure the launcher user is unlocked. (it's always the parent profile, so should
        // really be unlocked here though.)
        mService.throwIfUserLockedL(launcherUserId);

        // Next, validate the incoming shortcut, etc.补充3
        final PinItemRequest request = requestPinShortcutLocked(inShortcut, null, defaultLauncher,
                launcherUserId);
        return new Intent().putExtra(LauncherApps.EXTRA_PIN_ITEM_REQUEST, request);
    }

>3.requestPinShortcutLocked

    private PinItemRequest requestPinShortcutLocked(ShortcutInfo inShortcut,
            IntentSender resultIntentOriginal, String launcherPackage, int launcherUserId) {
        final ShortcutPackage ps = mService.getPackageShortcutsForPublisherLocked(
                inShortcut.getPackage(), inShortcut.getUserId());

        final ShortcutInfo existing = ps.findShortcutById(inShortcut.getId());
        final boolean existsAlready = existing != null;
        final boolean existingIsVisible = existsAlready && existing.isVisibleToPublisher();


        // This is the shortcut that'll be sent to the launcher.
        final ShortcutInfo shortcutForLauncher;

        IntentSender resultIntentToSend = resultIntentOriginal;
//快捷方式已经存在
        if (existsAlready) {
            validateExistingShortcut(existing);

            final boolean isAlreadyPinned = mService.getLauncherShortcutsLocked(
                    launcherPackage, existing.getUserId(), launcherUserId).hasPinned(existing);
            if (isAlreadyPinned) {
                // When the shortcut is already pinned by this launcher, the request will always
                // succeed, so just send the result at this point.
                sendResultIntent(resultIntentOriginal, null);

                // So, do not send the intent again.
                resultIntentToSend = null;
            }

            // Pass a clone, not the original.
            // Note this will remove the intent and icons.
            shortcutForLauncher = existing.clone(ShortcutInfo.CLONE_REMOVE_FOR_LAUNCHER);

            if (!isAlreadyPinned) {
                // FLAG_PINNED may still be set, if it's pinned by other launchers.
                shortcutForLauncher.clearFlags(ShortcutInfo.FLAG_PINNED);
            }
        } else {
            // If the shortcut has no default activity, try to set the main activity.
            // But in the request-pin case, it's optional, so it's okay even if the caller
            // has no default activity.
            if (inShortcut.getActivity() == null) {
                inShortcut.setActivity(mService.injectGetDefaultMainActivity(
                        inShortcut.getPackage(), inShortcut.getUserId()));
            }

            // It doesn't exist, so it must have all mandatory fields.
            mService.validateShortcutForPinRequest(inShortcut);

            // Initialize the ShortcutInfo for pending approval.
            inShortcut.resolveResourceStrings(mService.injectGetResourcesForApplicationAsUser(
                    inShortcut.getPackage(), inShortcut.getUserId()));

            // We should strip out the intent, but should preserve the icon.
            shortcutForLauncher = inShortcut.clone(
                    ShortcutInfo.CLONE_REMOVE_FOR_LAUNCHER_APPROVAL);
        }

        // Create a request object.
        final PinShortcutRequestInner inner =
                new PinShortcutRequestInner(this, inShortcut, shortcutForLauncher,
                        resultIntentToSend, launcherPackage, launcherUserId,
                        mService.injectGetPackageUid(launcherPackage, launcherUserId),
                        existsAlready);

        return new PinItemRequest(inner, PinItemRequest.REQUEST_TYPE_SHORTCUT);
    }

4.Launcher.java

4.1.handleActivityResult

  • onActivityResult回调里会调用
    private void handleActivityResult(
            final int requestCode, final int resultCode, final Intent data) {
        if (isWorkspaceLoading()) {
            //workspace还在加载数据中,等完成再处理
            mPendingActivityResult = new ActivityResultInfo(requestCode, resultCode, data);
            return;
        }
        mPendingActivityResult = null;

        // Reset the startActivity waiting flag
        final PendingRequestArgs requestArgs = mPendingRequestArgs;
        setWaitingForResult(null);
        if (requestArgs == null) {
            return;
        }

        final int pendingAddWidgetId = requestArgs.getWidgetId();

        Runnable exitSpringLoaded = new Runnable() {
            @Override
            public void run() {
                mStateManager.goToState(NORMAL, SPRING_LOADED_EXIT_DELAY);
            }
        };

        if (requestCode == REQUEST_BIND_APPWIDGET) {
            // This is called only if the user did not previously have permissions to bind widgets
            final int appWidgetId = data != null ?
                    data.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, -1) : -1;
            if (resultCode == RESULT_CANCELED) {
                completeTwoStageWidgetDrop(RESULT_CANCELED, appWidgetId, requestArgs);
                mWorkspace.removeExtraEmptyScreenDelayed(
                        ON_ACTIVITY_RESULT_ANIMATION_DELAY, false, exitSpringLoaded);
            } else if (resultCode == RESULT_OK) {
                addAppWidgetImpl(
                        appWidgetId, requestArgs, null,
                        requestArgs.getWidgetHandler(),
                        ON_ACTIVITY_RESULT_ANIMATION_DELAY);
            }
            return;
        }

        boolean isWidgetDrop = (requestCode == REQUEST_PICK_APPWIDGET ||
                requestCode == REQUEST_CREATE_APPWIDGET);

        // We have special handling for widgets
        if (isWidgetDrop) {
            final int appWidgetId;
            int widgetId = data != null ? data.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, -1)
                    : -1;
            if (widgetId < 0) {
                appWidgetId = pendingAddWidgetId;
            } else {
                appWidgetId = widgetId;
            }

            final int result;
            if (appWidgetId < 0 || resultCode == RESULT_CANCELED) {
                Log.e(TAG, "Error: appWidgetId (EXTRA_APPWIDGET_ID) was not " +
                        "returned from the widget configuration activity.");
                result = RESULT_CANCELED;
                completeTwoStageWidgetDrop(result, appWidgetId, requestArgs);
                mWorkspace.removeExtraEmptyScreenDelayed(
                        ON_ACTIVITY_RESULT_ANIMATION_DELAY, false,
                        () -> getStateManager().goToState(NORMAL));
            } else {
                CellPos presenterPos = getCellPosMapper().mapModelToPresenter(requestArgs);
                if (requestArgs.container == CONTAINER_DESKTOP) {
                    // When the screen id represents an actual screen (as opposed to a rank)
                    // we make sure that the drop page actually exists.
                    int newScreenId = ensurePendingDropLayoutExists(presenterPos.screenId);
                    requestArgs.screenId = getCellPosMapper().mapPresenterToModel(
                            presenterPos.cellX, presenterPos.cellY, newScreenId, CONTAINER_DESKTOP)
                                    .screenId;
                }
                final CellLayout dropLayout =
                        mWorkspace.getScreenWithId(presenterPos.screenId);

                dropLayout.setDropPending(true);
                final Runnable onComplete = new Runnable() {
                    @Override
                    public void run() {
                        completeTwoStageWidgetDrop(resultCode, appWidgetId, requestArgs);
                        dropLayout.setDropPending(false);
                    }
                };
                mWorkspace.removeExtraEmptyScreenDelayed(
                        ON_ACTIVITY_RESULT_ANIMATION_DELAY, false, onComplete);
            }
            return;
        }

        if (requestCode == REQUEST_RECONFIGURE_APPWIDGET
                || requestCode == REQUEST_BIND_PENDING_APPWIDGET) {
            if (resultCode == RESULT_OK) {
                // Update the widget view.
                completeAdd(requestCode, data, pendingAddWidgetId, requestArgs);
            }
            // Leave the widget in the pending state if the user canceled the configure.
            return;
        }
//打印了下,小节3的页面点击一个选项后,会走这里
        if (requestCode == REQUEST_CREATE_SHORTCUT) {
            // Handle custom shortcuts created using ACTION_CREATE_SHORTCUT.
            if (resultCode == RESULT_OK && requestArgs.container != ItemInfo.NO_ID) {
                completeAdd(requestCode, data, -1, requestArgs);//补充1,处理数据
                mWorkspace.removeExtraEmptyScreenDelayed(
                        ON_ACTIVITY_RESULT_ANIMATION_DELAY, false, exitSpringLoaded);

            } else if (resultCode == RESULT_CANCELED) {
                mWorkspace.removeExtraEmptyScreenDelayed(
                        ON_ACTIVITY_RESULT_ANIMATION_DELAY, false, exitSpringLoaded);
            }
        }

        mDragLayer.clearAnimatedView();
    }

>1.completeAdd

    private int completeAdd(
            int requestCode, Intent intent, int appWidgetId, PendingRequestArgs info) {
        CellPos cellPos = getCellPosMapper().mapModelToPresenter(info);
        int screenId = cellPos.screenId;
        if (info.container == CONTAINER_DESKTOP) {
            // When the screen id represents an actual screen (as opposed to a rank) we make sure
            // that the drop page actually exists.
            screenId = ensurePendingDropLayoutExists(cellPos.screenId);
        }

        switch (requestCode) {
            case REQUEST_CREATE_SHORTCUT:
            //参考4.2
                completeAddShortcut(intent, info.container, screenId,
                        cellPos.cellX, cellPos.cellY, info);
                announceForAccessibility(R.string.item_added_to_workspace);
                break;
            case REQUEST_CREATE_APPWIDGET:
                completeAddAppWidget(appWidgetId, info, null, null);
                break;
            case REQUEST_RECONFIGURE_APPWIDGET:
                getStatsLogManager().logger().withItemInfo(info).log(LAUNCHER_WIDGET_RECONFIGURED);
                completeRestoreAppWidget(appWidgetId, LauncherAppWidgetInfo.RESTORE_COMPLETED);
                break;
            case REQUEST_BIND_PENDING_APPWIDGET: {
                int widgetId = appWidgetId;
                LauncherAppWidgetInfo widgetInfo =
                        completeRestoreAppWidget(widgetId, LauncherAppWidgetInfo.FLAG_UI_NOT_READY);
                if (widgetInfo != null) {
                    // Since the view was just bound, also launch the configure activity if needed
                    LauncherAppWidgetProviderInfo provider = mAppWidgetManager
                            .getLauncherAppWidgetInfo(widgetId);
                    if (provider != null) {
                        new WidgetAddFlowHandler(provider)
                                .startConfigActivity(this, widgetInfo,
                                        REQUEST_RECONFIGURE_APPWIDGET);
                    }
                }
                break;
            }
        }
        return screenId;
    }

4.2.completeAddShortcut

    protected void completeAddShortcut(Intent data, int container, int screenId, int cellX,
            int cellY, PendingRequestArgs args) {
        if (args.getRequestCode() != REQUEST_CREATE_SHORTCUT
                || args.getPendingIntent().getComponent() == null) {
            return;
        }

        int[] cellXY = mTmpAddItemCellCoordinates;
        CellLayout layout = getCellLayout(container, screenId);
//参考补充2
        WorkspaceItemInfo info = PinRequestHelper.createWorkspaceItemFromPinItemRequest(
                    this, PinRequestHelper.getPinItemRequest(data), 0);//补充1

//后边就是添加数据
    }

>1.getPinItemRequest

    public static PinItemRequest getPinItemRequest(Intent intent) {
        Parcelable extra = intent.getParcelableExtra(LauncherApps.EXTRA_PIN_ITEM_REQUEST);
        return extra instanceof PinItemRequest ? (PinItemRequest) extra : null;
    }

>2.createWorkspaceItemFromPinItemRequest

获取返回的数据

    public static WorkspaceItemInfo createWorkspaceItemFromPinItemRequest(
            Context context, final PinItemRequest request, final long acceptDelay) {
            //类型是3.4.3构造方法里设置的
        if (request != null && request.getRequestType() == PinItemRequest.REQUEST_TYPE_SHORTCUT
                && request.isValid()) {

//...解析返回的数据,来源参考3.3.1
            ShortcutInfo si = request.getShortcutInfo();
            WorkspaceItemInfo info = new WorkspaceItemInfo(si, context);
            // Apply the unbadged icon synchronously using the caching logic directly and
            // fetch the actual icon asynchronously.
            info.bitmap = new ShortcutCachingLogic().loadIcon(context, si);
            //绑定数据
            LauncherAppState.getInstance(context).getModel().updateAndBindWorkspaceItem(info, si);
            return info;
        } else {
            return null;
        }
    }

4.3.addPendingItem

这个就是拖动结束会走,参考5.7.2

    public void addPendingItem(PendingAddItemInfo info, int container, int screenId,
            int[] cell, int spanX, int spanY) {
//..

        switch (info.itemType) {
            case LauncherSettings.Favorites.ITEM_TYPE_CUSTOM_APPWIDGET:
            case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET:
                addAppWidgetFromDrop((PendingAddWidgetInfo) info);
                break;
            case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
                processShortcutFromDrop((PendingAddShortcutInfo) info);//补充1
                break;
            default:
                throw new IllegalStateException("Unknown item type: " + info.itemType);
        }
    }

>1.processShortcutFromDrop

    private void processShortcutFromDrop(PendingAddShortcutInfo info) {
        Intent intent = new Intent(Intent.ACTION_CREATE_SHORTCUT).setComponent(info.componentName);
        //设置等待结果变量
        setWaitingForResult(PendingRequestArgs.forIntent(REQUEST_CREATE_SHORTCUT, intent, info));
//参考补充2,看是否可以正常跳转到快捷方式配置页面
        if (!info.getActivityInfo(this).startConfigActivity(this, REQUEST_CREATE_SHORTCUT)) {
            handleActivityResult(REQUEST_CREATE_SHORTCUT, RESULT_CANCELED, null);
        }
    }

>2.startConfigActivity

反正最终就是startXXXForResult跳转页面

    public static class ShortcutConfigActivityInfoVO extends ShortcutConfigActivityInfo {
    //...
        public boolean startConfigActivity(Activity activity, int requestCode) {
            if (getUser().equals(Process.myUserHandle())) {
                return super.startConfigActivity(activity, requestCode);//参考父类
            }
            IntentSender is = activity.getSystemService(LauncherApps.class)
                    .getShortcutConfigActivityIntent(mInfo);
            try {
                activity.startIntentSenderForResult(is, requestCode, null, 0, 0, 0);
                return true;
            } catch (IntentSender.SendIntentException e) {
                Toast.makeText(activity, R.string.activity_not_found, Toast.LENGTH_SHORT).show();
                return false;
            }
        }

父类,回调参考4.1

    public boolean startConfigActivity(Activity activity, int requestCode) {
        Intent intent = new Intent(Intent.ACTION_CREATE_SHORTCUT)
                .setComponent(getComponent());
        try {
            activity.startActivityForResult(intent, requestCode);
            return true;
        }
        return false;
    }

4.4.小部件添加的方式

对应的组件是WidgetsBottomSheet

>1.方式1

  • 长按应用图标,点击widgets,底部弹出一个小部件选择的页面,选择一个长按拖动到桌面

>2.方式2

  • 长按桌面空白处,弹框选择widgets,找到某个应用,比如settings,选择一个长按拖动到桌面

5.WidgetsBottomSheet.java

5.1.populateAndShow

小部件底部弹框显示

    public void populateAndShow(ItemInfo itemInfo) {
        mOriginalItemInfo = itemInfo;
        ((TextView) findViewById(R.id.title)).setText(mOriginalItemInfo.title);

        onWidgetsBound();//补充1
        attachToContainer();
        mIsOpen = false;
        animateOpen();
    }

>1.onWidgetsBound

    public void onWidgetsBound() {
    //获取widget数据
        List<WidgetItem> widgets = mActivityContext.getPopupDataProvider().getWidgetsForPackageUser(
                new PackageUserKey(
                        mOriginalItemInfo.getTargetComponent().getPackageName(),
                        mOriginalItemInfo.user));

        TableLayout widgetsTable = findViewById(R.id.widgets_table);
        widgetsTable.removeAllViews();
//这个就是进行了分组处理
        WidgetsTableUtils.groupWidgetItemsUsingRowPxWithReordering(widgets, mActivityContext,
                mActivityContext.getDeviceProfile(), mMaxHorizontalSpan,
                mWidgetCellHorizontalPadding)
                .forEach(row -> {
                    TableRow tableRow = new TableRow(getContext());
                    tableRow.setGravity(Gravity.TOP);
                    row.forEach(widgetItem -> {
                        WidgetCell widget = addItemCell(tableRow);
                        //应用数据,参考5.2.1
                        widget.applyFromCellItem(widgetItem);
                    });
                    widgetsTable.addView(tableRow);
                });
    }

5.2.WidgetCell.java

>1.applyFromCellItem

   public void applyFromCellItem(WidgetItem item) {
        applyFromCellItem(item, 1f);
    }


    public void applyFromCellItem(WidgetItem item, float previewScale) {
        applyFromCellItem(item, previewScale, this::applyPreview, null);
    }


    public void applyFromCellItem(WidgetItem item, float previewScale,
            @NonNull Consumer<Bitmap> callback, @Nullable Bitmap cachedPreview) {
        // setPreviewSize
        DeviceProfile deviceProfile = mActivity.getDeviceProfile();
        Size widgetSize = WidgetSizes.getWidgetItemSizePx(getContext(), deviceProfile, item);
        mTargetPreviewWidth = widgetSize.getWidth();
        mTargetPreviewHeight = widgetSize.getHeight();
        mPreviewContainerScale = previewScale;

        applyPreviewOnAppWidgetHostView(item);

        Context context = getContext();
        mItem = item;
        mWidgetName.setText(mItem.label);
        mWidgetName.setContentDescription(
                context.getString(R.string.widget_preview_context_description, mItem.label));
        mWidgetDims.setText(context.getString(R.string.widget_dims_format,
                mItem.spanX, mItem.spanY));
        mWidgetDims.setContentDescription(context.getString(
                R.string.widget_accessible_dims_format, mItem.spanX, mItem.spanY));
        if (ATLEAST_S && mItem.widgetInfo != null) {
            CharSequence description = mItem.widgetInfo.loadDescription(context);
            if (description != null && description.length() > 0) {
                mWidgetDescription.setText(description);
                mWidgetDescription.setVisibility(VISIBLE);
            } else {
                mWidgetDescription.setVisibility(GONE);
            }
        }
//有activity的是shortcut,没有的是widget
//参考2.1数据来源,以及2.4的构造方法
        if (item.activityInfo != null) {
            setTag(new PendingAddShortcutInfo(item.activityInfo));
        } else {
            setTag(new PendingAddWidgetInfo(item.widgetInfo, mSourceContainer));
        }

        ensurePreviewWithCallback(callback, cachedPreview);
    }

5.3.父类BaseWidgetSheet

>1.onClick

点击的话就是弹个提示

    public final void onClick(View v) {
        Object tag = null;
        if (v instanceof WidgetCell) {
            tag = v.getTag();
        } else if (v.getParent() instanceof WidgetCell) {
            tag = ((WidgetCell) v.getParent()).getTag();
        }
        if (tag instanceof PendingAddShortcutInfo) {
            mWidgetInstructionToast = showShortcutToast(getContext(), mWidgetInstructionToast);
        } else {
            mWidgetInstructionToast = showWidgetToast(getContext(), mWidgetInstructionToast);
        }

    }

>2.onLongClick

长按开始拖拽

    public boolean onLongClick(View v) {
        v.cancelLongPress();
        if (!ItemLongClickListener.canStartDrag(mActivityContext)) return false;

        if (v instanceof WidgetCell) {//参考补充3
            return beginDraggingWidget((WidgetCell) v);
        } else if (v.getParent() instanceof WidgetCell) {
            return beginDraggingWidget((WidgetCell) v.getParent());
        }
        return true;
    }

>3.beginDraggingWidget

    private boolean beginDraggingWidget(WidgetCell v) {

        WidgetImageView image = v.getWidgetView();

        //图片和预览都为空,中断
        if (image.getDrawable() == null && v.getAppWidgetHostViewPreview() == null) {
            return false;
        }
//交给helper类处理了,参考5.4
        PendingItemDragHelper dragHelper = new PendingItemDragHelper(v);

        dragHelper.setRemoteViewsPreview(v.getRemoteViewsPreview(), v.getAppWidgetHostViewScale());
        dragHelper.setAppWidgetHostViewPreview(v.getAppWidgetHostViewPreview());

        if (image.getDrawable() != null) {
            int[] loc = new int[2];
            getPopupContainer().getLocationInDragLayer(image, loc);
//开始拖动,参考5.4.2
            dragHelper.startDrag(image.getBitmapBounds(), image.getDrawable().getIntrinsicWidth(),
                    image.getWidth(), new Point(loc[0], loc[1]), this, new DragOptions());
        } else {
            NavigableAppWidgetHostView preview = v.getAppWidgetHostViewPreview();
            int[] loc = new int[2];
            getPopupContainer().getLocationInDragLayer(preview, loc);
            Rect r = new Rect();
            preview.getWorkspaceVisualDragBounds(r);
            dragHelper.startDrag(r, preview.getMeasuredWidth(), preview.getMeasuredWidth(),
                    new Point(loc[0], loc[1]), this, new DragOptions());
        }
        close(true);
        return true;
    }

5.4.PendingItemDragHelper

>1.构造方法

    public PendingItemDragHelper(View view) {
        super(view);
        //通过tag获取数据,参考5.2.1设置数据
        mAddInfo = (PendingAddItemInfo) view.getTag();
        mEnforcedRoundedCornersForWidget = RoundedCornerEnforcement.computeEnforcedRadius(
                view.getContext());
    }

>2.startDrag

  • launcher用的controller是LauncherDragController.java
    public void startDrag(Rect previewBounds, int previewBitmapWidth, int previewViewWidth,
            Point screenPos, DragSource source, DragOptions options) {
            
            //....
        // Start the drag
        if (mAppWidgetHostViewPreview != null) {
            launcher.getDragController().startDrag(mAppWidgetHostViewPreview, draggableView,
                    dragLayerX, dragLayerY, source, mAddInfo, dragOffset, dragRegion, scale, scale,
                    options);
        } else {
            launcher.getDragController().startDrag(preview, draggableView, dragLayerX, dragLayerY,
                    source, mAddInfo, dragOffset, dragRegion, scale, scale, options);
        }
    }

5.5.DragController.java

>1.startDrag

LauncherDragController.java

    protected DragView startDrag(
//....
        mDragDriver = DragDriver.create(this, mOptions, mFlingToDeleteHelper::recordMotionEvent);
        

>2.onDragEvent

jiaogei

    public boolean onDragEvent(DragEvent event) {
        return mDragDriver != null && mDragDriver.onDragEvent(event);
    }

>3.onDriverDragEnd

    public void onDriverDragEnd(float x, float y) {
        if (!endWithFlingAnimation()) {
        //参考补充4
            drop(findDropTarget((int) x, (int) y, mCoordinatesTemp), null);
        }
        endDrag();
    }

>4.drop

    protected void drop(DropTarget dropTarget, Runnable flingAnimation) {
        final int[] coordinates = mCoordinatesTemp;
        mDragObject.x = coordinates[0];
        mDragObject.y = coordinates[1];

        // Move dragging to the final target.
        if (dropTarget != mLastDropTarget) {
            if (mLastDropTarget != null) {
                mLastDropTarget.onDragExit(mDragObject);
            }
            mLastDropTarget = dropTarget;
            if (dropTarget != null) {
                dropTarget.onDragEnter(mDragObject);
            }
        }

        mDragObject.dragComplete = true;
        if (mIsInPreDrag) {
            if (dropTarget != null) {
                dropTarget.onDragExit(mDragObject);
            }
            return;
        }

        // Drop onto the target.
        boolean accepted = false;
        if (dropTarget != null) {
            dropTarget.onDragExit(mDragObject);
            if (dropTarget.acceptDrop(mDragObject)) {
                if (flingAnimation != null) {
                    flingAnimation.run();
                } else {//参考5.7
                    dropTarget.onDrop(mDragObject, mOptions);
                }
                accepted = true;
            }
        }
        final View dropTargetAsView = dropTarget instanceof View ? (View) dropTarget : null;
        dispatchDropComplete(dropTargetAsView, accepted);
    }

5.6.DragDriver

###> 1.create

    public static DragDriver create(DragController dragController, DragOptions options,
            Consumer<MotionEvent> sec) {
        if (options.simulatedDndStartPoint != null) {
            if  (options.isAccessibleDrag) {
                return null;
            }
            return new SystemDragDriver(dragController, sec);
        } else {
            return new InternalDragDriver(dragController, sec);
        }
    }

>2.InternalDragDriver

mEventListener就是DragController

    static class InternalDragDriver extends DragDriver {
        private final DragController mDragController;

        InternalDragDriver(DragController dragController, Consumer<MotionEvent> sec) {
            super(dragController, sec);
            mDragController = dragController;
        }

        @Override
        public boolean onTouchEvent(MotionEvent ev) {
            mSecondaryEventConsumer.accept(ev);
            final int action = ev.getAction();

            switch (action) {
                case MotionEvent.ACTION_MOVE:
                    mEventListener.onDriverDragMove(mDragController.getX(ev),
                            mDragController.getY(ev));
                    break;
                case MotionEvent.ACTION_UP:
                    mEventListener.onDriverDragMove(mDragController.getX(ev),
                            mDragController.getY(ev));
                            //参考5.5.3
                    mEventListener.onDriverDragEnd(mDragController.getX(ev),
                            mDragController.getY(ev));
                    break;
                case MotionEvent.ACTION_CANCEL:
                    mEventListener.onDriverDragCancel();
                    break;
            }

            return true;
        }

5.7.Workspace.java

public class Workspace<T extends View & PageIndicator> extends PagedView<T>
        implements DropTarget, DragSource, View.OnTouchListener,
        DragController.DragListener, Insettable, StateHandler<LauncherState>,
        WorkspaceLayoutManager, LauncherBindableItemsContainer, LauncherOverlayCallbacks {

>1.onDrop

    public void onDrop(final DragObject d, DragOptions options) {
//....
        if (d.dragSource != this || mDragInfo == null) {
            final int[] touchXY = new int[]{(int) mDragViewVisualCenter[0],
                    (int) mDragViewVisualCenter[1]};
                    //补充2
            onDropExternal(touchXY, dropTargetLayout, d);
        } else {

>2.onDropExternal

    private void onDropExternal(final int[] touchXY, final CellLayout cellLayout, DragObject d) {
    //...
    
        if (info instanceof PendingAddItemInfo) {
            final PendingAddItemInfo pendingInfo = (PendingAddItemInfo) info;
//..
            Runnable onAnimationCompleteRunnable = new Runnable() {
                @Override
                public void run() {
                    // Normally removeExtraEmptyScreen is called in Workspace#onDrop, but when
                    // adding an item that may not be dropped right away (due to a config activity)
                    // we defer the removal until the activity returns.
                    deferRemoveExtraEmptyScreen();

                    //参考4.3
                    mLauncher.addPendingItem(pendingInfo, container, screenId, mTargetCell,
                            item.spanX, item.spanY);
                }
            };
//..
            animateWidgetDrop(info, cellLayout, d.dragView, onAnimationCompleteRunnable,
                    animationStyle, finalView, true);