From 96466226c043f2d56de760a6bd2330fd3b28b35f Mon Sep 17 00:00:00 2001
From: Francesco Andreuzzi <andreuzzi.francesco@gmail.com>
Date: Thu, 16 Jun 2016 15:18:16 +0200
Subject: [PATCH] optional CompareString & suggested apps

---
 .../consolelauncher/managers/AppsManager.java | 581 ++++++++++++------
 1 file changed, 399 insertions(+), 182 deletions(-)

diff --git a/app/src/main/java/ohi/andre/consolelauncher/managers/AppsManager.java b/app/src/main/java/ohi/andre/consolelauncher/managers/AppsManager.java
index 4a2a447..a664af4 100755
--- a/app/src/main/java/ohi/andre/consolelauncher/managers/AppsManager.java
+++ b/app/src/main/java/ohi/andre/consolelauncher/managers/AppsManager.java
@@ -1,6 +1,6 @@
 package ohi.andre.consolelauncher.managers;
 
-import android.app.Activity;
+import android.annotation.TargetApi;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
@@ -9,29 +9,39 @@ import android.content.SharedPreferences;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.ResolveInfo;
+import android.os.Build;
 
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.Comparator;
-import java.util.HashSet;
-import java.util.Iterator;
+import java.util.HashMap;
 import java.util.List;
-import java.util.Set;
+import java.util.Map;
 
 import ohi.andre.comparestring.Compare;
