The thing about android Launcher (2)

    At the request of a friend, I will explain the Launcher application in detail.

    First of all, we need to go to LauncherApplication to have a look, because there are no two member variables that are very important to us, they are

    public LauncherModel mModel;
    public IconCache mIconCache;

Created in LauncherApplication's onCreate()

        mIconCache = new IconCache(this);
        mModel = new LauncherModel(this, mIconCache);

IconCache is obviously an application Icon cache. We will talk about this later. LauncherModel is a BroadcastReceiver, which is used to accept broadcasts of application addition, deletion, changes, etc., to perform responsive operations. Let's go to the constructor of LauncherModel to see

    LauncherModel(LauncherApplication app, IconCache iconCache) {
        mAppsCanBeOnExternalStorage = !Environment.isExternalStorageEmulated();
        mApp = app;
        mAllAppsList = new AllAppsList(iconCache);
        mIconCache = iconCache;

        mDefaultIcon = Utilities.createIconBitmap(
                mIconCache.getFullResDefaultActivityIcon(), app);

        final Resources res = app.getResources();
        mAllAppsLoadDelay = res.getInteger(R.integer.config_allAppsBatchLoadDelay);
        mBatchSize = res.getInteger(R.integer.config_allAppsBatchSize);
        Configuration config = res.getConfiguration();
        mPreviousConfigMcc = config.mcc;
    }

The mAllAppsList here is AllAppsList, which is used to store our application information. It is very important. Half of our Launcher operations are carried out around it. The important variables in the AllAppsList class

    /** The list off all apps. */
    public ArrayList<ApplicationInfo> data =
            new ArrayList<ApplicationInfo>(DEFAULT_APPLICATIONS_NUMBER);
    /** The list of apps that have been added since the last notify() call. */
    public ArrayList<ApplicationInfo> added =
            new ArrayList<ApplicationInfo>(DEFAULT_APPLICATIONS_NUMBER);
    /** The list of apps that have been removed since the last notify() call. */
    public ArrayList<ApplicationInfo> removed = new ArrayList<ApplicationInfo>();
    /** The list of apps that have been modified since the last notify() call. */
    public ArrayList<ApplicationInfo> modified = new ArrayList<ApplicationInfo>();

    private IconCache mIconCache;

From the name, you can know what these are used to store. I won’t explain them one by one here. Let’s see what information about the application is stored in ApplicationInfo.

    /**
     * The application name.
     */
    CharSequence title;

    /**
     * The intent used to start the application.
     */
    Intent intent;

    /**
     * A bitmap version of the application icon.
     */
    Bitmap iconBitmap;

    /**
     * The time at which the app was first installed.
     */
    long firstInstallTime;

    ComponentName componentName;

The name title, the started Intent, iconBitmap, the first installation time firstInstallTime, ComponentName, etc., what do these specifically do, I don’t need to explain them one by one.

    Well, we can see here the LauncherApplication, and then we enter the Launcher, the Launcher is an Activity, so we first look at its onCreate function, we only intercept the code we care about

        LauncherApplication app = ((LauncherApplication)getApplication());
        mModel = app.setLauncher(this);

        mIconCache = app.getIconCache();
        mDragController = new DragController(this);

Here we get the LauncherApplication we just created, then get the LauncherModel we just created and pass our Launcher in, then get the IconCache we just created, and finally create DragController, which is what we use when dragging icons, which will be later explained to. Since the management of Widget is similar to that of app, we won't explain here and only talk about app. then

        if (!mRestoring) {
            mModel.startLoader(this, true);
        }

to load our application

    public void startLoader(Context context, boolean isLaunching) {
        synchronized (mLock) {
            if (DEBUG_LOADERS) {
                Log.d(TAG, "startLoader isLaunching=" + isLaunching);
            }

            // Don't bother to start the thread if we know it's not going to do anything
            if (mCallbacks != null && mCallbacks.get() != null) {
                // If there is already one running, tell it to stop.
                // also, don't downgrade isLaunching if we're already running
                isLaunching = isLaunching || stopLoaderLocked();
                mLoaderTask = new LoaderTask(context, isLaunching);
                sWorkerThread.setPriority(Thread.NORM_PRIORITY);
                sWorker.post(mLoaderTask);
            }
        }
    }

Start a thread LoaderTask to load our application, we go directly to the run() method of LoaderTask to check

            keep_running: {
                // Elevate priority when Home launches for the first time to avoid
                // starving at boot time. Staring at a blank home is not cool.
                synchronized (mLock) {
                    if (DEBUG_LOADERS) Log.d(TAG, "Setting thread priority to " +
                            (mIsLaunching ? "DEFAULT" : "BACKGROUND"));
                    android.os.Process.setThreadPriority(mIsLaunching
                            ? Process.THREAD_PRIORITY_DEFAULT : Process.THREAD_PRIORITY_BACKGROUND);
                }
                if (loadWorkspaceFirst) {
                    if (DEBUG_LOADERS) Log.d(TAG, "step 1: loading workspace");
                    loadAndBindWorkspace();
                } else {
                    if (DEBUG_LOADERS) Log.d(TAG, "step 1: special: loading all apps");
                    loadAndBindAllApps();
                }

                if (mStopped) {
                    break keep_running;
                }

                // Whew! Hard work done.  Slow us down, and wait until the UI thread has
                // settled down.
                synchronized (mLock) {
                    if (mIsLaunching) {
                        if (DEBUG_LOADERS) Log.d(TAG, "Setting thread priority to BACKGROUND");
                        android.os.Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
                    }
                }
                waitForIdle();

                // second step
                if (loadWorkspaceFirst) {
                    if (DEBUG_LOADERS) Log.d(TAG, "step 2: loading all apps");
                    loadAndBindAllApps();
                } else {
                    if (DEBUG_LOADERS) Log.d(TAG, "step 2: special: loading workspace");
                    loadAndBindWorkspace();
                }

                // Restore the default thread priority after we are done loading items
                synchronized (mLock) {
                    android.os.Process.setThreadPriority(Process.THREAD_PRIORITY_DEFAULT);
                }
            }

Here it is decided whether to load Apps or Workspace first. We only explain Apps, that is, loadAndBindAllApps()

        private void loadAndBindAllApps() {
            if (DEBUG_LOADERS) {
                Log.d(TAG, "loadAndBindAllApps mAllAppsLoaded=" + mAllAppsLoaded);
            }
            if (!mAllAppsLoaded) {
            	loadAllAppsFromPersistence();
                loadAllAppsByBatch();
                synchronized (LoaderTask.this) {
                    if (mStopped) {
                        return;
                    }
                    mAllAppsLoaded = true;
                }
            } else {
                onlyBindAllApps();
            }
        }

