Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 711ee90a authored by Sunny Goyal's avatar Sunny Goyal Committed by Android (Google) Code Review
Browse files

Merge "Refactoring package tracking in managed profile heuristic into a...

Merge "Refactoring package tracking in managed profile heuristic into a separate class" into ub-launcher3-calgary
parents 1ed6c4ad 752848a3
Loading
Loading
Loading
Loading
+188 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2016 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.launcher3.util;

import android.content.Context;
import android.content.SharedPreferences;

import com.android.launcher3.Utilities;
import com.android.launcher3.compat.LauncherActivityInfoCompat;
import com.android.launcher3.compat.LauncherAppsCompat;
import com.android.launcher3.compat.LauncherAppsCompat.OnAppsChangedCallbackCompat;
import com.android.launcher3.compat.UserHandleCompat;
import com.android.launcher3.compat.UserManagerCompat;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

/**
 * Utility class to track list of installed packages. It persists the list so that apps
 * installed/uninstalled while Launcher was dead can also be handled properly.
 */
public abstract class CachedPackageTracker implements OnAppsChangedCallbackCompat {

    protected static final String INSTALLED_PACKAGES_PREFIX = "installed_packages_for_user_";

    protected final SharedPreferences mPrefs;
    protected final UserManagerCompat mUserManager;
    protected final LauncherAppsCompat mLauncherApps;

    public CachedPackageTracker(Context context, String preferenceFileName) {
        mPrefs = context.getSharedPreferences(preferenceFileName, Context.MODE_PRIVATE);
        mUserManager = UserManagerCompat.getInstance(context);
        mLauncherApps = LauncherAppsCompat.getInstance(context);
    }

    /**
     * Checks the list of user apps, and generates package event accordingly.
     * {@see #onLauncherAppsAdded}, {@see #onLauncherPackageRemoved}
     */
    public void processUserApps(List<LauncherActivityInfoCompat> apps, UserHandleCompat user) {
        String prefKey = INSTALLED_PACKAGES_PREFIX + mUserManager.getSerialNumberForUser(user);
        HashSet<String> oldPackageSet = new HashSet<>();
        final boolean userAppsExisted = getUserApps(oldPackageSet, prefKey);

        HashSet<String> packagesRemoved = new HashSet<>(oldPackageSet);
        HashSet<String> newPackageSet = new HashSet<>();
        ArrayList<LauncherActivityInstallInfo> packagesAdded = new ArrayList<>();

        for (LauncherActivityInfoCompat info : apps) {
            String packageName = info.getComponentName().getPackageName();
            newPackageSet.add(packageName);
            packagesRemoved.remove(packageName);

            if (!oldPackageSet.contains(packageName)) {
                oldPackageSet.add(packageName);
                packagesAdded.add(new LauncherActivityInstallInfo(
                        info, info.getFirstInstallTime()));
            }
        }

        if (!packagesAdded.isEmpty() || !packagesRemoved.isEmpty()) {
            mPrefs.edit().putStringSet(prefKey, newPackageSet).apply();

            if (!packagesAdded.isEmpty()) {
                Collections.sort(packagesAdded);
                onLauncherAppsAdded(packagesAdded, user, userAppsExisted);
            }

            if (!packagesRemoved.isEmpty()) {
                for (String pkg : packagesRemoved) {
                    onLauncherPackageRemoved(pkg, user);
                }
            }
        }
    }

    /**
     * Reads the list of user apps which have already been processed.
     * @return false if the list didn't exist, true otherwise
     */
    private boolean getUserApps(HashSet<String> outExistingApps, String prefKey) {
        Set<String> userApps = mPrefs.getStringSet(prefKey, null);
        if (userApps == null) {
            return false;
        } else {
            outExistingApps.addAll(userApps);
            return true;
        }
    }

    @Override
    public void onPackageRemoved(String packageName, UserHandleCompat user) {
        String prefKey = INSTALLED_PACKAGES_PREFIX + mUserManager.getSerialNumberForUser(user);
        HashSet<String> packageSet = new HashSet<>();
        if (getUserApps(packageSet, prefKey) && packageSet.remove(packageName)) {
            mPrefs.edit().putStringSet(prefKey, packageSet).apply();
        }

        onLauncherPackageRemoved(packageName, user);
    }

    @Override
    public void onPackageAdded(String packageName, UserHandleCompat user) {
        String prefKey = INSTALLED_PACKAGES_PREFIX + mUserManager.getSerialNumberForUser(user);
        HashSet<String> packageSet = new HashSet<>();
        final boolean userAppsExisted = getUserApps(packageSet, prefKey);
        if (!packageSet.contains(packageName)) {
            List<LauncherActivityInfoCompat> activities =
                    mLauncherApps.getActivityList(packageName, user);
            if (!activities.isEmpty()) {
                LauncherActivityInfoCompat activityInfo = activities.get(0);

                packageSet.add(packageName);
                mPrefs.edit().putStringSet(prefKey, packageSet).apply();
                onLauncherAppsAdded(Arrays.asList(
                        new LauncherActivityInstallInfo(activityInfo, System.currentTimeMillis())),
                        user, userAppsExisted);
            }
        }
    }