-import ohi.andre.consolelauncher.tuils.AppInfo;
 import ohi.andre.consolelauncher.tuils.Tuils;
 
 public class AppsManager {
 
+    public static final int SHOWN_APPS = 10;
+    public static final int HIDDEN_APPS = 11;
+
     public static final int MIN_RATE = 5;
     public static final boolean USE_SCROLL_COMPARE = false;
-    private static final String HIDDENAPP_KEY = "hiddenapp_";
-    private static final String HIDDENAPP_N_KEY = "hiddenapp_n";
+
+    private final int SUGGESTED_APPS_LENGTH = 5;
+
+    private final String APPS_PREFERENCES = "appsPreferences";
+
+    private Context context;
+    private SharedPreferences.Editor prefsEditor;
     private PackageManager mgr;
 
-    private Set<AppInfo> apps;
-    private Set<AppInfo> hiddenApps;
+    private AppsHolder appsHolder;
+    private List<AppInfo> hiddenApps;
+
+    private boolean useCompareString;
 
     private BroadcastReceiver appsBroadcast = new BroadcastReceiver() {
         @Override
@@ -45,11 +55,14 @@ public class AppsManager {
         }
     };
 
-    //    constructor
-    public AppsManager(Context context) {
-        mgr = context.getPackageManager();
+    public AppsManager(Context context, boolean useCompareString) {
+        this.context = context;
+        this.mgr = context.getPackageManager();
+        this.useCompareString = useCompareString;
 
-        fill(((Activity) context).getPreferences(0));
+        SharedPreferences preferences = context.getSharedPreferences(APPS_PREFERENCES, Context.MODE_PRIVATE);
+        prefsEditor = preferences.edit();
+        fill(preferences);
 
         initAppListener(context);
     }
@@ -64,246 +77,450 @@ public class AppsManager {
     }
 
     public void fill(SharedPreferences preferences) {
-        apps = Tuils.getApps(mgr);
-
-        Set<String> hiddenPackages = readHiddenApps(preferences);
-        hiddenApps = new HashSet<>();
-
-//        remove hidden apps from apps & store coincidences in hiddenApps
-        Iterator<AppInfo> it = apps.iterator();
-        while (it.hasNext()) {
-            AppInfo app = it.next();
-            if (hiddenPackages.contains(app.packageName)) {
-                hiddenApps.add(app);
-                it.remove();
+        Map<String, AppInfo> map = createAppMap(mgr);
+        List<AppInfo> shownApps = new ArrayList<>();
+        hiddenApps = new ArrayList<>();
+
+        Map<String, ?> values;
+        try {
+            values = preferences.getAll();
+        } catch (Exception e) {
+            for(Map.Entry<String, AppInfo> entry : map.entrySet()) {
+                shownApps.add(entry.getValue());
             }
+            appsHolder = new AppsHolder(shownApps);
+            return;
         }
 
-//        add company name if necessary (Google/FaceBook Messenger)
-        apps = checkEquality(apps);
-        hiddenApps = checkEquality(hiddenApps);
-    }
+        for(Map.Entry<String, ?> entry : values.entrySet()) {
+            if(entry.getValue() instanceof Boolean) {
+                if((Boolean) entry.getValue()) {
+                    AppInfo info = map.get(entry.getKey());
+                    hiddenApps.add(info);
+                    map.remove(entry.getKey());
+                }
+            } else {
+                map.get(entry.getKey()).launchedTimes = (Integer) entry.getValue();
+            }
+        }
 
-    //    add a new app (onPackageAdded listener)
-    private void add(String packageName) {
-        try {
-            ApplicationInfo info = mgr.getApplicationInfo(packageName, 0);
-            AppInfo app = new AppInfo(packageName, info.loadLabel(mgr).toString());
-            apps.add(app);
-        } catch (NameNotFoundException e) {
-            e.printStackTrace();
+        for (Map.Entry<String, AppInfo> stringAppInfoEntry : map.entrySet()) {
+            AppInfo app = stringAppInfoEntry.getValue();
+            shownApps.add(app);
         }
 
-        apps = checkEquality(apps);
+        appsHolder = new AppsHolder(shownApps);
+        AppUtils.checkEquality(hiddenApps);
     }
 
-    //    as below, but remove
-    private void remove(String packageName) {
-        Iterator<AppInfo> it = apps.iterator();
-
-        AppInfo app;
-        while (it.hasNext()) {
-            app = it.next();
-            if (app.packageName.equals(packageName)) {
-                it.remove();
-                break;
-            }
-        }
-    }
+    private Map<String, AppInfo> createAppMap(PackageManager mgr) {
+        Map<String, AppInfo> map = new HashMap<>();
 
-    //    find a package using its public label
-//    notice that it can be an app or an hidden app (depends on appList parameter)
-    public String findPackage(Set<AppInfo> appList, String name) {
-        String label = Compare.similarString(labelSet(appList), name, MIN_RATE, USE_SCROLL_COMPARE);
-        if (label == null)
-            return null;
+        Intent i = new Intent(Intent.ACTION_MAIN, null);
+        i.addCategory(Intent.CATEGORY_LAUNCHER);
+        List<ResolveInfo> infos = mgr.queryIntentActivities(i, 0);
 
-        Iterator<AppInfo> it = appList.iterator();
-        AppInfo app;
-        while (it.hasNext()) {
-            app = it.next();
-            if (app.publicLabel.equals(label))
-                return app.packageName;
+        for (ResolveInfo info : infos) {
+            AppInfo app = new AppInfo(info.activityInfo.packageName, info.loadLabel(mgr).toString());
+            map.put(info.activityInfo.packageName, app);
         }
 
-        return null;
+        return map;
+    }
+
+    private void add(String packageName) {
+        try {
+            ApplicationInfo info = mgr.getApplicationInfo(packageName, 0);
+            AppInfo app = new AppInfo(packageName, info.loadLabel(mgr).toString(), 0);
+            appsHolder.add(app);
+        } catch (NameNotFoundException e) {}
+    }
+
+    private void remove(String packageName) {
+        appsHolder.remove(packageName);
     }
 
     public String findPackage(String name) {
-        Set<AppInfo> allApps = new HashSet<>(apps);
+        List<AppInfo> allApps = new ArrayList<>(appsHolder.getApps());
         allApps.addAll(hiddenApps);
-        return findPackage(allApps, name);
+        return findPackage(allApps, null, name);
     }
 
-    //    find the Application intent from an input
-    public Intent getIntent(String packageName) {
-        return mgr.getLaunchIntentForPackage(packageName);
+    public String findPackage(List<AppInfo> appList, List<String> labels, String name) {
+        name = Compare.removeSpaces(name).toLowerCase();
+        if(labels == null) {
+            labels = AppUtils.labelList(appList);
+        }
+
+        if(useCompareString) {
+            String label = Compare.similarString(labels, name, MIN_RATE, USE_SCROLL_COMPARE);
+            if (label == null) {
+                return null;
+            }
+
+            for(AppInfo info : appList) {
+                if (info.publicLabel.equals(name)) {
+                    return info.packageName;
+                }
+            }
+        } else {
+            for(AppInfo info : appList) {
+                if(name.equals(Compare.removeSpaces(info.publicLabel.toLowerCase()))) {
+                    return info.packageName;
+                }
+            }
+        }
+
+        return null;
     }
 
-    //    read hidden apps
-    private Set<String> readHiddenApps(SharedPreferences preferences) {
-        int n = preferences.getInt(HIDDENAPP_N_KEY, 0);
+    public String findPackage(String name, int type) {
+        List<AppInfo> appList;
+        List<String> labelList;
+        if(type == SHOWN_APPS) {
+            appList = appsHolder.getApps();
+            labelList = appsHolder.getAppLabels();
+        } else {
+            appList = hiddenApps;
+            labelList = AppUtils.labelList(appList);
+        }
 
-        Set<String> hiddenPackages = new HashSet<>();
-        for (int count = 0; count < n; count++)
-            hiddenPackages.add(preferences.getString(HIDDENAPP_KEY + count, null));
-        return hiddenPackages;
+        return findPackage(appList, labelList, name);
     }
 
-    //    store hidden apps in shared preferences
-    private void storeHiddenApps(SharedPreferences.Editor editor, Set<String> hiddenPackages) {
-        int n = hiddenPackages.size();
+    public Intent getIntent(String packageName) {
+        AppInfo info = AppUtils.findAppInfo(packageName, appsHolder.getApps());
+        if(info == null) {
+            return null;
+        }
 
-        editor.putInt(HIDDENAPP_N_KEY, n);
+        info.launchedTimes++;
+        appsHolder.updateSuggestion(info);
 
-        Iterator<String> it = hiddenPackages.iterator();
-        for (int count = 0; count < n; count++)
-            editor.putString(HIDDENAPP_KEY + count, it.next());
+        prefsEditor.putInt(packageName, info.launchedTimes);
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.GINGERBREAD) {
+            applyPrefs();
+        } else {
+            prefsEditor.commit();
+        }
+
+        return mgr.getLaunchIntentForPackage(packageName);
     }
 
-    //    hide an app
-    public String hideApp(SharedPreferences.Editor editor, String packageName) {
-        Iterator<AppInfo> it = apps.iterator();
-        while (it.hasNext()) {
-            AppInfo i = it.next();
-            if (i.packageName.equals(packageName)) {
-                it.remove();
-                hiddenApps.add(i);
-                hiddenApps = checkEquality(hiddenApps);
+    public String hideApp(String packageName) {
+        AppInfo info = AppUtils.findAppInfo(packageName, appsHolder.getApps());
+        if(info == null) {
+            return null;
+        }
 
-                storeHiddenApps(editor, packageSet(hiddenApps));
+        appsHolder.remove(packageName);
+        hiddenApps.add(info);
+        AppUtils.checkEquality(hiddenApps);
 
-                return i.publicLabel;
-            }
+        prefsEditor.putBoolean(packageName, true);
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.GINGERBREAD) {
+            applyPrefs();
+        } else {
+            prefsEditor.commit();
         }
 
-        return null;
+        return info.publicLabel;
     }
 
-    //    unhide an app
-    public String unhideApp(SharedPreferences.Editor editor, String packageName) {
+    public String unhideApp(String packageName) {
 
-        Iterator<AppInfo> it = hiddenApps.iterator();
-        while (it.hasNext()) {
-            AppInfo i = it.next();
-            if (i.packageName.equals(packageName)) {
-                it.remove();
-                apps.add(i);
+        AppInfo info = AppUtils.findAppInfo(packageName, hiddenApps);
+        if(info == null) {
+            return null;
+        }
 
-                storeHiddenApps(editor, packageSet(hiddenApps));
+        hiddenApps.remove(info);
+        appsHolder.add(info);
 
-                return i.publicLabel;
-            }
+        prefsEditor.putBoolean(packageName, false);
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.GINGERBREAD) {
+            applyPrefs();
+        } else {
+            prefsEditor.commit();
         }
 
-        return null;
+        return info.publicLabel;
     }
 
-    //    return a set of labels in AppInfos
-    private Set<String> labelSet(Set<AppInfo> infos) {
-        Set<String> set = new HashSet<>();
+    public List<String> getAppLabels() {
+        return appsHolder.getAppLabels();
+    }
 
-        Iterator<AppInfo> it = infos.iterator();
-        while (it.hasNext())
-            set.add(it.next().publicLabel);
+    public String[] getSuggestedApps() {
+        return appsHolder.getSuggestedApps();
+    }
 
-        return set;
+    public String printApps(int type) {
+        List<String> labels = type == SHOWN_APPS ? appsHolder.appLabels : AppUtils.labelList(hiddenApps);
+        return AppUtils.printApps(labels);
     }
 
-    //    return a set of packages in AppInfos
-    private Set<String> packageSet(Set<AppInfo> infos) {
-        Set<String> set = new HashSet<>();
+    public void unregisterReceiver(Context context) {
+        context.unregisterReceiver(appsBroadcast);
+    }
 
-        Iterator<AppInfo> it = infos.iterator();
-        while (it.hasNext())
-            set.add(it.next().packageName);
+    public void onDestroy() {
+        unregisterReceiver(context);
+    }
 
-        return set;
+    @TargetApi(Build.VERSION_CODES.GINGERBREAD)
+    private void applyPrefs() {
+        prefsEditor.apply();
     }
 
-    public String printApps() {
-        List<String> list = new ArrayList<>(labelSet(apps));
+    public static class AppInfo {
 
-        Collections.sort(list, new Comparator<String>() {
-            @Override
-            public int compare(String lhs, String rhs) {
-                return Compare.alphabeticCompare(lhs, rhs);
+        public String packageName;
+        public String publicLabel;
+        public int launchedTimes;
+
+        public AppInfo(String packageName, String publicLabel, int launchedTimes) {
+            this.packageName = packageName;
+            this.publicLabel = publicLabel;
+            this.launchedTimes = launchedTimes;
+        }
+
+        public AppInfo(String packageName, String publicLabel) {
+            this.packageName = packageName;
+            this.publicLabel = publicLabel;
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if(o == null) {
+                return false;
             }
-        });
 
-        Tuils.addPrefix(list, Tuils.DOUBLE_SPACE);
-        Tuils.insertHeaders(list, false);
-        return Tuils.toPlanString(list);
-    }
+            if(o instanceof AppInfo) {
+                AppInfo i = (AppInfo) o;
+                return this.packageName.equals(i.packageName);
+            } else if(o instanceof String) {
+                return this.packageName.equals(o);
+            }
+            return false;
+        }
 
-    public Set<AppInfo> getApps() {
-        return apps;
-    }
+        @Override
+        public String toString() {
+            return packageName + " - " + publicLabel;
+        }
 
-    public Set<String> getAppsLabels() {
-        return labelSet(apps);
+        @Override
+        public int hashCode() {
+            return packageName.hashCode();
+        }
     }
 
-    public String printHiddenApps() {
-        List<String> list = new ArrayList<>(labelSet(hiddenApps));
+    private class AppsHolder {
 
-        Collections.sort(list, new Comparator<String>() {
+        private List<AppInfo> infos;
+        private List<String> appLabels;
+        private AppInfo[] suggestedApps = new AppInfo[SUGGESTED_APPS_LENGTH];
+
+        Comparator<AppInfo> mostUsedComparator = new Comparator<AppInfo>() {
             @Override
-            public int compare(String lhs, String rhs) {
-                return Compare.alphabeticCompare(lhs, rhs);
+            public int compare(AppInfo lhs, AppInfo rhs) {
+                return rhs.launchedTimes > lhs.launchedTimes ? -1 : rhs.launchedTimes == lhs.launchedTimes ? 0 : 1;
             }
-        });
+        };
 
-        Tuils.addPrefix(list, "  ");
-        Tuils.insertHeaders(list, false);
-        return Tuils.toPlanString(list);
-    }
+        public AppsHolder(List<AppInfo> infos) {
+            this.infos = infos;
+            update(true);
+        }
 
-    private Set<AppInfo> checkEquality(Set<AppInfo> appInfoSet) {
-        List<AppInfo> list = new ArrayList<>(appInfoSet);
+        public void add(AppInfo info) {
+            infos.add(info);
+            update(false);
+        }
 
-        for (Iterator<AppInfo> iterator = appInfoSet.iterator(); iterator.hasNext(); ) {
-            AppInfo info = iterator.next();
+        public void remove(String packageName) {
+            infos.remove(packageName);
+            update(true);
+        }
 
-            for (int count = 0; count < list.size(); count++) {
-                AppInfo info2 = list.get(count);
-                if (!info.equals(info2) && info.publicLabel.equals(info2.publicLabel))
-                    list.set(count, new AppInfo(info2.packageName, getNewLabel(info2.publicLabel, info2.packageName)));
+        public void updateSuggestion(AppInfo app) {
+            int index = suggestionIndex(app);
+            if(index != -1) {
+                for(int count = suggestedApps.length - 1; count > index; count--) {
+                    AppInfo cycleInfo = suggestedApps[count];
+                    if(cycleInfo == null) {
+//                        this should not happen
+                        throw new UnsupportedOperationException("suggestion is null");
+                    }
+
+                    if(app.launchedTimes > cycleInfo.launchedTimes) {
+                        suggestedApps[index] = cycleInfo;
+                        suggestedApps[count] = app;
+                        return;
+                    }
+                }
+            } else {
+                for(int count = suggestedApps.length - 1; count >= 0; count--) {
+                    AppInfo cycleInfo = suggestedApps[count];
+                    if(cycleInfo == null || app.launchedTimes > cycleInfo.launchedTimes) {
+                        System.arraycopy(suggestedApps, 1, suggestedApps, 0, count);
+                        suggestedApps[count] = app;
+                        return;
+                    }
+                }
             }
         }
 
-        return new HashSet<>(list);
-    }
+        private int suggestionIndex(AppInfo app) {
+            for(int count = 0; count < suggestedApps.length; count++) {
+                if(app.equals(suggestedApps[count])) {
+                    return count;
+                }
+            }
+            return -1;
+        }
 
-    private String getNewLabel(String oldLabel, String packageName) {
-        try {
-            int firstDot = packageName.indexOf(".") + 1;
-            int secondDot = packageName.substring(firstDot).indexOf(".") + firstDot;
-
-            StringBuilder newLabel = new StringBuilder();
-            if (firstDot == -1) {
-                newLabel.append(packageName);
-                newLabel.append(Tuils.SPACE);
-                newLabel.append(oldLabel);
-            } else if (secondDot == -1) {
-                newLabel.append(packageName.substring(firstDot, packageName.length()));
-                newLabel.append(Tuils.SPACE);
-                newLabel.append(oldLabel);
-            } else {
-                newLabel.append(packageName.substring(firstDot, secondDot));
-                newLabel.append(Tuils.SPACE);
-                newLabel.append(oldLabel);
+        private void sort() {
+            try {
+                Collections.sort(infos, mostUsedComparator);
+            } catch (NullPointerException e) {}
+        }
+
+        private void fillLabels() {
+            appLabels = AppUtils.labelList(infos);
+        }
+
+        private void fillSuggestions() {
+            for(AppInfo info : infos) {
+                if(info.launchedTimes == 0){
+                    continue;
+                }
+                for(int count = suggestedApps.length - 1; count >= 0; count--) {
+                    AppInfo i = suggestedApps[count];
+                    if(i == null) {
+                        suggestedApps[count] = info;
+                        break;
+                    } else if(i.launchedTimes < info.launchedTimes) {
+                        suggestedApps[count] = info;
+                        if(count < suggestedApps.length - 1) {
+                            suggestedApps[count + 1] = i;
+                        }
+                        break;
+                    }
+                }
             }
+        }
 
-            String label = newLabel.toString();
-            return label.substring(0, 1).toUpperCase() + label.substring(1);
-        } catch (IndexOutOfBoundsException e) {
-            return packageName;
+        private void update(boolean refreshSuggestions) {
+            AppUtils.checkEquality(infos);
+            sort();
+            fillLabels();
+            if(refreshSuggestions) {
+                fillSuggestions();
+            }
         }
+
+        public List<String> getAppLabels() {
+            return appLabels;
+        }
+
+        public List<AppInfo> getApps() {
+            return infos;
+        }
+
+        public String[] getSuggestedApps() {
+            return AppUtils.labelList(suggestedApps);
+        }
+
     }
 
-    public void unregisterReceiver(Context context) {
-        context.unregisterReceiver(appsBroadcast);
+    private static class AppUtils {
+
+        public static void checkEquality(List<AppInfo> list) {
+            for (AppInfo info : list) {
+                for (int count = 0; count < list.size(); count++) {
+                    AppInfo info2 = list.get(count);
+                    if (!info.equals(info2) && info.publicLabel.equals(info2.publicLabel)) {
+                        list.set(count, new AppInfo(info2.packageName, getNewLabel(info2.publicLabel, info2.packageName),
+                                info2.launchedTimes));
+                    }
+                }
+            }
+        }
+
+        public static String getNewLabel(String oldLabel, String packageName) {
+            try {
+                int firstDot = packageName.indexOf(Tuils.DOT) + 1;
+                int secondDot = packageName.substring(firstDot).indexOf(Tuils.DOT) + firstDot;
+
+                StringBuilder newLabel = new StringBuilder();
+                if (firstDot == -1) {
+                    newLabel.append(packageName);
+                    newLabel.append(Tuils.SPACE);
+                    newLabel.append(oldLabel);
+                } else if (secondDot == -1) {
+                    newLabel.append(packageName.substring(firstDot, packageName.length()));
+                    newLabel.append(Tuils.SPACE);
+                    newLabel.append(oldLabel);
+                } else {
+                    newLabel.append(packageName.substring(firstDot, secondDot));
+                    newLabel.append(Tuils.SPACE);
+                    newLabel.append(oldLabel);
+                }
+
+                String label = newLabel.toString();
+                return label.substring(0, 1).toUpperCase() + label.substring(1);
+            } catch (IndexOutOfBoundsException e) {
+                return packageName;
+            }
+        }
+
+        protected static AppInfo findAppInfo(String packageName, List<AppInfo> infos) {
+            for(AppInfo info : infos) {
+                if(info.packageName.equals(packageName)) {
+                    return info;
+                }
+            }
+            return null;
+        }
+
+        public static String printApps(List<String> apps) {
+            if(apps.size() == 0) {
+                return apps.toString();
+            }
+
+            List<String> list = new ArrayList<>(apps);
+
+            Collections.sort(list, new Comparator<String>() {
+                @Override
+                public int compare(String lhs, String rhs) {
+                    return Compare.alphabeticCompare(lhs, rhs);
+                }
+            });
+
+            Tuils.addPrefix(list, Tuils.DOUBLE_SPACE);
+            Tuils.insertHeaders(list, false);
+            return Tuils.toPlanString(list);
+        }
+
+        public static List<String> labelList(List<AppInfo> infos) {
+            List<String> labels = new ArrayList<>();
+            for (AppInfo info : infos) {
+                labels.add(info.publicLabel);
+            }
+            return labels;
+        }
+
+        public static String[] labelList(AppInfo[] infos) {
+            String[] labels = new String[infos.length];
+            for(int count = 0; count < infos.length; count++) {
+                if(infos[count] != null) {
+                    labels[count] = infos[count].publicLabel;
+                }
+            }
+            return labels;
+        }
     }
 
 }
\ No newline at end of file
-- 
GitLab