loadAllAppsFromPersistence() loads applications from the database. Due to different codes, some may not have this process. loadAllAppsByBatch() parses the application to load the application, and onlyBindAllApps() skips the process of updating the application list. It is part of the code of loadAllAppsByBatch(). No more explanation, here we only explain loadAllAppsByBatch()

        private void loadAllAppsByBatch() {
            final long t = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0;

            // Don't use these two variables in any of the callback runnables.
            // Otherwise we hold a reference to them.
            final Callbacks oldCallbacks = mCallbacks.get();
            if (oldCallbacks == null) {
                // This launcher has exited and nobody bothered to tell us.  Just bail.
                Log.w(TAG, "LoaderTask running with no launcher (loadAllAppsByBatch)");
                return;
            }

            final Intent mainIntent = new Intent(Intent.ACTION_MAIN, null);
            mainIntent.addCategory(Intent.CATEGORY_LAUNCHER);

            final PackageManager packageManager = mContext.getPackageManager();
            List<ResolveInfo> apps = null;

            int N = Integer.MAX_VALUE;

            int startIndex;
            int i=0;
            int batchSize = -1;
            while (i < N && !mStopped) {
                if (i == 0) {
                    mAllAppsList.clear();
                    final long qiaTime = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0;
                    apps = packageManager.queryIntentActivities(mainIntent, 0);
//                    for (ResolveInfo resolveInfo : apps) {
//                    	ActivityInfo activityInfo = resolveInfo.activityInfo;
//                    	if(activityInfo == null){
//                        	Log.v("Launcher", "loadAllAppsByBatch resolvePackageName:" + resolveInfo.resolvePackageName);
//                    	}else{
//                        	Log.v("Launcher", "loadAllAppsByBatch activityInfo:" + activityInfo.loadLabel(packageManager).toString());
//                    	}
//            		}
                    if (DEBUG_LOADERS) {
                        Log.d(TAG, "queryIntentActivities took "
                                + (SystemClock.uptimeMillis()-qiaTime) + "ms");
                    }
                    if (apps == null) {
                        return;
                    }
                    N = apps.size();
                    if (DEBUG_LOADERS) {
                        Log.d(TAG, "queryIntentActivities got " + N + " apps");
                    }
                    if (N == 0) {
                        // There are no apps?!?
                        return;
                    }
                    if (mBatchSize == 0) {
                        batchSize = N;
                    } else {
                        batchSize = mBatchSize;
                    }

                    final long sortTime = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0;
                    Collections.sort(apps,
                            new LauncherModel.ShortcutNameComparator(packageManager, mLabelCache));
                    if (DEBUG_LOADERS) {
                        Log.d(TAG, "sort took "
                                + (SystemClock.uptimeMillis()-sortTime) + "ms");
                    }
                }

                final long t2 = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0;

                startIndex = i;
                for (int j=0; i<N && j<batchSize; j++) {
                    // This builds the icon bitmaps.
                    mAllAppsList.add(new ApplicationInfo(packageManager, apps.get(i),
                            mIconCache, mLabelCache));
                    i++;
                }

                final boolean first = i <= batchSize;
                final Callbacks callbacks = tryGetCallbacks(oldCallbacks);
                final ArrayList<ApplicationInfo> added = mAllAppsList.added;
                mAllAppsList.added = new ArrayList<ApplicationInfo>();

                mHandler.post(new Runnable() {
                    public void run() {
                        final long t = SystemClock.uptimeMillis();
                        if (callbacks != null) {
                        	isNeedSave = true;
                            if (first) {
                                callbacks.bindAllApplications(added);
                            } else {
                                callbacks.bindAppsAdded(added);
                            }
                            if (DEBUG_LOADERS) {
                                Log.d(TAG, "bound " + added.size() + " apps in "
                                    + (SystemClock.uptimeMillis() - t) + "ms");
                            }
                        } else {
                            Log.i(TAG, "not binding apps: no Launcher activity");
                        }
                    }
                });

                if (DEBUG_LOADERS) {
                    Log.d(TAG, "batch of " + (i-startIndex) + " icons processed in "
                            + (SystemClock.uptimeMillis()-t2) + "ms");
                }

                if (mAllAppsLoadDelay > 0 && i < N) {
                    try {
                        if (DEBUG_LOADERS) {
                            Log.d(TAG, "sleeping for " + mAllAppsLoadDelay + "ms");
                        }
                        Thread.sleep(mAllAppsLoadDelay);
                    } catch (InterruptedException exc) { }
                }
            }

            if (DEBUG_LOADERS) {
                Log.d(TAG, "cached all " + N + " apps in "
                        + (SystemClock.uptimeMillis()-t) + "ms"
                        + (mAllAppsLoadDelay > 0 ? " (including delay)" : ""));
            }
        }

First get all Apps whose Action is Intent.ACTION_MAIN and Category is Intent.CATEGORY_LAUNCHER

            final Intent mainIntent = new Intent(Intent.ACTION_MAIN, null);
            mainIntent.addCategory(Intent.CATEGORY_LAUNCHER);

            final PackageManager packageManager = mContext.getPackageManager();
            List<ResolveInfo> apps = null;

                    mAllAppsList.clear();
                    final long qiaTime = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0;
                    apps = packageManager.queryIntentActivities(mainIntent, 0);

And clear our mAllAppsList, this mAllAppsList we said before, so I won’t go into details here, and then

                for (int j=0; i<N && j<batchSize; j++) {
                    // This builds the icon bitmaps.
                    mAllAppsList.add(new ApplicationInfo(packageManager, apps.get(i),
                            mIconCache, mLabelCache));
                    i++;
                }