    @Override
    public void onPackageChanged(String packageName, UserHandleCompat user) { }

    @Override
    public void onPackagesAvailable(
            String[] packageNames, UserHandleCompat user, boolean replacing) { }

    @Override
    public void onPackagesUnavailable(
            String[] packageNames, UserHandleCompat user, boolean replacing) { }

    @Override
    public void onPackagesSuspended(String[] packageNames, UserHandleCompat user) { }

    @Override
    public void onPackagesUnsuspended(String[] packageNames, UserHandleCompat user) { }

    /**
     * Called when new launcher apps are added.
     * @param apps list of newly added activities. Only one entry per package is sent.
     * @param user the user for this event. All activities in {@param apps} will belong to
     *             the same user.
     * @param userAppsExisted false if the list was processed for the first time, like in case
     *                        when Launcher was newly installed or a new user was added.
     */
    protected abstract void onLauncherAppsAdded(List<LauncherActivityInstallInfo> apps,
            UserHandleCompat user, boolean userAppsExisted);

    /**
     * Called when apps are removed from the system.
     */
    protected abstract void onLauncherPackageRemoved(String packageName, UserHandleCompat user);

    protected static class LauncherActivityInstallInfo
            implements Comparable<LauncherActivityInstallInfo> {
        public final LauncherActivityInfoCompat info;
        public final long installTime;

        public LauncherActivityInstallInfo(LauncherActivityInfoCompat info, long installTime) {
            this.info = info;
            this.installTime = installTime;
        }

        @Override
        public int compareTo(LauncherActivityInstallInfo another) {
            return Utilities.longCompare(installTime, another.installTime);
        }
    }
}
+90 −195
Original line number Diff line number Diff line
@@ -16,14 +16,8 @@

package com.android.launcher3.util;

import android.annotation.TargetApi;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.os.Build;
import android.util.Log;

import com.android.launcher3.FolderInfo;
import com.android.launcher3.ItemInfo;
@@ -35,27 +29,19 @@ import com.android.launcher3.R;
import com.android.launcher3.ShortcutInfo;
import com.android.launcher3.Utilities;
import com.android.launcher3.compat.LauncherActivityInfoCompat;
import com.android.launcher3.compat.LauncherAppsCompat;
import com.android.launcher3.compat.UserHandleCompat;
import com.android.launcher3.compat.UserManagerCompat;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