To load our application, we also mentioned ApplicationInfo before, but we only saw what information it stores in our application, here we go to see its constructor

    public ApplicationInfo(PackageManager pm, ResolveInfo info, IconCache iconCache,
            HashMap<Object, CharSequence> labelCache) {
        final String packageName = info.activityInfo.applicationInfo.packageName;

        this.componentName = new ComponentName(packageName, info.activityInfo.name);
        this.container = ItemInfo.NO_ID;
        this.setActivity(componentName,
                Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);

        try {
            int appFlags = pm.getApplicationInfo(packageName, 0).flags;
            if ((appFlags & android.content.pm.ApplicationInfo.FLAG_SYSTEM) == 0) {
                flags |= DOWNLOADED_FLAG;

                if ((appFlags & android.content.pm.ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0) {
                    flags |= UPDATED_SYSTEM_APP_FLAG;
                }
            }
            firstInstallTime = pm.getPackageInfo(packageName, 0).firstInstallTime;
        } catch (NameNotFoundException e) {
            Log.d(TAG, "PackageManager.getApplicationInfo failed for " + packageName);
        }

        iconCache.getTitleAndIcon(this, info, labelCache);
    }

First, assign values ​​to the componentName and container variables in ApplicationInfo, and then

        this.setActivity(componentName,
                Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);

that is

    final void setActivity(ComponentName className, int launchFlags) {
        intent = new Intent(Intent.ACTION_MAIN);
        intent.addCategory(Intent.CATEGORY_LAUNCHER);
        intent.setComponent(className);
        intent.setFlags(launchFlags);
        itemType = LauncherSettings.BaseLauncherColumns.ITEM_TYPE_APPLICATION;
    }

Assign a value to the intent of ApplicationInfo, here we can see that the corresponding application can be started through the intent. The next step is to assign values ​​​​to the flags and firstInstallTime of ApplicationInfo. These will not be explained in detail. Let’s take a look at iconCache.getTitleAndIcon(this, info, labelCache) in detail

    public void getTitleAndIcon(ApplicationInfo application, ResolveInfo info,
            HashMap<Object, CharSequence> labelCache) {
        synchronized (mCache) {
            CacheEntry entry = cacheLocked(application.componentName, info, labelCache);

            application.title = entry.title;
            application.iconBitmap = createBitmap(application.componentName, entry.icon,
                    application);
        }
    }

CacheEntry is

    private static class CacheEntry {
        public Bitmap icon;
        public String title;
    }

Only the app's icon and name are saved. cacheLocked(application.componentName, info, labelCache) is

    private CacheEntry cacheLocked(ComponentName componentName, ResolveInfo info,
            HashMap<Object, CharSequence> labelCache) {
        CacheEntry entry = mCache.get(componentName);
        if (entry == null) {
            entry = new CacheEntry();

            mCache.put(componentName, entry);

            ComponentName key = LauncherModel.getComponentNameFromResolveInfo(info);
            if (labelCache != null && labelCache.containsKey(key)) {
                entry.title = labelCache.get(key).toString();
            } else {
                entry.title = info.loadLabel(mPackageManager).toString();
                if (labelCache != null) {
                    labelCache.put(key, entry.title);
                }
            }
            if (entry.title == null) {
                entry.title = info.activityInfo.name;
            }

            entry.icon = Utilities.createIconBitmap(
                    getFullResIcon(info), mContext);
        }
        return entry;
    }

First get it from our cache, if there is one, return it directly, if not, get it. The same is true for obtaining the title, first obtain it from the cache, use it if it is available, and obtain it from the application information if not, here we can change the name displayed in the Launcher of the application, these are easy to understand, no need to explain too much. The next step is to get the Icon

            entry.icon = Utilities.createIconBitmap(
                    getFullResIcon(info), mContext);

Let's look at getFullResIcon that is

    public Drawable getFullResIcon(ResolveInfo info) {
        Resources resources;
        try {
            resources = mPackageManager.getResourcesForApplication(
                    info.activityInfo.applicationInfo);
        } catch (PackageManager.NameNotFoundException e) {
            resources = null;
        }
        if (resources != null) {
            int iconId = info.activityInfo.getIconResource();
            if (iconId != 0) {
                return getFullResIcon(resources, iconId);
            }
        }
        return getFullResDefaultActivityIcon();
    }

First get the resource information of our loaded application

            resources = mPackageManager.getResourcesForApplication(
                    info.activityInfo.applicationInfo);

If the resource acquisition is unsuccessful, return getFullResDefaultActivityIcon(), if the resource acquisition is successful

        if (resources != null) {
            int iconId = info.activityInfo.getIconResource();
            if (iconId != 0) {
                return getFullResIcon(resources, iconId);
            }
        }

Get the ID of the corresponding application icon, and then return getFullResIcon that is

    public Drawable getFullResIcon(Resources resources, int iconId) {
        Drawable d;
        try {
            d = resources.getDrawableForDensity(iconId, mIconDpi);
        } catch (Resources.NotFoundException e) {
            d = null;
        }

        return (d != null) ? d : getFullResDefaultActivityIcon();
    }

Get the picture Drawable through the ID of the icon and return it. If the picture is not obtained successfully, it will also return getFullResDefaultActivityIcon(), that is,

    public Drawable getFullResDefaultActivityIcon() {
        return getFullResIcon(Resources.getSystem(),
                com.android.internal.R.mipmap.sym_def_app_icon);
    }

This resource is a picture under our framework, which is the android villain we often see. This picture may be changed according to different codes. If you want to change the icon of the Launcher to what we want, you can do it in this part. For example, we can get a picture we prepared, and then just return. I won’t talk about it any more. Now go back to cacheLocked and see

            entry.icon = Utilities.createIconBitmap(
                    getFullResIcon(info), mContext);

This sentence, just now we analyzed getFullResIcon(info) returns a Drawable, now let's look at Utilities.createIconBitmap, Utilities.createIconBitmap is a refactoring function, one passes into Bitmap, one passes into Drawable, let's look at the function passed into Drawable

    static Bitmap createIconBitmap(Drawable icon, Context context) {
        synchronized (sCanvas) { // we share the statics :-(
            if (sIconWidth == -1) {
                initStatics(context);
            }

            int width = sIconWidth;
            int height = sIconHeight;

            if (icon instanceof PaintDrawable) {
                PaintDrawable painter = (PaintDrawable) icon;
                painter.setIntrinsicWidth(width);
                painter.setIntrinsicHeight(height);
            } else if (icon instanceof BitmapDrawable) {
                // Ensure the bitmap has a density.
                BitmapDrawable bitmapDrawable = (BitmapDrawable) icon;
                Bitmap bitmap = bitmapDrawable.getBitmap();
                if (bitmap.getDensity() == Bitmap.DENSITY_NONE) {
                    bitmapDrawable.setTargetDensity(context.getResources().getDisplayMetrics());
                }
            }
            int sourceWidth = icon.getIntrinsicWidth();
            int sourceHeight = icon.getIntrinsicHeight();

            if (sourceWidth > 0 && sourceHeight > 0) {
                // There are intrinsic sizes.
                if (width < sourceWidth || height < sourceHeight) {
                    // It's too big, scale it down.
                    final float ratio = (float) sourceWidth / sourceHeight;
                    if (sourceWidth > sourceHeight) {
                        height = (int) (width / ratio);
                    } else if (sourceHeight > sourceWidth) {
                        width = (int) (height * ratio);
                    }
                } else if (sourceWidth < width && sourceHeight < height) {
                    // Don't scale up the icon
                    width = sourceWidth;
                    height = sourceHeight;
                }
            }

            // no intrinsic size --> use default size
            int textureWidth = sIconTextureWidth;
            int textureHeight = sIconTextureHeight;

            final Bitmap bitmap = Bitmap.createBitmap(textureWidth, textureHeight,
                    Bitmap.Config.ARGB_8888);
            final Canvas canvas = sCanvas;
            canvas.setBitmap(bitmap);

            final int left = (textureWidth-width) / 2;
            final int top = (textureHeight-height) / 2;

            if (false) {
                // draw a big box for the icon for debugging
                canvas.drawColor(sColors[sColorIndex]);
                if (++sColorIndex >= sColors.length) sColorIndex = 0;
                Paint debugPaint = new Paint();
                debugPaint.setColor(0xffcccc00);
                canvas.drawRect(left, top, left+width, top+height, debugPaint);
            }

            sOldBounds.set(icon.getBounds());
            icon.setBounds(left, top, left+width, top+height);
            icon.draw(canvas);
            icon.setBounds(sOldBounds);
            canvas.setBitmap(null);

            return bitmap;
        }
    }

Obviously, the image size has been adjusted here. If you want to control the display of the Launcher icon, you can do it here. For example, you can add a background image or display size, so I won’t say more. Then we go back to getTitleAndIcon, and then look at

            application.title = entry.title;
            application.iconBitmap = createBitmap(application.componentName, entry.icon,
                    application);

It is to assign values ​​to the title and iconBitmap in our ApplicationInfo. Have a question here? Our icon picture is not ready, how to createBitmap here? let's go see

    public Bitmap createBitmap(ComponentName componentName, Bitmap bitmap,
            ApplicationInfo application) {
        if (!componentName.getPackageName().equals("com.android.mms")) {
            return bitmap;
        }
        //return the Bitmap with unRead Tip
        return MessageManager.getInstance(mContext).createMmsBitmap(bitmap, application);
    }

Now we suddenly realized, what is this doing? It is displaying the number of our unread text messages on the icon, understand. If we want to display the number of missed calls on the picture, we can do it here. As for how to get the unread text messages, the number of missed calls, and how to put the numbers on the picture, these are learning The necessary knowledge of android is not explained in detail. If you don't understand, you can follow this code to see it. At this point, we have completed the ApplicationInfo of an application. Then there is the problem of looping, let's go back to LoadAllAppsByBatch in LauncherModel and continue to watch

                for (int j=0; i<N && j<batchSize; j++) {
                    // This builds the icon bitmaps.
                    mAllAppsList.add(new ApplicationInfo(packageManager, apps.get(i),
                            mIconCache, mLabelCache));
                    i++;
                }

Just now we used a lot of space to explain the process of making an ApplicationInfo, I hope everyone can understand. Then we look at mAllAppsList.add that is

    public void add(ApplicationInfo info) {
        if (findActivity(data, info.componentName)) {
            return;
        }
        data.add(info);
        added.add(info);
    }

We gave the ApplicationInfo we made to the data and added of AllAppsList, which we said before, so I won’t say more here. Continue to look at the content behind loadAllAppsByBatch in LauncherModel

                final boolean first = i <= batchSize;
                final Callbacks callbacks = tryGetCallbacks(oldCallbacks);
                final ArrayList<ApplicationInfo> added = mAllAppsList.added;
                mAllAppsList.added = new ArrayList<ApplicationInfo>();

                mHandler.post(new Runnable() {
                    public void run() {
                        final long t = SystemClock.uptimeMillis();
                        if (callbacks != null) {
                        	isNeedSave = true;
                            if (first) {
                                callbacks.bindAllApplications(added);
                            } else {
                                callbacks.bindAppsAdded(added);
                            }
                            if (DEBUG_LOADERS) {
                                Log.d(TAG, "bound " + added.size() + " apps in "
                                    + (SystemClock.uptimeMillis() - t) + "ms");
                            }
                        } else {
                            Log.i(TAG, "not binding apps: no Launcher activity");
                        }
                    }
                });

Give our mAllAppsList.added to a newly applied ArrayList<ApplicationInfo>, which is added, and then re-apply our mAllAppsList.added and leave it empty. Finally, another thread is started to load and display our application, that is

                            if (first) {
                                callbacks.bindAllApplications(added);
                            } else {
                                callbacks.bindAppsAdded(added);
                            }

These two are almost the same, and they will eventually go the same way. We only look at one bindAllApplications, which is implemented in the Launcher.

    public void bindAllApplications(final ArrayList<ApplicationInfo> apps) {
        // Remove the progress bar entirely; we could also make it GONE
        // but better to remove it since we know it's not going to be used
        View progressBar = mAppsCustomizeTabHost.
            findViewById(R.id.apps_customize_progress_bar);
        if (progressBar != null) {
            ((ViewGroup)progressBar.getParent()).removeView(progressBar);
        }
        if (LOGD) Log.d(TAG, "bindAllApplications " + apps.toString());
        // We just post the call to setApps so the user sees the progress bar
        // disappear-- otherwise, it just looks like the progress bar froze
        // which doesn't look great
        mAppsCustomizeTabHost.post(new Runnable() {
            public void run() {
                if (mAppsCustomizeContent != null) {
                    mAppsCustomizeContent.setApps(apps);
                }
            }
        });
    }

There are two layouts mAppsCustomizeTabHost and mAppsCustomizeContent here. Let’s insert a ditty here. Let’s talk about the Launcher layout. We don’t start from the bottom layer. We start from the DragLayout layer. As the name implies, it is the drag layer. This layer contains AppsCustomizeTabHost, which is The main menu, the row of application icons at the bottom of Hotseat, Workspace is the standby interface and so on, let’s not talk about other unimportant ones. Then talk about Workspace, Workspace contains a lot of CellLayout, CellLayout is the page when we slide left and right in standby, CellLayout also contains CellLayoutChildren, CellLayoutChildren contains many LauncherAppWidgetHostView Next is our Widget, CellLayoutChildren also contains BubbleTextView, which is our App icon, This is the construction of Workspace. Then talk about the main menu, which is AppsCustomizeTabHost. There are many layers above AppsCustomizeTabHost. I will not explain it anymore. Go directly to the AppsCustomizePagedView layer. AppsCustomizePagedView contains many PagedViewCellLayout. PagedViewCellLayout is the page that we slide left and right on the main menu. PagedViewCellLayoutChildren contains many PagedViewIcon which is our application icon. In this way, we can understand the general layout of the Launcher. Ok, let's talk about mAppsCustomizeTabHost and mAppsCustomizeContent, these two are AppsCustomizeTabHost and AppsCustomizePagedView, the relationship between them can be understood through the above explanation, we want to load the application here, just go to AppsCus

    public void setApps(ArrayList<ApplicationInfo> list) {
        mApps = list;
        Collections.sort(mApps, LauncherModel.APP_NAME_COMPARATOR);
        updatePageCounts();

        // The next layout pass will trigger data-ready if both widgets and apps are set, so
        // request a layout to do this test and invalidate the page data when ready.
        LauncherModel.cacheAllApp(mContext, mApps);
        if (testDataReady()) requestLayout();
        invalidatePageData();
    }

First recalculate the number of our page pages

    private void updatePageCounts() {
        mNumWidgetPages = (int) Math.ceil(mWidgets.size() /
                (float) (mWidgetCountX * mWidgetCountY));
        mNumAppsPages = (int) Math.ceil((float) mApps.size() / (mCellCountX * mCellCountY));
    }

Then back up our application information to the database LauncherModel.cacheAllApp(mContext, mApps), which is the process of loading applications from the database as we said earlier. Because the codes are different, some codes do not have this part, so I will not explain it. The advantage is that the boot loads App icons are faster. Then we update our layout to display our application. And what is invalidatePageData()? That is, the Launcher page is full of icons, so add a new page to put the icons, and finally use requestLayout() to refresh the display from the new distribution layout. At this point our icon is displayed.

    Next, let's talk about how to start the application when the icon is clicked. This is divided into two, one is to click the main menu icon, and the other is to click the standby icon. Let me first say that clicking the standby icon is the Workspace icon, and this event response is in the Launcher, that is,

    public void onClick(View v) {
        // Make sure that rogue clicks don't get through while allapps is launching, or after the
        // view has detached (it's possible for this to happen if the view is removed mid touch).
        if (v.getWindowToken() == null) {
            return;
        }

        if (mWorkspace.isSwitchingState()) {
            return;
        }

        Object tag = v.getTag();
        if (tag instanceof ShortcutInfo) {
            // Open shortcut
            final Intent intent = ((ShortcutInfo) tag).intent;
            int[] pos = new int[2];
            v.getLocationOnScreen(pos);
            intent.setSourceBounds(new Rect(pos[0], pos[1],
                    pos[0] + v.getWidth(), pos[1] + v.getHeight()));
            boolean success = startActivitySafely(intent, tag);

            if (success && v instanceof BubbleTextView) {
                mWaitingForResume = (BubbleTextView) v;
                mWaitingForResume.setStayPressed(true);
            }
        } else if (tag instanceof FolderInfo) {
            if (v instanceof FolderIcon) {
                FolderIcon fi = (FolderIcon) v;
                handleFolderClick(fi);
            }
        } else if (v == mAllAppsButton) {
            if (mState == State.APPS_CUSTOMIZE) {
                showWorkspace(true);
            } else {
                onClickAllAppsButton(v);
            }
        }
    }

Let’s talk about ShortcutInfo first. The ShortcutInfo information is obtained from the ApplicationInfo information. As for how to obtain it, I won’t explain it here. Children’s shoes can study it by themselves. So the ((ShortcutInfo) tag).intent information here is very clear to everyone. It can start an application. As mentioned earlier, I will not explain it again. As for the FolderInfo below, what is done when you click on a folder, I won’t explain it here, and do my own research. Then look at how to start the application when the main menu is clicked, this part is in AppsCustomizePagedView

    public void onClick(View v) {
        // When we have exited all apps or are in transition, disregard clicks
        if (!mLauncher.isAllAppsCustomizeOpen() ||
                mLauncher.getWorkspace().isSwitchingState()) return;

        if (v instanceof PagedViewIcon) {
            // Animate some feedback to the click
            final ApplicationInfo appInfo = (ApplicationInfo) v.getTag();
            // bug 211336 begin
            //if (OptConfig.LC_RAM_SUPPORT) {
                // remove the animation when click
                mLauncher.startActivitySafely(appInfo.intent, appInfo);
            //} else {
            //    animateClickFeedback(v, new Runnable() {
            //        @Override
            //        public void run() {
            //            mLauncher.startActivitySafely(appInfo.intent, appInfo);
            //        }
            //    });
            //}
            // bug 211336 end
        } else if (v instanceof PagedViewWidget) {
            // Let the user know that they have to long press to add a widget
            Toast.makeText(getContext(), R.string.long_press_widget_to_add,
                    Toast.LENGTH_SHORT).show();

            // Create a little animation to show that the widget can move
            float offsetY = getResources().getDimensionPixelSize(R.dimen.dragViewOffsetY);
            final ImageView p = (ImageView) v.findViewById(R.id.widget_preview);
            AnimatorSet bounce = new AnimatorSet();
            ValueAnimator tyuAnim = ObjectAnimator.ofFloat(p, "translationY", offsetY);
            tyuAnim.setDuration(125);
            ValueAnimator tydAnim = ObjectAnimator.ofFloat(p, "translationY", 0f);
            tydAnim.setDuration(100);
            bounce.play(tyuAnim).before(tydAnim);
            bounce.setInterpolator(new AccelerateInterpolator());
            bounce.start();
        }
    }

There are two parts, one is to click on the main menu icon, and the other is to click on the Widget, that is, long press the Widget to add it to the Workspace standby. Here we only say that clicking on the main menu icon is the PagedViewIcon. When you click on the PagedViewIcon, you will get the corresponding ApplicationInfo information. It is completely possible to start an application through the intent of ApplicationInfo, which we have emphasized many times before.

    Clicking the icon to start the application is so much, no more, let's talk about dragging the icon or widget. When we press and hold, we can move the icon or widget from standby, and we can also move the icon or widget to standby from the main menu. What is the process? Let me explain here. Let’s talk about dragging the icon in the workspace standby, let’s start with the long press event, which is in the Launcher

    public boolean onLongClick(View v) {
        if (mState != State.WORKSPACE) {
            return false;
        }

        if (isWorkspaceLocked()) {
            return false;
        }

        if (!(v instanceof CellLayout)) {
            v = (View) v.getParent().getParent();
        }

        resetAddInfo();
        CellLayout.CellInfo longClickCellInfo = (CellLayout.CellInfo) v.getTag();
        // This happens when long clicking an item with the dpad/trackball
        if (longClickCellInfo == null) {
            return true;
        }

        // The hotseat touch handling does not go through Workspace, and we always allow long press
        // on hotseat items.
        final View itemUnderLongClick = longClickCellInfo.cell;
        boolean allowLongPress = isHotseatLayout(v) || mWorkspace.allowLongPress();
        if (allowLongPress && !mDragController.isDragging()) {
            if (itemUnderLongClick == null) {
                // User long pressed on empty space
                mWorkspace.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS,
                        HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING);
                startWallpaper();
            } else {
                if (!(itemUnderLongClick instanceof Folder)) {
                    // User long pressed on an item
                    mWorkspace.startDrag(longClickCellInfo);
                }
            }
        }
        return true;
    }

That is, mWorkspace.startDrag(longClickCellInfo) does not explain other contents, you can take a look if you are interested. mWorkspace.startDrag(longClickCellInfo) is

    void startDrag(CellLayout.CellInfo cellInfo) {
        View child = cellInfo.cell;

        // Make sure the drag was started by a long press as opposed to a long click.
        if (!child.isInTouchMode()) {
            return;
        }

        mDragInfo = cellInfo;
        child.setVisibility(GONE);

        child.clearFocus();
        child.setPressed(false);

        final Canvas canvas = new Canvas();

        // We need to add extra padding to the bitmap to make room for the glow effect
        final int bitmapPadding = HolographicOutlineHelper.MAX_OUTER_BLUR_RADIUS;

        // The outline is used to visualize where the item will land if dropped
        mDragOutline = createDragOutline(child, canvas, bitmapPadding);
        beginDragShared(child, this);
    }

Create a Bitmap for mDragOutline. This mDragOutline saves the original picture, which will be used when it is UP, that is, when you let go. Then there is beginDragShared that is

    public void beginDragShared(View child, DragSource source) {
        Resources r = getResources();

        // We need to add extra padding to the bitmap to make room for the glow effect
        final int bitmapPadding = HolographicOutlineHelper.MAX_OUTER_BLUR_RADIUS;

        // The drag bitmap follows the touch point around on the screen
        final Bitmap b = createDragBitmap(child, new Canvas(), bitmapPadding);

        final int bmpWidth = b.getWidth();

        mLauncher.getDragLayer().getLocationInDragLayer(child, mTempXY);
        final int dragLayerX = (int) mTempXY[0] + (child.getWidth() - bmpWidth) / 2;
        int dragLayerY = mTempXY[1] - bitmapPadding / 2;

        Point dragVisualizeOffset = null;
        Rect dragRect = null;
        if (child instanceof BubbleTextView || child instanceof PagedViewIcon) {
            int iconSize = r.getDimensionPixelSize(R.dimen.app_icon_size);
            int iconPaddingTop = r.getDimensionPixelSize(R.dimen.app_icon_padding_top);
            int top = child.getPaddingTop();
            int left = (bmpWidth - iconSize) / 2;
            int right = left + iconSize;
            int bottom = top + iconSize;
            dragLayerY += top;
            // Note: The drag region is used to calculate drag layer offsets, but the
            // dragVisualizeOffset in addition to the dragRect (the size) to position the outline.
            dragVisualizeOffset = new Point(-bitmapPadding / 2, iconPaddingTop - bitmapPadding / 2);
            dragRect = new Rect(left, top, right, bottom);
        } else if (child instanceof FolderIcon) {
            int previewSize = r.getDimensionPixelSize(R.dimen.folder_preview_size);
            dragRect = new Rect(0, 0, child.getWidth(), previewSize);
        }

        // Clear the pressed state if necessary
        if (child instanceof BubbleTextView) {
            BubbleTextView icon = (BubbleTextView) child;
            icon.clearPressedOrFocusedBackground();
        }

        mDragController.startDrag(b, dragLayerX, dragLayerY, source, child.getTag(),
                DragController.DRAG_ACTION_MOVE, dragVisualizeOffset, dragRect);
        b.recycle();
    }