/**
 * Handles addition of app shortcuts for managed profiles.
 * Methods of class should only be called on {@link LauncherModel#sWorkerThread}.
 */
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public class ManagedProfileHeuristic {

    private static final String TAG = "ManagedProfileHeuristic";

    /**
     * Maintain a set of packages installed per user.
     */
@@ -76,118 +62,100 @@ public class ManagedProfileHeuristic {
    }

    private final Context mContext;
    private final UserHandleCompat mUser;
    private final LauncherModel mModel;

    private final SharedPreferences mPrefs;
    private final long mUserSerial;
    private final long mUserCreationTime;
    private final String mPackageSetKey;

    private ArrayList<ShortcutInfo> mHomescreenApps;
    private ArrayList<ShortcutInfo> mWorkFolderApps;
    private HashMap<ShortcutInfo, Long> mShortcutToInstallTimeMap;
    private final UserHandleCompat mUser;

    private ManagedProfileHeuristic(Context context, UserHandleCompat user) {
        mContext = context;
        mUser = user;
        mModel = LauncherAppState.getInstance().getModel();
    }

        UserManagerCompat userManager = UserManagerCompat.getInstance(context);
        mUserSerial = userManager.getSerialNumberForUser(user);
        mUserCreationTime = userManager.getUserCreationTime(user);
        mPackageSetKey = INSTALLED_PACKAGES_PREFIX + mUserSerial;

        mPrefs = mContext.getSharedPreferences(LauncherFiles.MANAGED_USER_PREFERENCES_KEY,
                Context.MODE_PRIVATE);
    public void processPackageRemoved(String[] packages) {
        Preconditions.assertWorkerThread();
        ManagedProfilePackageHandler handler = new ManagedProfilePackageHandler();
        for (String pkg : packages) {
            handler.onPackageRemoved(pkg, mUser);
        }
    }

    private void initVars() {
        mHomescreenApps = new ArrayList<>();
        mWorkFolderApps = new ArrayList<>();
        mShortcutToInstallTimeMap = new HashMap<>();
    public void processPackageAdd(String[] packages) {
        Preconditions.assertWorkerThread();
        ManagedProfilePackageHandler handler = new ManagedProfilePackageHandler();
        for (String pkg : packages) {
            handler.onPackageAdded(pkg, mUser);
        }
    }

    /**
     * Checks the list of user apps and adds icons for newly installed apps on the homescreen or
     * workfolder.
     */
    public void processUserApps(List<LauncherActivityInfoCompat> apps) {
        initVars();
        Preconditions.assertWorkerThread();
        new ManagedProfilePackageHandler().processUserApps(apps, mUser);
    }

        HashSet<String> packageSet = new HashSet<>();
        final boolean userAppsExisted = getUserApps(packageSet);
    private class ManagedProfilePackageHandler extends CachedPackageTracker {

        boolean newPackageAdded = false;
        for (LauncherActivityInfoCompat info : apps) {
            String packageName = info.getComponentName().getPackageName();
            if (!packageSet.contains(packageName)) {
                packageSet.add(packageName);
                newPackageAdded = true;
                markForAddition(info, info.getFirstInstallTime());
        private ManagedProfilePackageHandler() {
            super(mContext, LauncherFiles.MANAGED_USER_PREFERENCES_KEY);
        }

        protected void onLauncherAppsAdded(
                List<LauncherActivityInstallInfo> apps, UserHandleCompat user, boolean userAppsExisted) {
            ArrayList<ShortcutInfo> workFolderApps = new ArrayList<>();
            ArrayList<ShortcutInfo> homescreenApps = new ArrayList<>();

            int count = apps.size();
            long folderCreationTime =
                    mUserManager.getUserCreationTime(user) + AUTO_ADD_TO_FOLDER_DURATION;

            for (int i = 0; i < count; i++) {
                LauncherActivityInstallInfo info = apps.get(i);

                ShortcutInfo si = ShortcutInfo.fromActivityInfo(info.info, mContext);
                ((info.installTime <= folderCreationTime) ? workFolderApps : homescreenApps).add(si);
            }

        if (newPackageAdded) {
            mPrefs.edit().putStringSet(mPackageSetKey, packageSet).apply();
            finalizeWorkFolder(user, workFolderApps, homescreenApps);

            // Do not add shortcuts on the homescreen for the first time. This prevents the launcher
            // getting filled with the managed user apps, when it start with a fresh DB (or after
            // a very long time).
            finalizeAdditions(userAppsExisted);
        }
            if (userAppsExisted && !homescreenApps.isEmpty()) {
                mModel.addAndBindAddedWorkspaceItems(mContext, homescreenApps);
            }

    private void markForAddition(LauncherActivityInfoCompat info, long installTime) {
        ArrayList<ShortcutInfo> targetList =
                (installTime <= mUserCreationTime + AUTO_ADD_TO_FOLDER_DURATION) ?
                        mWorkFolderApps : mHomescreenApps;
        ShortcutInfo si = ShortcutInfo.fromActivityInfo(info, mContext);
        mShortcutToInstallTimeMap.put(si, installTime);
        targetList.add(si);
        }

    private void sortList(ArrayList<ShortcutInfo> infos) {
        Collections.sort(infos, new Comparator<ShortcutInfo>() {

        @Override
            public int compare(ShortcutInfo lhs, ShortcutInfo rhs) {
                Long lhsTime = mShortcutToInstallTimeMap.get(lhs);
                Long rhsTime = mShortcutToInstallTimeMap.get(rhs);
                return Utilities.longCompare(lhsTime == null ? 0 : lhsTime,
                        rhsTime == null ? 0 : rhsTime);
            }
        });
        protected void onLauncherPackageRemoved(String packageName, UserHandleCompat user) {
        }

        /**
         * Adds and binds shortcuts marked to be added to the work folder.
         */
    private void finalizeWorkFolder() {
        if (mWorkFolderApps.isEmpty()) {
        private void finalizeWorkFolder(
                UserHandleCompat user, final ArrayList<ShortcutInfo> workFolderApps,
                ArrayList<ShortcutInfo> homescreenApps) {
            if (workFolderApps.isEmpty()) {
                return;
            }
        sortList(mWorkFolderApps);

            // Try to get a work folder.
        String folderIdKey = USER_FOLDER_ID_PREFIX + mUserSerial;
            String folderIdKey = USER_FOLDER_ID_PREFIX + mUserManager.getSerialNumberForUser(user);
            if (mPrefs.contains(folderIdKey)) {
                long folderId = mPrefs.getLong(folderIdKey, 0);
                final FolderInfo workFolder = mModel.findFolderById(folderId);

                if (workFolder == null || !workFolder.hasOption(FolderInfo.FLAG_WORK_FOLDER)) {
                    // Could not get a work folder. Add all the icons to homescreen.
                mHomescreenApps.addAll(mWorkFolderApps);
                    homescreenApps.addAll(0, workFolderApps);
                    return;
                }
            saveWorkFolderShortcuts(folderId, workFolder.contents.size());
                saveWorkFolderShortcuts(folderId, workFolder.contents.size(), workFolderApps);

            final ArrayList<ShortcutInfo> shortcuts = mWorkFolderApps;
                // FolderInfo could already be bound. We need to add shortcuts on the UI thread.
                new MainThreadExecutor().execute(new Runnable() {

                    @Override
                    public void run() {
                    for (ShortcutInfo info : shortcuts) {
                        for (ShortcutInfo info : workFolderApps) {
                            workFolder.add(info, false);
                        }
                    }
@@ -199,7 +167,7 @@ public class ManagedProfileHeuristic {
                workFolder.setOption(FolderInfo.FLAG_WORK_FOLDER, true, null);

                // Add all shortcuts before adding it to the UI, as an empty folder might get deleted.
            for (ShortcutInfo info : mWorkFolderApps) {
                for (ShortcutInfo info : workFolderApps) {
                    workFolder.add(info, false);
                }

@@ -207,97 +175,24 @@ public class ManagedProfileHeuristic {
                ArrayList<ItemInfo> itemList = new ArrayList<ItemInfo>(1);
                itemList.add(workFolder);
                mModel.addAndBindAddedWorkspaceItems(mContext, itemList);
            mPrefs.edit().putLong(USER_FOLDER_ID_PREFIX + mUserSerial, workFolder.id).apply();
                mPrefs.edit().putLong(folderIdKey, workFolder.id).apply();

            saveWorkFolderShortcuts(workFolder.id, 0);
                saveWorkFolderShortcuts(workFolder.id, 0, workFolderApps);
            }
        }
    }

    /**
     * Add work folder shortcuts to the DB.
     */
    private void saveWorkFolderShortcuts(long workFolderId, int startingRank) {
        for (ItemInfo info : mWorkFolderApps) {
    private void saveWorkFolderShortcuts(
            long workFolderId, int startingRank, ArrayList<ShortcutInfo> workFolderApps) {
        for (ItemInfo info : workFolderApps) {
            info.rank = startingRank++;
            LauncherModel.addItemToDatabase(mContext, info, workFolderId, 0, 0, 0);
        }
    }

    /**
     * Adds and binds all shortcuts marked for addition.
     */
    private void finalizeAdditions(boolean addHomeScreenShortcuts) {
        finalizeWorkFolder();

        if (addHomeScreenShortcuts && !mHomescreenApps.isEmpty()) {
            sortList(mHomescreenApps);
            mModel.addAndBindAddedWorkspaceItems(mContext, mHomescreenApps);
        }
    }

    /**
     * Updates the list of installed apps and adds any new icons on homescreen or work folder.
     */
    public void processPackageAdd(String[] packages) {
        initVars();
        HashSet<String> packageSet = new HashSet<>();
        final boolean userAppsExisted = getUserApps(packageSet);

        boolean newPackageAdded = false;
        long installTime = System.currentTimeMillis();
        LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(mContext);

        for (String packageName : packages) {
            if (!packageSet.contains(packageName)) {
                packageSet.add(packageName);
                newPackageAdded = true;

                List<LauncherActivityInfoCompat> activities =
                        launcherApps.getActivityList(packageName, mUser);
                if (!activities.isEmpty()) {
                    markForAddition(activities.get(0), installTime);
                }
            }
        }

        if (newPackageAdded) {
            mPrefs.edit().putStringSet(mPackageSetKey, packageSet).apply();
            finalizeAdditions(userAppsExisted);
        }
    }

    /**
     * Updates the list of installed packages for the user.
     */
    public void processPackageRemoved(String[] packages) {
        HashSet<String> packageSet = new HashSet<String>();
        getUserApps(packageSet);
        boolean packageRemoved = false;

        for (String packageName : packages) {
            if (packageSet.remove(packageName)) {
                packageRemoved = true;
            }
        }

        if (packageRemoved) {
            mPrefs.edit().putStringSet(mPackageSetKey, packageSet).apply();
        }
    }

    /**
     * Reads the list of user apps which have already been processed.
     * @return false if the list didn't exist, true otherwise
     */
    private boolean getUserApps(HashSet<String> outExistingApps) {
        Set<String> userApps = mPrefs.getStringSet(mPackageSetKey, null);
        if (userApps == null) {
            return false;
        } else {
            outExistingApps.addAll(userApps);
            return true;
        }
    }

    /**
     * Verifies that entries corresponding to {@param users} exist and removes all invalid entries.