Create a dragged Bitmap. This is different from the mDragOutline just now. The transparency, size, etc. of this Bitmap change. and then

        mDragController.startDrag(b, dragLayerX, dragLayerY, source, child.getTag(),
                DragController.DRAG_ACTION_MOVE, dragVisualizeOffset, dragRect);

that is

    public void startDrag(Bitmap b, int dragLayerX, int dragLayerY,
            DragSource source, Object dragInfo, int dragAction, Point dragOffset, Rect dragRegion) {
        if (PROFILE_DRAWING_DURING_DRAG) {
            android.os.Debug.startMethodTracing("Launcher");
        }

        // Hide soft keyboard, if visible
        if (mInputMethodManager == null) {
            mInputMethodManager = (InputMethodManager)
                    mLauncher.getSystemService(Context.INPUT_METHOD_SERVICE);
        }
        mInputMethodManager.hideSoftInputFromWindow(mWindowToken, 0);

        for (DragListener listener : mListeners) {
            listener.onDragStart(source, dragInfo, dragAction);
        }

        final int registrationX = mMotionDownX - dragLayerX;
        final int registrationY = mMotionDownY - dragLayerY;

        final int dragRegionLeft = dragRegion == null ? 0 : dragRegion.left;
        final int dragRegionTop = dragRegion == null ? 0 : dragRegion.top;

        mDragging = true;

        mDragObject = new DropTarget.DragObject();

        mDragObject.dragComplete = false;
        mDragObject.xOffset = mMotionDownX - (dragLayerX + dragRegionLeft);
        mDragObject.yOffset = mMotionDownY - (dragLayerY + dragRegionTop);
        mDragObject.dragSource = source;
        mDragObject.dragInfo = dragInfo;

        mVibrator.vibrate(VIBRATE_DURATION);

        final DragView dragView = mDragObject.dragView = new DragView(mLauncher, b, registrationX,
                registrationY, 0, 0, b.getWidth(), b.getHeight());

        if (dragOffset != null) {
            dragView.setDragVisualizeOffset(new Point(dragOffset));
        }
        if (dragRegion != null) {
            dragView.setDragRegion(new Rect(dragRegion));
        }

        dragView.show(mMotionDownX, mMotionDownY);
        handleMoveEvent(mMotionDownX, mMotionDownY);
    }

Create a DragView and then display dragView.show, it will vibrate when you press and hold it, which is here mVibrator.vibrate(VIBRATE_DURATION), and finally handleMoveEvent(mMotionDownX, mMotionDownY) is

    private void handleMoveEvent(int x, int y) {
        mDragObject.dragView.move(x, y);

        // Drop on someone?
        final int[] coordinates = mCoordinatesTemp;
        DropTarget dropTarget = findDropTarget(x, y, coordinates);
        mDragObject.x = coordinates[0];
        mDragObject.y = coordinates[1];
        if (dropTarget != null) {
            DropTarget delegate = dropTarget.getDropTargetDelegate(mDragObject);
            if (delegate != null) {
                dropTarget = delegate;
            }

            if (mLastDropTarget != dropTarget) {
                if (mLastDropTarget != null) {
                    mLastDropTarget.onDragExit(mDragObject);
                }
                dropTarget.onDragEnter(mDragObject);
            }
            dropTarget.onDragOver(mDragObject);
        } else {
            if (mLastDropTarget != null) {
                mLastDropTarget.onDragExit(mDragObject);
            }
        }
        mLastDropTarget = dropTarget;

        // After a scroll, the touch point will still be in the scroll region.
        // Rather than scrolling immediately, require a bit of twiddling to scroll again
        final int slop = ViewConfiguration.get(mLauncher).getScaledWindowTouchSlop();
        mDistanceSinceScroll +=
            Math.sqrt(Math.pow(mLastTouch[0] - x, 2) + Math.pow(mLastTouch[1] - y, 2));
        mLastTouch[0] = x;
        mLastTouch[1] = y;

        if (x < mScrollZone) {
            if (mScrollState == SCROLL_OUTSIDE_ZONE && mDistanceSinceScroll > slop) {
                mScrollState = SCROLL_WAITING_IN_ZONE;
                if (mDragScroller.onEnterScrollArea(x, y, SCROLL_LEFT)) {
                    mScrollRunnable.setDirection(SCROLL_LEFT);
                    mHandler.postDelayed(mScrollRunnable, SCROLL_DELAY);
                }
            }
        } else if (x > mScrollView.getWidth() - mScrollZone) {
            if (mScrollState == SCROLL_OUTSIDE_ZONE && mDistanceSinceScroll > slop) {
                mScrollState = SCROLL_WAITING_IN_ZONE;
                if (mDragScroller.onEnterScrollArea(x, y, SCROLL_RIGHT)) {
                    mScrollRunnable.setDirection(SCROLL_RIGHT);
                    mHandler.postDelayed(mScrollRunnable, SCROLL_DELAY);
                }
            }
        } else {
            if (mScrollState == SCROLL_WAITING_IN_ZONE) {
                mScrollState = SCROLL_OUTSIDE_ZONE;
                mScrollRunnable.setDirection(SCROLL_RIGHT);
                mHandler.removeCallbacks(mScrollRunnable);
                mDragScroller.onExitScrollArea();
            }
        }
    }

When you start dragging, you start to distribute ACTION_MOVE messages, that is

    public boolean onTouchEvent(MotionEvent ev) {
        if (!mDragging) {
            return false;
        }

        final int action = ev.getAction();
        final int[] dragLayerPos = getClampedDragLayerPos(ev.getX(), ev.getY());
        final int dragLayerX = dragLayerPos[0];
        final int dragLayerY = dragLayerPos[1];

        switch (action) {
        case MotionEvent.ACTION_DOWN:
            // Remember where the motion event started
            mMotionDownX = dragLayerX;
            mMotionDownY = dragLayerY;

            if ((dragLayerX < mScrollZone) || (dragLayerX > mScrollView.getWidth() - mScrollZone)) {
                mScrollState = SCROLL_WAITING_IN_ZONE;
                mHandler.postDelayed(mScrollRunnable, SCROLL_DELAY);
            } else {
                mScrollState = SCROLL_OUTSIDE_ZONE;
            }
            break;
        case MotionEvent.ACTION_MOVE:
            handleMoveEvent(dragLayerX, dragLayerY);
            break;
        case MotionEvent.ACTION_UP:
            // Ensure that we've processed a move event at the current pointer location.
            handleMoveEvent(dragLayerX, dragLayerY);

            mHandler.removeCallbacks(mScrollRunnable);
            if (mDragging) {
                drop(dragLayerX, dragLayerY);
            }
            endDrag();
            break;
        case MotionEvent.ACTION_CANCEL:
            cancelDrag();
            break;
        }

        return true;
    }

The MotionEvent.ACTION_MOVE message here keeps repeating handleMoveEvent, and when you let go, it is MotionEvent.ACTION_UP. Let's go back to handleMoveEvent first

mDragObject.dragView.move(x, y);

This is the process of dragging, that is,

    void move(int touchX, int touchY) {
        DragLayer.LayoutParams lp = mLayoutParams;
        lp.x = touchX - mRegistrationX + (int) mOffsetX;
        lp.y = touchY - mRegistrationY + (int) mOffsetY;
        mDragLayer.requestLayout();
    }

Change the coordinates all the time, then update. Then what is the content below handleMoveEvent? A general explanation will not be analyzed in depth, that is, what to do when the drop, that is, UP, let go, and switch to the next page when moving to the edge, these will not be explained. Then we go back to the MotionEvent.ACTION_UP message, which is

            handleMoveEvent(dragLayerX, dragLayerY);

            mHandler.removeCallbacks(mScrollRunnable);
            if (mDragging) {
                drop(dragLayerX, dragLayerY);
            }
            endDrag();

handleMoveEvent We have just said that when endDrag() drags and releases resources, we only look at drop(dragLayerX, dragLayerY) that is

    private void drop(float x, float y) {
        final int[] coordinates = mCoordinatesTemp;
        final DropTarget dropTarget = findDropTarget((int) x, (int) y, coordinates);

        mDragObject.x = coordinates[0];
        mDragObject.y = coordinates[1];
        boolean accepted = false;
        if (dropTarget != null) {
            mDragObject.dragComplete = true;
            dropTarget.onDragExit(mDragObject);
            if (dropTarget.acceptDrop(mDragObject)) {
                dropTarget.onDrop(mDragObject);
                accepted = true;
            }
        }
        mDragObject.dragSource.onDropCompleted((View) dropTarget, mDragObject, accepted);
    }

That is, dropTarget.onDrop(mDragObject), other content will not be explained in detail, but some processing is done after relaxing hands. Let’s just look at dropTarget.onDrop(mDragObject). DropTarget is an interface, which is implemented in Workspace

    public void onDrop(DragObject d) {
        mDragViewVisualCenter = getDragViewVisualCenter(d.x, d.y, d.xOffset, d.yOffset, d.dragView,
                mDragViewVisualCenter);

        // We want the point to be mapped to the dragTarget.
        if (mDragTargetLayout != null) {
            if (mLauncher.isHotseatLayout(mDragTargetLayout)) {
                mapPointFromSelfToSibling(mLauncher.getHotseat(), mDragViewVisualCenter);
            } else {
                mapPointFromSelfToChild(mDragTargetLayout, mDragViewVisualCenter, null);
            }
        }

        CellLayout dropTargetLayout = mDragTargetLayout;

        int snapScreen = -1;
        if (d.dragSource != this) {
            final int[] touchXY = new int[] { (int) mDragViewVisualCenter[0],
                    (int) mDragViewVisualCenter[1] };
            onDropExternal(touchXY, d.dragInfo, dropTargetLayout, false, d);
        } else if (mDragInfo != null) {
            final View cell = mDragInfo.cell;

            if (dropTargetLayout != null) {
                // Move internally
                boolean hasMovedLayouts = (getParentCellLayoutForView(cell) != dropTargetLayout);
                boolean hasMovedIntoHotseat = mLauncher.isHotseatLayout(dropTargetLayout);
                long container = hasMovedIntoHotseat ?
                        LauncherSettings.Favorites.CONTAINER_HOTSEAT :
                        LauncherSettings.Favorites.CONTAINER_DESKTOP;
                int screen = (mTargetCell[0] < 0) ?
                        mDragInfo.screen : indexOfChild(dropTargetLayout);
                int spanX = mDragInfo != null ? mDragInfo.spanX : 1;
                int spanY = mDragInfo != null ? mDragInfo.spanY : 1;
                // First we find the cell nearest to point at which the item is
                // dropped, without any consideration to whether there is an item there.
                mTargetCell = findNearestArea((int) mDragViewVisualCenter[0], (int)
                        mDragViewVisualCenter[1], spanX, spanY, dropTargetLayout, mTargetCell);
                // If the item being dropped is a shortcut and the nearest drop
                // cell also contains a shortcut, then create a folder with the two shortcuts.
                if (!mInScrollArea && createUserFolderIfNecessary(cell, container,
                        dropTargetLayout, mTargetCell, false, d.dragView, null)) {
                    return;
                }

                if (addToExistingFolderIfNecessary(cell, dropTargetLayout, mTargetCell, d, false)) {
                    return;
                }

                // Aside from the special case where we're dropping a shortcut onto a shortcut,
                // we need to find the nearest cell location that is vacant
                mTargetCell = findNearestVacantArea((int) mDragViewVisualCenter[0],
                        (int) mDragViewVisualCenter[1], mDragInfo.spanX, mDragInfo.spanY, cell,
                        dropTargetLayout, mTargetCell);

                if (mCurrentPage != screen && !hasMovedIntoHotseat) {
                    snapScreen = screen;
                    snapToPage(screen);
                }

                if (mTargetCell[0] >= 0 && mTargetCell[1] >= 0) {
                    if (hasMovedLayouts) {
                        // Reparent the view
                        /* Modify 112809 Spreadst of 112809 Monkey start */
                        if(getParentCellLayoutForView(cell) != null){
                            getParentCellLayoutForView(cell).removeView(cell);
                        }else{
                            Log.d(TAG,"this view not be added to CellLayout");
                        }
                        addInScreen(cell, container, screen, mTargetCell[0], mTargetCell[1],
                                mDragInfo.spanX, mDragInfo.spanY);
                    }

                    // update the item's position after drop
                    final ItemInfo info = (ItemInfo) cell.getTag();
                    CellLayout.LayoutParams lp = (CellLayout.LayoutParams) cell.getLayoutParams();
                    dropTargetLayout.onMove(cell, mTargetCell[0], mTargetCell[1]);
                    lp.cellX = mTargetCell[0];
                    lp.cellY = mTargetCell[1];
                    cell.setId(LauncherModel.getCellLayoutChildId(container, mDragInfo.screen,
                            mTargetCell[0], mTargetCell[1], mDragInfo.spanX, mDragInfo.spanY));

                    if (container != LauncherSettings.Favorites.CONTAINER_HOTSEAT &&
                            cell instanceof LauncherAppWidgetHostView) {
                        final CellLayout cellLayout = dropTargetLayout;
                        // We post this call so that the widget has a chance to be placed
                        // in its final location

                        final LauncherAppWidgetHostView hostView = (LauncherAppWidgetHostView) cell;
                        AppWidgetProviderInfo pinfo = hostView.getAppWidgetInfo();
                        if (pinfo != null && pinfo.resizeMode != AppWidgetProviderInfo.RESIZE_NONE) {
                            final Runnable resizeRunnable = new Runnable() {
                                public void run() {
                                    DragLayer dragLayer = mLauncher.getDragLayer();
                                    dragLayer.addResizeFrame(info, hostView, cellLayout);
                                }
                            };
                            post(new Runnable() {
                                public void run() {
                                    if (!isPageMoving()) {
                                        resizeRunnable.run();
                                    } else {
                                        mDelayedResizeRunnable = resizeRunnable;
                                    }
                                }
                            });
                        }
                    }
                    ItemInfo modelItem = null;
                    if(info != null) {
                        modelItem = LauncherModel.sItemsIdMap.get(info.id);
                    }
                    if(modelItem == null){
                        /**Bug141020 Bug146476 start.if the item has been deleted from db ,such as stk1 ,stk2,
                         *  just return,if the item is Folder and there is no other Shorcut except stk1 ,stk2 
                         *  delete the Emputy Folder**/
                        if(cell instanceof FolderIcon){
                            FolderIcon folder= (FolderIcon)cell;
                            ArrayList<View> folderItem = folder.mFolder.getItemsInReadingOrder();
                            if(folderItem.size() == 0){
                                getParentCellLayoutForView(cell).removeView(cell);
                            }
                        }
                        return;
                    }
                    LauncherModel.moveItemInDatabase(mLauncher, info, container, screen, lp.cellX,
                            lp.cellY);
                }
            }

            final CellLayout parent = (CellLayout) cell.getParent().getParent();

            // Prepare it to be animated into its new position
            // This must be called after the view has been re-parented
            final Runnable disableHardwareLayersRunnable = new Runnable() {
                @Override
                public void run() {
                    mAnimatingViewIntoPlace = false;
                    updateChildrenLayersEnabled();
                }
            };
            mAnimatingViewIntoPlace = true;
            if (d.dragView.hasDrawn()) {
                int duration = snapScreen < 0 ? -1 : ADJACENT_SCREEN_DROP_DURATION;
                setFinalScrollForPageChange(snapScreen);
                mLauncher.getDragLayer().animateViewIntoPosition(d.dragView, cell, duration,
                        disableHardwareLayersRunnable);
                resetFinalScrollForPageChange(snapScreen);
            } else {
                cell.setVisibility(VISIBLE);
            }
            parent.onDropChild(cell);
        }
    }

This function is relatively large, so I won’t explain it one by one. Generally speaking, it can be divided into three situations. First, if you drag it from the main menu to the workspace standby, go to onDropExternal(touchXY, d.dragInfo, dropTargetLayout, false, d) here , if it is dragged in the workspace, there are two situations, one is that the icon is not dragged to another page, it will be updated and refreshed, and if it is dragged to the next page, it will leave

                        addInScreen(cell, container, screen, mTargetCell[0], mTargetCell[1],
                                mDragInfo.spanX, mDragInfo.spanY);

addInScreen is

    void addInScreen(View child, long container, int screen, int x, int y, int spanX, int spanY,
            boolean insert) {
        if (container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
            if (screen < 0 || screen >= getChildCount()) {
                Log.e(TAG, "The screen must be >= 0 and < " + getChildCount()
                    + " (was " + screen + "); skipping child");
                return;
            }
        }

        final CellLayout layout;
        if (container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
            layout = mLauncher.getHotseat().getLayout();
            child.setOnKeyListener(null);

            // Hide folder title in the hotseat
            if (child instanceof FolderIcon) {
                ((FolderIcon) child).setTextVisible(false);
            }

            if (screen < 0) {
                screen = mLauncher.getHotseat().getOrderInHotseat(x, y);
            } else {
                // Note: We do this to ensure that the hotseat is always laid out in the orientation
                // of the hotseat in order regardless of which orientation they were added
                x = mLauncher.getHotseat().getCellXFromOrder(screen);
                y = mLauncher.getHotseat().getCellYFromOrder(screen);
            }
        } else {
            // Show folder title if not in the hotseat
            if (child instanceof FolderIcon) {
                ((FolderIcon) child).setTextVisible(true);
            }

            layout = (CellLayout) getChildAt(screen);
            child.setOnKeyListener(new IconKeyEventListener());
        }

        CellLayout.LayoutParams lp = (CellLayout.LayoutParams) child.getLayoutParams();
        if (lp == null) {
            lp = new CellLayout.LayoutParams(x, y, spanX, spanY);
        } else {
            lp.cellX = x;
            lp.cellY = y;
            lp.cellHSpan = spanX;
            lp.cellVSpan = spanY;
        }

        if (spanX < 0 && spanY < 0) {
            lp.isLockedToGrid = false;
        }

        // Get the canonical child id to uniquely represent this view in this screen
        int childId = LauncherModel.getCellLayoutChildId(container, screen, x, y, spanX, spanY);
        boolean markCellsAsOccupied = !(child instanceof Folder);
        if (!layout.addViewToCellLayout(child, insert ? 0 : -1, childId, lp, markCellsAsOccupied)) {
            // TODO: This branch occurs when the workspace is adding views
            // outside of the defined grid
            // maybe we should be deleting these items from the LauncherModel?
            Log.w(TAG, "Failed to add to item at (" + lp.cellX + "," + lp.cellY + ") to CellLayout");
        }

        if (!(child instanceof Folder)) {
            child.setHapticFeedbackEnabled(false);
            child.setOnLongClickListener(mLongClickListener);
        }
        if (child instanceof DropTarget) {
            mDragController.addDropTarget((DropTarget) child);
        }
    }

Some calculations are done here, what is being dragged, where to put it, etc., and then layout.addViewToCellLayout(child, insert ? 0 : -1, childId, lp, markCellsAsOccupied) The layout here is CellLayout, so layout.addViewToCellLayout is also that is

    public boolean addViewToCellLayout(
            View child, int index, int childId, LayoutParams params, boolean markCells) {
        final LayoutParams lp = params;

        // Generate an id for each view, this assumes we have at most 256x256 cells
        // per workspace screen
        if (lp.cellX >= 0 && lp.cellX <= mCountX - 1 && lp.cellY >= 0 && lp.cellY <= mCountY - 1) {
            // If the horizontal or vertical span is set to -1, it is taken to
            // mean that it spans the extent of the CellLayout
            if (lp.cellHSpan < 0) lp.cellHSpan = mCountX;
            if (lp.cellVSpan < 0) lp.cellVSpan = mCountY;

            child.setId(childId);

            mChildren.addView(child, index, lp);

            if (markCells) markCellsAsOccupiedForView(child);

            return true;
        }
        return false;
    }

That is mChildren.addView(child, index, lp), where mChildren is CellLayoutChildren, CellLayoutChildren we have said before, so I won’t talk about it again, so far a moving process is over. Now let's go back and see what is the process of dragging from the main menu to the workspace standby. This process is mainly the conversion process from the main menu to the workspace. We still start with the long press event. The long press event from the main menu should be in the Inside AppsCustomizePagedView, but not here, we go to its parent class PagedViewWithDraggableItems to find, that is

    @Override
    public boolean onLongClick(View v) {
        // Return early if this is not initiated from a touch
        if (!v.isInTouchMode()) return false;
        // Return early if we are still animating the pages
        if (mNextPage != INVALID_PAGE) return false;
        // When we have exited all apps or are in transition, disregard long clicks
        if (!mLauncher.isAllAppsCustomizeOpen() ||
                mLauncher.getWorkspace().isSwitchingState()) return false;

        return beginDragging(v);
    }

That is beginDragging, beginDragging is rewritten in its subclass AppsCustomizePagedView, that is

    @Override
    protected boolean beginDragging(View v) {
        // Dismiss the cling
        mLauncher.dismissAllAppsCling(null);

        if (!super.beginDragging(v)) return false;

        // Go into spring loaded mode (must happen before we startDrag())
        mLauncher.enterSpringLoadedDragMode();

        if (v instanceof PagedViewIcon) {
            beginDraggingApplication(v);
        } else if (v instanceof PagedViewWidget) {
            beginDraggingWidget(v);
        }
        return true;
    }

What does mLauncher.enterSpringLoadedDragMode() do? It is to hide the main menu and display the workspace standby, so that it switches from the display to the workspace, but the essence has not been switched to the workspace, which will be mentioned later, and then it is to distinguish whether to drag PagedViewIcon (App icon) or PagedViewWidget ( widget icon). Here we only look at the App icon, which is beginDraggingApplication(v)

    private void beginDraggingApplication(View v) {
        mLauncher.getWorkspace().onDragStartedWithItem(v);
        mLauncher.getWorkspace().beginDragShared(v, this);
    }

Here is essentially switching to the workspace, first look at the sentence above mLauncher.getWorkspace().onDragStartedWithItem(v) that is

    public void onDragStartedWithItem(View v) {
        final Canvas canvas = new Canvas();

        // We need to add extra padding to the bitmap to make room for the glow effect
        final int bitmapPadding = HolographicOutlineHelper.MAX_OUTER_BLUR_RADIUS;

        // The outline is used to visualize where the item will land if dropped
        mDragOutline = createDragOutline(v, canvas, bitmapPadding);
    }

A Bitmap is also created here as mDragOutline, which is the same as the workspace dragging we just explained, so I won’t talk about it anymore, and then look at the next sentence mLauncher.getWorkspace().beginDragShared(v, this) that is

    public void beginDragShared(View child, DragSource source) {
        Resources r = getResources();

        // We need to add extra padding to the bitmap to make room for the glow effect
        final int bitmapPadding = HolographicOutlineHelper.MAX_OUTER_BLUR_RADIUS;

        // The drag bitmap follows the touch point around on the screen
        final Bitmap b = createDragBitmap(child, new Canvas(), bitmapPadding);

        final int bmpWidth = b.getWidth();

        mLauncher.getDragLayer().getLocationInDragLayer(child, mTempXY);
        final int dragLayerX = (int) mTempXY[0] + (child.getWidth() - bmpWidth) / 2;
        int dragLayerY = mTempXY[1] - bitmapPadding / 2;

        Point dragVisualizeOffset = null;
        Rect dragRect = null;
        if (child instanceof BubbleTextView || child instanceof PagedViewIcon) {
            int iconSize = r.getDimensionPixelSize(R.dimen.app_icon_size);
            int iconPaddingTop = r.getDimensionPixelSize(R.dimen.app_icon_padding_top);
            int top = child.getPaddingTop();
            int left = (bmpWidth - iconSize) / 2;
            int right = left + iconSize;
            int bottom = top + iconSize;
            dragLayerY += top;
            // Note: The drag region is used to calculate drag layer offsets, but the
            // dragVisualizeOffset in addition to the dragRect (the size) to position the outline.
            dragVisualizeOffset = new Point(-bitmapPadding / 2, iconPaddingTop - bitmapPadding / 2);
            dragRect = new Rect(left, top, right, bottom);
        } else if (child instanceof FolderIcon) {
            int previewSize = r.getDimensionPixelSize(R.dimen.folder_preview_size);
            dragRect = new Rect(0, 0, child.getWidth(), previewSize);
        }

        // Clear the pressed state if necessary
        if (child instanceof BubbleTextView) {
            BubbleTextView icon = (BubbleTextView) child;
            icon.clearPressedOrFocusedBackground();
        }

        mDragController.startDrag(b, dragLayerX, dragLayerY, source, child.getTag(),
                DragController.DRAG_ACTION_MOVE, dragVisualizeOffset, dragRect);
        b.recycle();
    }

Here again, this is the same as the workspace dragging just now, and there is no explanation, then enter mDragController.startDrag and then the handleMoveEvent cycle, and then drag to the appropriate position MotionEvent.ACTION_UP message, and then drop , the dropTarget.onDrop process is the same as the workspace dragging process, except that it is different in the onDrop of the Workspace, which is what we mentioned earlier. When the icon is pulled out from the main menu, it will go onDropExternal(touchXY, d.dragInfo, dropTargetLayout, false, d) that is

    private void onDropExternal(final int[] touchXY, final Object dragInfo,
            final CellLayout cellLayout, boolean insertAtFirst, DragObject d) {
        final Runnable exitSpringLoadedRunnable = new Runnable() {
            @Override
            public void run() {
                mLauncher.exitSpringLoadedDragModeDelayed(true, false);
            }
        };

        ItemInfo info = (ItemInfo) dragInfo;
        int spanX = info.spanX;
        int spanY = info.spanY;
        if (mDragInfo != null) {
            spanX = mDragInfo.spanX;
            spanY = mDragInfo.spanY;
        }

        final long container = mLauncher.isHotseatLayout(cellLayout) ?
                LauncherSettings.Favorites.CONTAINER_HOTSEAT :
                    LauncherSettings.Favorites.CONTAINER_DESKTOP;
        final int screen = indexOfChild(cellLayout);
        if (!mLauncher.isHotseatLayout(cellLayout) && screen != mCurrentPage
                && mState != State.SPRING_LOADED) {
            snapToPage(screen);
        }

        if (info instanceof PendingAddItemInfo) {
            final PendingAddItemInfo pendingInfo = (PendingAddItemInfo) dragInfo;

            boolean findNearestVacantCell = true;
            if (pendingInfo.itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT) {
                mTargetCell = findNearestArea((int) touchXY[0], (int) touchXY[1], spanX, spanY,
                        cellLayout, mTargetCell);
                if (willCreateUserFolder((ItemInfo) d.dragInfo, mDragTargetLayout, mTargetCell,
                        true) || willAddToExistingUserFolder((ItemInfo) d.dragInfo,
                                mDragTargetLayout, mTargetCell)) {
                    findNearestVacantCell = false;
                }
            }
            if (findNearestVacantCell) {
                    mTargetCell = findNearestVacantArea(touchXY[0], touchXY[1], spanX, spanY, null,
                        cellLayout, mTargetCell);
            }

            Runnable onAnimationCompleteRunnable = new Runnable() {
                @Override
                public void run() {
                    // When dragging and dropping from customization tray, we deal with creating
                    // widgets/shortcuts/folders in a slightly different way
                    switch (pendingInfo.itemType) {
                    case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET:
                        mLauncher.addAppWidgetFromDrop((PendingAddWidgetInfo) pendingInfo,
                                container, screen, mTargetCell, null);
                        break;
                    case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
                        mLauncher.processShortcutFromDrop(pendingInfo.componentName,
                                container, screen, mTargetCell, null);
                        break;
                    default:
                        throw new IllegalStateException("Unknown item type: " +
                                pendingInfo.itemType);
                    }
                    cellLayout.onDragExit();
                }
            };

            // Now we animate the dragView, (ie. the widget or shortcut preview) into its final
            // location and size on the home screen.
            RectF r = estimateItemPosition(cellLayout, pendingInfo,
                    mTargetCell[0], mTargetCell[1], spanX, spanY);
            int loc[] = new int[2];
            loc[0] = (int) r.left;
            loc[1] = (int) r.top;
            setFinalTransitionTransform(cellLayout);
            float cellLayoutScale =
                    mLauncher.getDragLayer().getDescendantCoordRelativeToSelf(cellLayout, loc);
            resetTransitionTransform(cellLayout);

            float dragViewScale =  Math.min(r.width() / d.dragView.getMeasuredWidth(),
                    r.height() / d.dragView.getMeasuredHeight());
            // The animation will scale the dragView about its center, so we need to center about
            // the final location.
            loc[0] -= (d.dragView.getMeasuredWidth() - cellLayoutScale * r.width()) / 2;
            loc[1] -= (d.dragView.getMeasuredHeight() - cellLayoutScale * r.height()) / 2;

            mLauncher.getDragLayer().animateViewIntoPosition(d.dragView, loc,
                    dragViewScale * cellLayoutScale, onAnimationCompleteRunnable);
        } else {
            // This is for other drag/drop cases, like dragging from All Apps
            View view = null;

            switch (info.itemType) {
            case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
            case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
                if (info.container == NO_ID && info instanceof ApplicationInfo) {
                    // Came from all apps -- make a copy
                    info = new ShortcutInfo((ApplicationInfo) info);
                }
                view = mLauncher.createShortcut(R.layout.application, cellLayout,
                        (ShortcutInfo) info);
                break;
            case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
                view = FolderIcon.fromXml(R.layout.folder_icon, mLauncher, cellLayout,
                        (FolderInfo) info, mIconCache);
                break;
            default:
                throw new IllegalStateException("Unknown item type: " + info.itemType);
            }

            // First we find the cell nearest to point at which the item is
            // dropped, without any consideration to whether there is an item there.
            if (touchXY != null) {
                mTargetCell = findNearestArea((int) touchXY[0], (int) touchXY[1], spanX, spanY,
                        cellLayout, mTargetCell);
                d.postAnimationRunnable = exitSpringLoadedRunnable;
                if (createUserFolderIfNecessary(view, container, cellLayout, mTargetCell, true,
                        d.dragView, d.postAnimationRunnable)) {
                    return;
                }
                if (addToExistingFolderIfNecessary(view, cellLayout, mTargetCell, d, true)) {
                    return;
                }
            }

            if (touchXY != null) {
                // when dragging and dropping, just find the closest free spot
                mTargetCell = findNearestVacantArea(touchXY[0], touchXY[1], 1, 1, null,
                        cellLayout, mTargetCell);
            } else {
                cellLayout.findCellForSpan(mTargetCell, 1, 1);
            }
            addInScreen(view, container, screen, mTargetCell[0], mTargetCell[1], info.spanX,
                    info.spanY, insertAtFirst);
            cellLayout.onDropChild(view);
            CellLayout.LayoutParams lp = (CellLayout.LayoutParams) view.getLayoutParams();
            cellLayout.getChildrenLayout().measureChild(view);

            LauncherModel.addOrMoveItemInDatabase(mLauncher, info, container, screen,
                    lp.cellX, lp.cellY);

            if (d.dragView != null) {
                // We wrap the animation call in the temporary set and reset of the current
                // cellLayout to its final transform -- this means we animate the drag view to
                // the correct final location.
                setFinalTransitionTransform(cellLayout);
                mLauncher.getDragLayer().animateViewIntoPosition(d.dragView, view,
                        exitSpringLoadedRunnable);
                resetTransitionTransform(cellLayout);
            }
        }
    }

This is also divided into two parts. One is PendingAddItemInfo. PendingAddItemInfo is related to Widget. It will not be explained in detail here, and our application icon will go to

            addInScreen(view, container, screen, mTargetCell[0], mTargetCell[1], info.spanX,
                    info.spanY, insertAtFirst);

Here, addInScreen has already been explained above, so I won't repeat it here. I won’t explain the other content any more, if you are interested, do your own research. So far, our dragging process has been explained.

    In this article, we explained the loading process of the Launcher icon, the process of clicking the icon to enter the application, and the process of dragging the icon. As for installing applications, uninstalling applications, updating applications, wallpapers, widgets, and other Launcher content, we will explain them later if necessary. Bar.

    Still the same sentence, for the masters to have fun after dinner, to attract newcomers, thank God for not scolding me behind my back.


Guess you like

Origin blog.csdn.net/zhiyuan263287/article/details/19970937