public class AppLauncher implements Comparable<AppLauncher> {


    private static final Map<ComponentName,AppLauncher> mAppLaunchers = Collections.synchronizedMap(new HashMap<ComponentName,AppLauncher>());
    private static final String LINK_SEP = ":IS_APP_LINK:";
    public static final String ACTION_PACKAGE = "ACTION.PACKAGE";
    private static final String OREOSHORTCUT = "OREOSHORTCUT:";
    public static final String OLDSHORTCUT = "OLDSHORTCUT:";
    private static final int THREAD_TIMEOUT = 5;


    public static AppLauncher createAppLauncher(String activityName, String packageName, String label, String category, boolean isWidget) {
        AppLauncher app = mAppLaunchers.get(new ComponentName(packageName, activityName));
        if (app == null) {
            app = new AppLauncher(activityName, packageName, label, category, isWidget);
            mAppLaunchers.put(app.getComponentName(), app);
        }
        return app;
    }

    public static AppLauncher createAppLauncher(Context context, PackageManager pm, ResolveInfo ri) {
        return createAppLauncher(context, pm, ri, null, true);
    }

    public static AppLauncher createAppLauncher(Context context, PackageManager pm, ResolveInfo ri, String category, boolean autocat) {
        String activityName = ri.activityInfo.name;
        AppLauncher app = mAppLaunchers.get(new ComponentName(ri.activityInfo.packageName, activityName));
        if (app == null) {
            app = new AppLauncher(context, pm, ri, category, autocat);
            mAppLaunchers.put(app.getComponentName(), app);
        }
        return app;
    }

    public static AppLauncher createAppLauncher(AppLauncher launcher) {
        return createAppLauncher(launcher, false);
    }

    public static AppLauncher createAppLauncher(AppLauncher launcher, boolean copyOrig) {
        return new AppLauncher(launcher, copyOrig);
    }


//    public static AppLauncher createActionLink(String activityName, Uri linkUri, String packageName, String label, String category) {
//        activityName = makeLink(activityName, linkUri);
//        AppLauncher app = mAppLaunchers.get(new ComponentName(packageName, activityName));
//        if (app == null) {
//            app = new AppLauncher(activityName, packageName, label, category, false);
//            mAppLaunchers.put(app.getComponentName(), app);
//        }
//        return app;
//    }
//
//    public static AppLauncher createActionLink(String actionName, Uri linkUri, String label, String category) {
//
//        actionName = makeLink(actionName, linkUri);
//        AppLauncher app = mAppLaunchers.get(new ComponentName(ACTION_PACKAGE, actionName));
//        if (app == null) {
//            app = new AppLauncher(actionName, ACTION_PACKAGE, label, category, false);
//            mAppLaunchers.put(app.getComponentName(), app);
//        }
//        return app;
//    }

    public static AppLauncher createActionShortcut(Intent launchintent, String label, String category) {
        launchintent.putExtra("_LT__LINK_",  Math.random());  //so we can have multiple shortcuts
        String actionName = makeLink(OLDSHORTCUT, launchintent.toUri(0));

        AppLauncher app = mAppLaunchers.get(new ComponentName(ACTION_PACKAGE, actionName));
        if (app == null) {
            app = new AppLauncher(actionName, ACTION_PACKAGE, label, category, false);
            mAppLaunchers.put(app.getComponentName(), app);
        }
        return app;
    }


    public static AppLauncher createShortcut(Intent launchintent, String packageName, String label, String category) {
        launchintent.putExtra("_LT__LINK_",  Math.random());  //so we can have multiple shortcuts
        String actionName = makeLink(OLDSHORTCUT, launchintent.toUri(0));

        AppLauncher app = mAppLaunchers.get(new ComponentName(packageName, actionName));
        if (app == null) {
            app = new AppLauncher(actionName, packageName, label, category, false);
            mAppLaunchers.put(app.getComponentName(), app);
        }
        return app;
    }


    public static AppLauncher createOreoShortcut(String shortcutid, String packageName, String label, String category) {

        String actionName = makeLink(OREOSHORTCUT, shortcutid);
        AppLauncher app = mAppLaunchers.get(new ComponentName(packageName, actionName));
        if (app == null) {
            app = new AppLauncher(actionName, packageName, label, category, false);
            mAppLaunchers.put(app.getComponentName(), app);
        }
        return app;
    }

    public static AppLauncher getAppLauncher(ComponentName activityName) {
        return mAppLaunchers.get(activityName);
    }

    public static void removeAppLauncher(ComponentName activityName) {
        mAppLaunchers.remove(activityName);
    }

    public static void removeAppLauncher(String activityName, String packageName) {
        try {
            if (activityName==null) activityName = "";
            mAppLaunchers.remove(new ComponentName(packageName, activityName));
        } catch (Exception e) {
            Log.e("AppLauncher", e.getMessage(), e);
        }
    }

    public static void clearIcons() {
        for (AppLauncher app: mAppLaunchers.values()) {
            app.clearDrawable();
        }
        mAppLaunchers.clear();
    }


    private final String mPackageName;
    private final String mActivityName;
    private String mLabel;


    private String mCategory;
    private final boolean mWidget;

    private volatile Drawable mIconDrawable;
    final private List<ImageView> mIconImage = new ArrayList<>(1);

    private AppLauncher(String activityName, String packageName, String label, String category, boolean isWidget) {
        mActivityName = activityName;
        mPackageName = packageName;
        mLabel = label;
        mCategory = category;
        mWidget = isWidget;
//        if (mCategory==null) {
//            mCategory = Categories.getCategoryForPackage(mPackageName);
//        }
    }


    private AppLauncher(AppLauncher launcher, boolean copyOrig) {
        mActivityName = copyOrig ? launcher.getLinkBaseActivityName() : launcher.getActivityName();
        mPackageName = launcher.getPackageName();
        mLabel = launcher.getLabel();
        mCategory = launcher.getCategory();
        if (!copyOrig) {
            mIconDrawable = launcher.mIconDrawable;
        }
        mWidget = launcher.mWidget;

    }


    private AppLauncher(Context context, PackageManager pm, ResolveInfo ri, String category, boolean autocat) {
        mActivityName = ri.activityInfo.name;
        mPackageName = ri.activityInfo.packageName;
        mLabel = ri.loadLabel(pm).toString();
        if (category!=null) {
            mCategory = category;
            //Log.d("LaunchTime", mPackageName + ", " + ri.activityInfo.name + ", " + mLabel + "  cat " + category);
        } else if (autocat) {
            mCategory = Categories.getCategoryForComponent(context, mActivityName, mPackageName, true, ri.activityInfo.applicationInfo);
            if (mCategory==null) {
                mCategory = Categories.getCategoryFromPiCat(ri.activityInfo.applicationInfo);
            }

            //Log.d("LaunchTime", mPackageName + ", " + ri.activityInfo.name + ", " + mLabel + "  auto " + category);
        } else {
            mCategory = Categories.CAT_OTHER;
            //Log.d("LaunchTime", mPackageName + ", " + ri.activityInfo.name + ", " + mLabel + "  plain " + category);
        }
        mWidget = false;



        loadAppIconAsync(context);
    }


    private static String makeLink(String activityName, Uri uri) {
        String uristr = "";
        if (uri != null) uristr = uri.toString();
        return makeLink(activityName, uristr);
    }

    private static String makeLink(String activityName, String uri) {
        if (!activityName.contains(LINK_SEP)) {
            return activityName + LINK_SEP + uri;
        }
        Log.e("Link", "Activity is already a link"+ activityName, new Throwable("Activity is already a link"+ activityName));
        return activityName;
    }

    private static final String linkuri = "_link";

    public AppLauncher makeAppLink() {
        //return AppLauncher.createActionLink(getLinkBaseActivityName(), new Uri.Builder().scheme(linkuri).path(linkuri + Math.random()).build(), getPackageName(), getLabel(), getCategory());
        Intent intent = new Intent(Intent.ACTION_MAIN);
        intent.setClassName(getPackageName(), getLinkBaseActivityName());
        intent.setPackage(getPackageName());

        return AppLauncher.createShortcut(intent, intent.getPackage(), getLabel(), null);
    }

    public boolean isAppLink() {
        String uristr = getLinkUri();
        return uristr!=null && uristr.startsWith(linkuri);
    }

    public ComponentName getComponentName() {
        return new ComponentName(mPackageName, mActivityName);
    }

    public ComponentName getBaseComponentName() {
        return new ComponentName(mPackageName, getLinkBaseActivityName());
    }

    public boolean isLink() {
        return mActivityName.contains(LINK_SEP);
    }

    public String getLinkBaseActivityName() {
       return mActivityName.split(LINK_SEP,2)[0];
    }

    public String getLinkUri() {
        String [] parts = mActivityName.split(LINK_SEP,2);
        if (parts.length==2) {
            return parts[1];
        }
        return null;
    }

    public boolean isOreoShortcut() {
        return mActivityName.contains(OREOSHORTCUT);
    }
    public boolean isShortcut() {
        return mActivityName.contains(OLDSHORTCUT);
    }

    public String getLabel() {
        return mLabel;
    }

    public void setLabel(String label) {
        mLabel = label;
    }

    public String getPackageName() {
        return mPackageName;
    }

    public String getActivityName() {
        return mActivityName;
    }

    public String getCategory() {
        return mCategory;
    }

    public void setCategory(String category) {
        mCategory = category;
    }

    public boolean isWidget() {
        return mWidget;
    }

    public boolean isActionLink() {
        return mPackageName.equals(ACTION_PACKAGE);
    }

    public boolean isNormalApp() {
        return !(isWidget() || isLink() || isActionLink() || isAppLink() || isShortcut() || isOreoShortcut());
    }

    public boolean iconLoaded() {
        return mIconDrawable != null;
    }

    public void addIconImage(ImageView iconImage) {
        mIconImage.add(iconImage);
        if (mIconDrawable != null) {
            //Log.d("icon1", mLabel +  " " + mIconDrawable+"");
            iconImage.setImageDrawable(mIconDrawable);
        }
    }

    public void clearImageViews() {
        mIconImage.clear();
    }

    public Drawable getIconDrawable() {
        return mIconDrawable;
    }

    public void setIconDrawable(Drawable drawable) {
        mIconDrawable = drawable;
        if (mIconImage.size()>0) {
            //Log.d("icon2", mLabel +  " " + mIconDrawable+"");
            for (ImageView im: mIconImage) {
                im.setImageDrawable(mIconDrawable);
            }
        }
    }

    public void clearDrawable() {
        mIconDrawable = null;
        mIconImage.clear();
    }

    @Override
    public boolean equals(Object obj) {
        if (obj instanceof AppLauncher) {
            return getComponentName().equals(((AppLauncher) obj).getComponentName());
        }
        return super.equals(obj);
    }

    @Override
    public int hashCode() {
        return getComponentName().hashCode();
    }

    @Override
    public int compareTo(@NonNull AppLauncher appLauncher) {
        return this.mLabel.toLowerCase(Locale.getDefault()).compareTo(appLauncher.mLabel.toLowerCase(Locale.getDefault()));
    }

    public void loadAppIcon(Context context, Handler handler) {
        if (iconLoaded()) return;
        Drawable app_icon = null;
        try {

            String uristr = null;
            if (isActionLink()) {
                uristr = getLinkUri();
                if (uristr == null) uristr = "";
            }

            app_icon = GlobState.getIconsHandler(context).getDrawableIconForPackage(this);

            if (app_icon == null) {
                app_icon = context.getPackageManager().getDefaultActivityIcon();
            }
            if (isLink()) {
                app_icon = IconsHandler.drawLinkSymbol(app_icon, context);
            }


        } catch (Exception | Error e) {
            Log.d("loadIcon", e.getMessage(), e);
        }

        if (app_icon == null) {
            app_icon = context.getPackageManager().getDefaultActivityIcon();
        }

        final Drawable app_iconf = app_icon;

        if (handler!=null) {
            handler.post(new Runnable() {
                @Override
                public void run() {
                    setIconDrawable(app_iconf);
                }
            });
        } else {
            setIconDrawable(app_iconf);
        }
    }


    private static final BlockingQueue<AppLauncher> iconQueue = new LinkedBlockingQueue<>();
    private static final Object iconLoaderSync = new Object();
    private static IconLoaderTask iconLoader;
    private static Handler handler;

    public void loadAppIconAsync(final Context context) {
        if (iconLoaded() || isWidget()) return;

        queueIconLoad(this);

        synchronized (iconLoaderSync) {
            if (iconLoader==null || !iconLoader.isrunning) {
                if (handler==null) handler = new Handler(Looper.getMainLooper());
                iconLoader = new IconLoaderTask(context, handler);
                try {
                    //AsyncTask.THREAD_POOL_EXECUTOR.execute(iconLoader);
                    GlobState.execute(context, iconLoader);
                } catch (Throwable t) {
                    Log.e("loadAppIconAsync", t.getMessage(), t);
                    if (!iconLoader.isrunning) {
                        new Thread(iconLoader).start();
                    }

                }
            }
        }

    }

    private void queueIconLoad(AppLauncher app) {
        iconQueue.offer(app);
    }


    //We do this because we know we will process many icon loading tasks in a row (ie at startup), and
    // there's no need to restart a thread or even a runnable for each one.
    // This way we just get a thread and keep it until we're done
    private static class IconLoaderTask implements Runnable {

        volatile boolean isrunning = true;
        private final WeakReference<Context> mContextRef;
        private final WeakReference<Handler> mHandlerRef;

        private int processed=0;


        IconLoaderTask(Context context, Handler handler) {
            mContextRef = new WeakReference<>(context);
            mHandlerRef = new WeakReference<>(handler);
        }

        @Override
        public void run() {
            Log.d("IconLoaderTask", "Starting IconLoad task");

            try {
                do {
                    Context context = mContextRef.get();
                    Handler handler = mHandlerRef.get();
                    if (context == null || handler == null) {
                        Log.d("IconLoaderTask", context + " " + handler);
                        return;
                    }
                    try {
                        final AppLauncher inst = iconQueue.poll(THREAD_TIMEOUT, TimeUnit.SECONDS);
                        if (inst == null) {
                            isrunning = false;
                        } else {
                            inst.loadAppIcon(context, handler);
                            processed++;
                        }


                    } catch (InterruptedException e) {
                        Log.d("loadAppIconAsync", e.getMessage(), e);
                        isrunning = false;
                    }
                } while (isrunning);

            } finally {
                synchronized (iconLoaderSync) {
                    iconLoader = null;
                    handler = null;
                    isrunning = false;
                }
                Log.d("IconLoaderTask", "Completing IconLoad task. Processed " + processed);

            }
        }

    }


} public class Badger {

    private BadgerCountChangeListener badgerCountChangeListener;

    private final Map<ComponentName,Integer> unreadCount = new HashMap<>();

    private final SharedPreferences prefs;

    public Badger(Context context) {
        prefs = context.getSharedPreferences("badges", Context.MODE_PRIVATE);
        for (String cname: prefs.getAll().keySet()) {
            ComponentName cn = ComponentName.unflattenFromString(cname);
            if (cn!=null) unreadCount.put(cn, (prefs.getInt(cname, 0)));
        }
    }

    public void setUnreadCount(String activityName, String packageName, int count) {
        ComponentName compName = new ComponentName(packageName, activityName);
        setUnreadCount(compName,count);
    }

    private void setUnreadCount(ComponentName compName, int count) {
        unreadCount.put(compName, count);
        if (count==0) {
            prefs.edit().remove(compName.flattenToString()).apply();
        } else {
            prefs.edit().putInt(compName.flattenToString(), count).apply();
        }
        if (badgerCountChangeListener!=null) badgerCountChangeListener.badgerCountChanged(compName, count);
    }

    public int getUnreadCount(String activityName, String packageName) {
        return getUnreadCount(new ComponentName(packageName,activityName));
    }

    public int getUnreadCount(ComponentName compname) {
        Integer count = unreadCount.get(compname);
        if (count==null) return 0;
        return count;
    }

    public void setBadgerCountChangeListener(BadgerCountChangeListener badgerCountChangeListener) {
        this.badgerCountChangeListener = badgerCountChangeListener;
    }

    public void clearAll() {
        Set<ComponentName> comps = new HashSet<>(unreadCount.keySet());
        unreadCount.clear();
        for (ComponentName compName: comps) {
            setUnreadCount(compName, 0);
        }

    }

    public interface BadgerCountChangeListener {
        void badgerCountChanged(ComponentName compname, int count);
    }
} public class DefaultApps {

    //QuickBar
    public static boolean checkDefaultApps(final Context context, List<AppLauncher> launchers, List<ComponentName> quickRowOrder) {
        return checkDefaultApps(context,launchers,quickRowOrder,null);
    }

    public static boolean checkDefaultApps(final Context context, List<AppLauncher> launchers, List<ComponentName> quickRowOrder, List<String> onlyTypes) {
        if (quickRowOrder.isEmpty()) {
            Map<String, List<String>> defactivities = getDefaultActivities(context);
           // boolean addeddefault = false;
            int max = 1;
            for (List<String> tests : defactivities.values()) {
                if (tests.size() > max) max = tests.size();
            }

            AppLauncher firstapp = null;
            for (int i = 0; i < max; i++) { // try the tests in order.

                for (AppLauncher app : launchers) {
                    if (firstapp == null) firstapp = app;
                    //Log.d("Trying: ", app.getActivityName() + " " + app.getPackageName());
                    //try the app for each one of the activities
                    for (Iterator<Map.Entry<String, List<String>>> defactit = defactivities.entrySet().iterator(); defactit.hasNext(); ) {
                        Map.Entry<String, List<String>> defactent = defactit.next();

                        if (onlyTypes!=null && !onlyTypes.contains(defactent.getKey())) continue;

                        //if we have a test, search the app name
                        if (defactent.getValue().size() > i) {
                            String test = defactent.getValue().get(i);
                            if (contains(app, test)) {
                                Log.d("Using: ", app.getActivityName() + " " + app.getPackageName() + " for " + defactent.getKey());
                                quickRowOrder.add(app.getComponentName());
                                defactit.remove(); // remove this group
                               // addeddefault = true;
                                break;  //we're using this app for something
                            }
                        }
                    }
                }

            }
            if (quickRowOrder.isEmpty() && firstapp != null) { //nothing found? add first app found.
                quickRowOrder.add(firstapp.getComponentName());
            }

            return true;
        }
        return false;
    }

    private static boolean contains(AppLauncher app, String test) {
        return app.getActivityName().toLowerCase().contains(test.toLowerCase())
                || app.getPackageName().toLowerCase().contains(test.toLowerCase());
    }


    private static Map<String, List<String>> getDefaultActivities(Context context) {

        Map<String, List<String>> activities = new LinkedHashMap<>();

        try {

            ComponentName camapp = getpkg(context, MediaStore.ACTION_IMAGE_CAPTURE, null, null);
            //Log.d("DefApps", camapp.getPackageName() + " " + camapp.getClassName());
            activities.put("camera", Arrays.asList(camapp.getClassName(),
                    "cameraApp", "CameraActivity", "camera.Camera", ".camera2", ".camera", "kamera", "camera", "photo", "foto", "kam", "cam"));

            ComponentName phoneapp = getpkg(context, Intent.ACTION_DIAL, "tel:411", null);
            activities.put("phone", Arrays.asList(phoneapp.getClassName(), phoneapp.getPackageName(),
                    "DialtactsActivity", "dialer", "dial", "phone", "fone", "contacts"));

            ComponentName msgapp = getpkg(context, Intent.ACTION_MAIN, null, Intent.CATEGORY_APP_MESSAGING);
            activities.put("msg", Arrays.asList(msgapp.getClassName(), msgapp.getPackageName(),
                    "messag", "msg", "sms", "mms", "messen", "chat", "irc" ));


            ComponentName emailapp = getpkg(context, Intent.ACTION_SENDTO, "mailto:", null);
            activities.put("email", Arrays.asList(emailapp.getPackageName(),
                    "k9", "inbox", "outlook", ".email", ".mail", "com.google.android.gm", "mail"));

            ComponentName musicapp = getpkg(context, Intent.ACTION_VIEW, "file:", "audio/*");
            activities.put("music", Arrays.asList(musicapp.getPackageName(),
                    "com.spotify.music",
                    "com.pandora.android",
                    "com.google.android.music",
                    "org.gateshipone.odyssey",
                    "org.fitchfamily.android.symphony",
                    "ch.blinkenlights.android.vanilla",
                    "com.rhapsody",
                    "musicplayer",  "music.player",
                    "mp3", "music", "tunein", "radio", "song"));

            ComponentName browseapp = getpkg(context, Intent.ACTION_VIEW, "http://www", null);
            activities.put("browser", Arrays.asList(browseapp.getPackageName(),
                    "duckduckgo", "web.browser", "webbrowser", "opera.", ".firefox", "mozilla.fennec", "mozilla.focus", "chromium",
                    "uc.browser", "brave.browser", "TunnyBrowser", "emmx", ".bing.", "chrome",
                    ".browser", "browser"));


        } catch (Exception e) {
            Log.e("LaunchTime", e.getMessage(), e);
        }

        return activities;
    }

    private static ComponentName getpkg(Context context, String intentaction, String intenturi, String intentcategory) {
        return getpkg(context, intentaction, intenturi, intentcategory, null);
    }

    private static ComponentName getpkg(Context context, String intentaction, String intenturi, String intentcategory, String intenttype) {

        ComponentName cn = new ComponentName("_fakename", "_fakename");

        try {
            Intent intent;
            if (intenturi == null) {
                intent = new Intent(intentaction);
            } else {
                intent = new Intent(intentaction, Uri.parse(intenturi));
            }
            if (intentcategory != null) {
                intent.addCategory(intentcategory);
            }
            if (intenttype!=null) {
                intent.setType(intenttype);
            }

            ResolveInfo resolveInfo = context.getPackageManager().resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY);

            if (resolveInfo != null) {
                Log.d("sh", resolveInfo.activityInfo.name + " " + resolveInfo.activityInfo.packageName);
                if (resolveInfo.activityInfo.name != null && !resolveInfo.activityInfo.name.equals("com.android.internal.app.ResolverActivity")) {

                    cn = new ComponentName(resolveInfo.activityInfo.packageName, resolveInfo.activityInfo.name);
                }
            }
        } catch (Throwable t) {
            Log.e("DefApps", t.getMessage(), t);
        }
        return cn;

    }

} public class LaunchApp {


    private static final String TAG = "LT LaunchApp";

    private final Activity activity;

    public LaunchApp(Activity activity) {
        this.activity = activity;
    }

    private DB db() {
        return GlobState.getGlobState(activity).getDB();
    }
    //Run/open the thing that was clicked
    public void launchApp(String activityname, String pkgname) {
        try {
            launchApp(db().getApp(new ComponentName(pkgname, activityname)));
        } catch (Exception e) {
            Log.e(TAG, e.getMessage(), e);
        }
    }

    public void launchApp(final AppLauncher app) {
        String activityname = app.getLinkBaseActivityName();

        try {

            if (app.isOreoShortcut()) {
                launchOreoShortcut(app);
            } else if (app.isShortcut()) {
                launchShortcut(app);
            } else {
                Intent intent = getAppIntent(app);

                Log.d(TAG, "Launching " + app.getComponentName());
                if (isValidActivity(intent)) {
                    // actually start it
                    activity.startActivity(intent);
                } else {
                    Toast.makeText(activity, "Could not launch item", Toast.LENGTH_LONG).show();
                }
            }

            //log the launch
            if (app.isAppLink()) {
                if (app.isActionLink()) {
                    db().appLaunched(new ComponentName(app.getPackageName(), app.getLinkBaseActivityName()));
                } else {
                    db().appLaunched(app.getBaseComponentName());
                }
            } else {
                db().appLaunched(app.getComponentName());
            }
        } catch (Exception e) {
            Log.d(TAG, "Could not launch " + activityname, e);
            Toast.makeText(activity, "Could not launch item: " + e.getLocalizedMessage(),Toast.LENGTH_LONG).show();
        }
        //showButtonBar(false, true);
    }


    private void launchShortcut(final AppLauncher app) {

        try {
            Intent launchIntent = Intent.parseUri(app.getLinkUri(),0);
            Log.d(TAG, "Launching " + app.getComponentName());
            if (isValidActivity(launchIntent)) {
                // actually start it
                activity.startActivity(launchIntent);
            } else {
                Toast.makeText(activity, "Could not launch item", Toast.LENGTH_LONG).show();
            }

        } catch (Exception e) {
            e.printStackTrace();
        }

    }


    //Thanks to KISS: https://github.com/Neamar/KISS/blob/0e654be99665294f601c58991dd20ae8595572bd/app/src/main/java/fr/neamar/kiss/result/ShortcutsResult.java
    private void launchOreoShortcut(final AppLauncher app) {
        if (Build.VERSION.SDK_INT >= 26) {
            final LauncherApps launcherApps = activity.getSystemService(LauncherApps.class);
            // Only the default launcher is allowed to start shortcuts
            if (launcherApps==null || !launcherApps.hasShortcutHostPermission()) {
                Toast.makeText(activity, "Must be default launcher", Toast.LENGTH_LONG).show();
                return;
            }

            LauncherApps.ShortcutQuery query = new LauncherApps.ShortcutQuery();
            query.setPackage(app.getPackageName());
            query.setShortcutIds(Collections.singletonList(app.getLinkUri()));
            query.setQueryFlags(LauncherApps.ShortcutQuery.FLAG_MATCH_PINNED);

            List<UserHandle> userHandles = launcherApps.getProfiles();

            Log.d(TAG, "launching " + app.getLinkUri());

            // Find the correct UserHandle, and launch the shortcut.
            boolean disabled = false;
            for (UserHandle userHandle : userHandles) {
                List<ShortcutInfo> shortcuts = launcherApps.getShortcuts(query, userHandle);
                if (shortcuts != null) {
                    for (ShortcutInfo s: shortcuts) {
                        if (s!=null && s.isEnabled()) {
                            launcherApps.startShortcut(s, null, null);
                            return;
                        } else {
                            disabled = true;
                        }
                    }
                }
            }
            Log.d(TAG, "failed to launch " + app.getLinkUri());
            if (app.getLinkUri().matches("^https?://.*")) {
                Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(app.getLinkUri()));
                //intent.setPackage(app.getPackageName());
                activity.startActivity(intent);
            } else {
                Toast.makeText(activity, "Failed to launch shortcut. " + (disabled?"Shortcut seems to be disabled.": "May be expired."), Toast.LENGTH_LONG).show();
            }

        }
    }

    public static Intent getAppIntent(final AppLauncher app) {
        String activityname = app.getLinkBaseActivityName();
        String packagename = app.getPackageName();

        Intent intent;
        try {
            String uristr = app.getLinkUri();
            Uri uri = null;
            if (uristr != null && !uristr.equals("")) {
                uri = Uri.parse(uristr);
            }

            if (app.isShortcut()) {
                intent = Intent.parseUri(app.getLinkUri(), 0);
            } else if (app.isActionLink()) {
                //Change "CALL" to "DIAL" so we can avoid needing the
                // android.permission.CALL_PHONE permission
                if (activityname.startsWith("android.intent.action.CALL")) {
                    activityname = "android.intent.action.DIAL";
                }

                if (uri == null) {
                    intent = new Intent(activityname);
                } else {
                    intent = new Intent(activityname, uri);
                }


            } else {
                intent = new Intent();
                intent.setClassName(packagename, activityname);
                if (uri != null) {
                    intent.setAction(Intent.ACTION_VIEW);
                    intent.setData(uri);
                } else {
                    intent.setAction(Intent.ACTION_MAIN);
                }
            }
            // Log.d("launch", activityname + "  " + uristr);
            //needed to place in the open apps list
            intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        } catch (Exception e) {
            Log.d(TAG, "Could not launch " + activityname, e);
            intent = new Intent();
        }
        return intent;

    }

    public boolean isValidActivity(AppLauncher app) {
        return isValidActivity(getAppIntent(app));
    }

    private boolean isValidActivity(Intent intent) {
        List<ResolveInfo> list = activity.getPackageManager().queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY);

        if (list.size() > 0) {
            ActivityInfo ai = list.get(0).activityInfo;
            if (ai != null && (ai.permission==null || activity.checkCallingOrSelfPermission(ai.permission)==PackageManager.PERMISSION_GRANTED)) {
                int enabledflag = activity.getPackageManager().getComponentEnabledSetting(intent.getComponent());

                return ai.exported && (enabledflag == PackageManager.COMPONENT_ENABLED_STATE_ENABLED || enabledflag == PackageManager.COMPONENT_ENABLED_STATE_DEFAULT && ai.enabled);
                //return ai.enabled && ai.exported;
            }
        }
        return false;
    }

} @SuppressLint("ApplySharedPref")
public class Theme {


    //public final static String NEW_SYS = "newish";
    public static final String PREFS_UPDATE_KEY = "prefsUpdate";

    private enum Thing {Mask, Text, AltText, Background, AltBackground, Wallpaper}

    private final int [] COLOR_PREFS_REFS = {R.string.pref_key_icon_tint, R.string.pref_key_cattab_background,
            R.string.pref_key_cattabselected_background, R.string.pref_key_cattabselected_text,
            R.string.pref_key_cattabtextcolor, R.string.pref_key_cattabtextcolorinv,
            R.string.pref_key_wallpapercolor,  R.string.pref_key_textcolor};

    private final String [] COLOR_PREFS;

    private final Thing [] THING_MAP = {Thing.Mask, Thing.Background, Thing.AltBackground, Thing.AltText, Thing.Text, Thing.Background, Thing.Wallpaper, Thing.Text};


//    private int [] getColorDefaultsClassic()  {
//        return new int [] {getResColor(R.color.icon_tint), getResColor(R.color.cattab_background), getResColor(R.color.cattabselected_background),
//                getResColor(R.color.cattabselected_text),  getResColor(R.color.textcolor), getResColor(R.color.textcolorinv),
//                Color.TRANSPARENT,  getResColor(R.color.textcolor)};
//    }

    private int [] getColorDefaults()  {
        return new int [] {getResColor(R.color.icon_tint), getResColor(R.color.cattab_background), getResColor(R.color.cattabselected_background),
                getResColor(R.color.cattabselected_text),  getResColor(R.color.textcolor), getResColor(R.color.textcolorinv),
                getResColor(R.color.wallpaper_color),  getResColor(R.color.textcolor)};
    }


    private final Context ctx;


    private final Map<String, BuiltinTheme> builtinThemes = new LinkedHashMap<>();

    private final IconsHandler iconsHandler;

    private final SharedPreferences prefs;

    Theme(Context ctx, IconsHandler ich) {
        this.ctx = ctx;

        COLOR_PREFS = new String[COLOR_PREFS_REFS.length];
        for (int i=0; i<COLOR_PREFS_REFS.length; i++) {
            COLOR_PREFS[i] = ctx.getString(COLOR_PREFS_REFS[i]);
        }

        prefs = PreferenceManager.getDefaultSharedPreferences(ctx.getApplicationContext());
        iconsHandler = ich;
        initBuiltinIconThemes();

    }



    //TODO: load these from a file / other package

    private void initBuiltinIconThemes() {
        //builtinThemes.put(IconsHandler.DEFAULT_PACK, new DefaultTheme(IconsHandler.DEFAULT_PACK, ctx.getString(R.string.icons_pack_default_name)));

        int[] defcolors = getColorDefaults();
        BuiltinTheme newish = new MonochromeTheme(IconsHandler.DEFAULT_PACK, ctx.getString(R.string.icons_pack_default_name))
                .setColor(Thing.Mask, defcolors[0])
                .setColor(Thing.Text, defcolors[4])
                .setColor(Thing.AltText, defcolors[3])
                .setColor(Thing.Background, defcolors[1])
                .setColor(Thing.Wallpaper, defcolors[6])
                .setColor(Thing.AltBackground, defcolors[2]);

        builtinThemes.put(newish.getPackKey(), newish);


        BuiltinTheme classic = new MonochromeTheme("classic", ctx.getString(R.string.theme_classic))
                .setColor(Thing.Mask, Color.TRANSPARENT)
                .setColor(Thing.Text, getResColor(R.color.textcolor_classic))
                .setColor(Thing.AltText, Color.WHITE)
                .setColor(Thing.Background, getResColor(R.color.cattab_background_classic))
                .setColor(Thing.Wallpaper, Color.TRANSPARENT)
                .setColor(Thing.AltBackground, getResColor(R.color.cattabselected_background_classic));

        builtinThemes.put(classic.getPackKey(), classic);


        BuiltinTheme crystal1= new MonochromeTheme("crystal1", ctx.getString(R.string.theme_crystal))
                .setColor(Thing.Mask, Color.parseColor("#CC888888"))
                .setColor(Thing.Text, Color.argb(255,240,240,240))
                .setColor(Thing.AltText, Color.WHITE)
                .setColor(Thing.Wallpaper, Color.parseColor("#772955A8"))
                .setColor(Thing.Background, Color.argb(20,250,250,255))
                .setColor(Thing.AltBackground, Color.argb(90,250,250,255));

        builtinThemes.put(crystal1.getPackKey(), crystal1);


        BuiltinTheme charcoal = new MonochromeTheme("charcoal", ctx.getString(R.string.theme_charcoal))
                .setColor(Thing.Mask, Color.parseColor("#4F1D1D1D"))
                .setColor(Thing.Text, Color.parseColor("#EFBBBBBB"))
                .setColor(Thing.AltText, Color.LTGRAY)
                .setColor(Thing.Wallpaper, Color.parseColor("#C2505050"))
                .setColor(Thing.Background, Color.parseColor("#DD434343"))
                .setColor(Thing.AltBackground, Color.parseColor("#e3353535"));

        builtinThemes.put(charcoal.getPackKey(), charcoal);

        BuiltinTheme paper= new MonochromeTheme("whitepaper", ctx.getString(R.string.theme_paper))
                .setColor(Thing.Mask, Color.parseColor("#CB404040"))
                .setColor(Thing.Text, Color.argb(255,20,20,20))
                .setColor(Thing.AltText, Color.DKGRAY)
                .setColor(Thing.Wallpaper, Color.parseColor("#DBF2F2F2"))
                .setColor(Thing.Background, Color.parseColor("#C3F7F7F7"))
                .setColor(Thing.AltBackground, Color.parseColor("#9FEFF2F2"));

        builtinThemes.put(paper.getPackKey(), paper);

        BuiltinTheme transp = new MonochromeTheme("transparent", ctx.getString(R.string.theme_transp))
                .setColor(Thing.Mask, Color.TRANSPARENT)
                .setColor(Thing.Text, Color.argb(255,240,240,240))
                .setColor(Thing.AltText, Color.WHITE)
                .setColor(Thing.Background, Color.TRANSPARENT)
                .setColor(Thing.Wallpaper, Color.parseColor("#77132951"))
                .setColor(Thing.AltBackground, Color.TRANSPARENT);

        builtinThemes.put(transp.getPackKey(), transp);



        int [] ucolors = {Color.argb(127,50,50,50), Color.argb(127,10,10,160), Color.argb(127,170,10,10)};
        int [] ubcolors = {Color.BLACK, Color.argb(127,0,0,60), Color.argb(127,60,0,6)};
        for (int i=1; i<=3; i++) {
            BuiltinTheme u = new MonochromeTheme("user" + i, ctx.getString(R.string.user_theme, i))
                    .setColor(Thing.Mask, Color.TRANSPARENT)
                    .setColor(Thing.Text, Color.argb(255,230,230,230))
                    .setColor(Thing.AltText, Color.WHITE)
                    .setColor(Thing.Background, ubcolors[i-1])
                    .setColor(Thing.Wallpaper, ubcolors[i-1])
                    .setColor(Thing.AltBackground, ucolors[i-1]);

            builtinThemes.put(u.getPackKey(), u);
        }


        BuiltinTheme bwicon = new MonochromeTheme("bwicon", ctx.getString(R.string.theme_bw))
                .setColor(Thing.Mask, Color.WHITE)
                .setColor(Thing.Text, Color.argb(255,220,220,220))
                .setColor(Thing.AltText, Color.WHITE)
                .setColor(Thing.Background, Color.BLACK)
                .setColor(Thing.Wallpaper, Color.BLACK)
                .setColor(Thing.AltBackground, Color.parseColor("#ff222222"));

        builtinThemes.put(bwicon.getPackKey(), bwicon);


        BuiltinTheme termcap = new MonochromeTheme("termcap", ctx.getString(R.string.theme_termcap))
                .setColor(Thing.Mask, Color.parseColor("#dd22ff22"))
                .setColor(Thing.Text, Color.parseColor("#dd22ff22"))
                .setColor(Thing.AltText, Color.parseColor("#dd22ff22"))
                .setColor(Thing.Background, Color.BLACK)
                .setColor(Thing.Wallpaper, Color.BLACK)
                .setColor(Thing.AltBackground, Color.parseColor("#dd112211"));

        builtinThemes.put(termcap.getPackKey(), termcap);


        BuiltinTheme coolblue = new MonochromeTheme("coolblue", ctx.getString(R.string.theme_coolblue))
                .setColor(Thing.Mask, Color.parseColor("#ff1111ff"))
                .setColor(Thing.Text, Color.parseColor("#eeffffff"))
                .setColor(Thing.AltText, Color.parseColor("#eeffffff"))
                .setColor(Thing.Background, Color.parseColor("#88000077"))
                .setColor(Thing.Wallpaper, Color.parseColor("#55000077"))
                .setColor(Thing.AltBackground, Color.parseColor("#881111ff"));

        builtinThemes.put(coolblue.getPackKey(), coolblue);

        BuiltinTheme redplanet = new MonochromeTheme("redplanet", ctx.getString(R.string.theme_redplanet))
                .setColor(Thing.Mask, Color.parseColor("#ffff2222"))
                .setColor(Thing.Text, Color.parseColor("#eeff2222"))
                .setColor(Thing.AltText, Color.parseColor("#eeff2222"))
                .setColor(Thing.Background, Color.parseColor("#77550000"))
                .setColor(Thing.Wallpaper, Color.parseColor("#33550000"))
                .setColor(Thing.AltBackground, Color.parseColor("#22121111"));

        builtinThemes.put(redplanet.getPackKey(), redplanet);

        BuiltinTheme ladypink = new MonochromeTheme("ladypink", ctx.getString(R.string.theme_ladypink))
                .setColor(Thing.Mask, Color.parseColor("#ffff1493"))
                .setColor(Thing.Text, Color.parseColor("#eeffffff"))
                .setColor(Thing.AltText, Color.parseColor("#eeffc0cb"))
                .setColor(Thing.Background, Color.parseColor("#ffff69b4"))
                .setColor(Thing.Wallpaper, Color.parseColor("#88ff69b4"))
                .setColor(Thing.AltBackground, Color.parseColor("#ffff1493"));

        builtinThemes.put(ladypink.getPackKey(), ladypink);
    }

    public  Map<String, BuiltinTheme> getBuiltinIconThemes() {
        return builtinThemes;
    }


    public boolean isBuiltinTheme(String packagename) {
        return builtinThemes.containsKey(packagename);
    }

    public BuiltinTheme getBuiltinTheme(String packagename) {
        return builtinThemes.get(packagename);
    }


    public boolean isBuiltinThemeIconTintable(String packagename) {
        return isBuiltinTheme(packagename) && (builtinThemes.get(packagename) instanceof MonochromeTheme);
    }



    private int getResColor(int res) {
        if (Build.VERSION.SDK_INT >= 23) {
            return ctx.getColor(res);
        } else {
            return ctx.getResources().getColor(res);
        }
    }


    private int getCurrentThemeColor(String pref) {
        BuiltinTheme theme = builtinThemes.get(iconsHandler.getIconsPackPackageName());
        if (theme!=null && theme.hasColors()) {
            int max = COLOR_PREFS.length;
            for (int i=0; i<max; i++) {
                if (pref.equals(COLOR_PREFS[i])) {
                    return theme.getColor(THING_MAP[i]);
                }
            }
        }

        int [] colorDefaults = getColorDefaults();
        int max = COLOR_PREFS.length;
        for (int i=0; i<max; i++) {
            if (pref.equals(COLOR_PREFS[i])) {
                return colorDefaults[i];
            }
        }
        throw new IllegalArgumentException("No such preference '" + pref + "'");
    }


    private String getThemePrefName(String pref) {
        return "theme_" + iconsHandler.getIconsPackPackageName() + "_" + pref;
    }



    public void resetUserColors() {


        SharedPreferences.Editor themeedit = ctx.getSharedPreferences("theme", Context.MODE_PRIVATE).edit();

        prefs.edit().putBoolean(Theme.PREFS_UPDATE_KEY, true).apply();
        SharedPreferences.Editor appedit = prefs.edit();

        try {

            int max = COLOR_PREFS.length;
            for (String COLOR_PREF : COLOR_PREFS) {
                appedit.putInt(COLOR_PREF, getCurrentThemeColor(COLOR_PREF));
                //themeedit.putInt(getThemePrefName(COLOR_PREFS[i]),  getCurrentThemeColor(COLOR_PREFS[i]));
                themeedit.remove(getThemePrefName(COLOR_PREF));
            }

        } finally {
            appedit.apply();
            prefs.edit().remove(PREFS_UPDATE_KEY).apply();
            themeedit.apply();
        }
    }


    public void saveUserColors() {

        SharedPreferences appprefs = prefs;
        SharedPreferences.Editor themeedit = ctx.getSharedPreferences("theme",Context.MODE_PRIVATE).edit();

        try {

            int max = COLOR_PREFS.length;
            for (String COLOR_PREF : COLOR_PREFS) {
                themeedit.putInt(getThemePrefName(COLOR_PREF), appprefs.getInt(COLOR_PREF, getCurrentThemeColor(COLOR_PREF)));
            }

        } finally {
            themeedit.apply();
        }
    }


    public boolean restoreUserColors() {
        Log.d("Theme", "restoreUserColors");
        SharedPreferences themeprefs = ctx.getSharedPreferences("theme",Context.MODE_PRIVATE);

        prefs.edit().putBoolean(PREFS_UPDATE_KEY, true).apply();

        SharedPreferences.Editor appedit = prefs.edit();

        try {

            int max = COLOR_PREFS.length;
            for (String COLOR_PREF : COLOR_PREFS) {
                appedit.putInt(COLOR_PREF, themeprefs.getInt(getThemePrefName(COLOR_PREF), getCurrentThemeColor(COLOR_PREF)));
            }
        } finally {
            appedit.apply();
            prefs.edit().remove(PREFS_UPDATE_KEY).apply();
        }
        return themeprefs.contains(getThemePrefName(COLOR_PREFS[0]));
    }



    public Map<String,String> getUserSetts() {

        Map<String,String> setts = new LinkedHashMap<>();

        for (Map.Entry<String,?> ent : prefs.getAll().entrySet()) {
            setts.put(ent.getKey(), ent.getValue()==null?"":ent.getValue().toString());
        }
        return setts;
    }




    abstract class BuiltinTheme {

        private final String mKey;
        private final String mName;

        private final Map<Thing,Integer> mColors = new HashMap<>();

        BuiltinTheme(String key, String name) {
            this(key, name, null);
        }

        BuiltinTheme(String key, String name, Map<Thing, Integer> colors) {
            mKey = key;
            mName = name;
            if (colors != null) {
                mColors.putAll(colors);
            }

        }

        String getPackKey() {
            return mKey;
        }

        String getPackName() {
            return mName;
        }

        public abstract Drawable getDrawable(AppLauncher app);

        boolean hasColors() {
            return mColors.size()>0;
        }

        BuiltinTheme setColor(Thing thing, int color) {
            mColors.put(thing, color);
            return this;
        }

        Integer getColor(Thing thing) {
            Integer val = mColors.get(thing);
            if (val == null) val = Color.BLACK;
            return val;
        }



        void applyTheme() {

            //SharedPreferences themeprefs = ctx.getSharedPreferences("theme",Context.MODE_PRIVATE);
            Log.d("Theme", "applyTheme");

            SharedPreferences appprefs = prefs;
            prefs.edit().putBoolean(PREFS_UPDATE_KEY, true).apply();

            SharedPreferences.Editor appedit = appprefs.edit();
            try {

                int[] colorDefaults = getColorDefaults();
                int max = COLOR_PREFS.length;
                for (int i = 0; i < max; i++) {
                    if (hasColors()) {
                        appedit.putInt(COLOR_PREFS[i], getColor(THING_MAP[i]));
                    } else {
                        appedit.putInt(COLOR_PREFS[i], colorDefaults[i]);
                    }
                }
            } finally {
                appedit.apply();
                prefs.edit().remove(PREFS_UPDATE_KEY).apply();
            }

        }

    }


    private class DefaultTheme extends BuiltinTheme {

        DefaultTheme(String key, String name) {
            super(key, name);
        }

//        public DefaultTheme(String key, String name, Map<Thing, Integer> colors) {
//            super(key, name, colors);
//        }
        @Override
        public Drawable getDrawable(AppLauncher app) {
            return iconsHandler.getDefaultAppDrawable(app);
        }

    }



    private class MonochromeTheme extends BuiltinTheme {
        MonochromeTheme(String key, String name) {
            super(key, name);
        }

//        public MonochromeTheme(String key, String name, Map<Thing, Integer> colors) {
//            super(key, name, colors);
//        }

        @Override
        public Drawable getDrawable(AppLauncher app) {


            //Log.d(TAG, "getDrawable called for " + componentName.getPackageName());

            Drawable app_icon = iconsHandler.getDefaultAppDrawable(app);


            int mask_color = prefs.getInt(ctx.getString(R.string.pref_key_icon_tint), getColor(Thing.Mask));

           // Log.d("iconi", mask_color + " mask");

            IconsHandler.applyIconTint(app_icon, mask_color);


            return app_icon;
        }


    }


//    public class PolychromeTheme extends BuiltinTheme {
//        private final int [] mFGColors;
//        private final int mBGColor;
//
//        public PolychromeTheme(String key, String name, int [] color, int bgcolor) {
//            super(key, name);
//            mFGColors = Arrays.copyOf(color, color.length);
//            mBGColor = bgcolor;
//        }
//
//        @Override
//        public Drawable getDrawable(AppLauncher app) {
//
//            //Log.d(TAG, "getDrawable called for " + componentName.getPackageName());
//
//            Drawable app_icon = iconsHandler.getDefaultAppDrawable(app);
//
//            app_icon = app_icon.mutate();
//
//
//            PorterDuff.Mode mode = PorterDuff.Mode.MULTIPLY;
//
//            int color = Math.abs(app.getComponentName().getPackageName().hashCode()) % mFGColors.length;
//            app_icon.setColorFilter(mFGColors[color], mode);
//
//            return app_icon;
//        }
//
//
//    }

} public class InteractiveScrollView extends ScrollView {
    private OnPositionChangedListener mListener;
    private OnSwipeHorizontalListener mHSwipeListener;

    public InteractiveScrollView(Context context, AttributeSet attrs,
                                 int defStyle) {
        super(context, attrs, defStyle);
        init(context);
    }

    public InteractiveScrollView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init(context);
    }

    public InteractiveScrollView(Context context) {
        super(context);
        init(context);
    }

    private static final String TAG = "SV";

    private void init(Context context) {
        float density = context.getResources().getDisplayMetrics().density;
        swipelen = ViewConfiguration.get(context).getScaledPagingTouchSlop()*density;
        clickSlop = ViewConfiguration.get(context).getScaledTouchSlop()*density;
        Log.d(TAG, "swipelen=" + swipelen );
        Log.d(TAG, "clickSlop=" + clickSlop );
    }

    private float x, y;
    private long startTime;

    private float swipelen = 150;
    private float clickSlop = 10;

    @Override
    public boolean onInterceptTouchEvent(MotionEvent motionEvent) {
        //Log.d(TAG, "onInterceptTouchEvent");
        boolean isLRSwipe = shouldSwipe(motionEvent);

        return isLRSwipe || super.onInterceptTouchEvent(motionEvent);
    }

    @SuppressLint("ClickableViewAccessibility")
    @Override
    public boolean onTouchEvent(MotionEvent motionEvent) {
        //Log.d(TAG, "onTouchEvent");

        boolean isLRSwipe = shouldSwipe(motionEvent);

        return isLRSwipe || super.onTouchEvent(motionEvent);
    }

    private boolean shouldSwipe(MotionEvent motionEvent) {
        if (mHSwipeListener!=null) {
            int action = motionEvent.getActionMasked();
            switch (action) {
                case MotionEvent.ACTION_DOWN:
                    x = motionEvent.getX();
                    y = motionEvent.getY();
                    startTime = System.currentTimeMillis();
                    //Log.d(TAG, "ACTION_DOWN=" + x);
                    break;


                case MotionEvent.ACTION_MOVE:
                    //Log.d(TAG, "ACTION_MOVE=" + motionEvent.getX());
                    break;

                case MotionEvent.ACTION_UP:
                    //Log.d(TAG, "ACTION_UP=" + motionEvent.getX() + " " + x);
                    if (System.currentTimeMillis() - startTime < 200) {
                        float xdiff = motionEvent.getX() - x;
                        float ydiff = motionEvent.getY() - y;
                        //Log.d(TAG, " xdiff==" + xdiff + " " + ydiff);
                        if (Math.abs(xdiff)>Math.abs(ydiff*3/2)) {
                            if (xdiff > swipelen) {
                               // Log.d(TAG, "xdiff=" + xdiff);
                                mHSwipeListener.onLeftSwipe(xdiff);
                                return true;
                            } else if (xdiff < -swipelen) {
                               // Log.d(TAG, "xdiff=" + xdiff);
                                mHSwipeListener.onRightSwipe(xdiff);
                                return true;
                            }
                        }
                        if (Math.abs(xdiff) <= clickSlop && Math.abs(xdiff) <= clickSlop) {
                            performClick();
                        }
                    }

            }
        }
        return false;
    }


    @Override
    public boolean performClick() {
        return super.performClick();
    }

    @Override
    protected void onScrollChanged(int l, int t, int oldl, int oldt) {
        if (mListener != null) {
            View view = getChildAt(getChildCount() - 1);

            float bottom = (float) view.getBottom();

            if (bottom == 0) return;

            int diff = (int) (bottom - (getHeight() + getScrollY()));

            float percentDown = (getHeight() + getScrollY()) / bottom;
            float percentUp = (bottom - getScrollY()) / bottom;

            mListener.onPositionChanged(percentUp, percentDown, getScrollY(), diff);

        }

        super.onScrollChanged(l, t, oldl, oldt);
    }


    public OnPositionChangedListener getOnPositionChangedListener() {
        return mListener;
    }

    public void setOnPositionChangedListener(
            OnPositionChangedListener onPositionChangedListener) {
        mListener = onPositionChangedListener;
    }


    public OnSwipeHorizontalListener getHSwipeListener() {
        return mHSwipeListener;
    }

    public void setHSwipeListener(OnSwipeHorizontalListener mHSwipeListener) {
        this.mHSwipeListener = mHSwipeListener;
    }

    // public enum InteractiveScrollViewPosition {Top, Body, Bottom}

    /**
     * Event listener.
     */
    public interface OnPositionChangedListener {
        void onPositionChanged(float percentUp, float percentDown, int distFromTop, int distFromBottom);
    }

    public interface OnSwipeHorizontalListener {
        void onLeftSwipe(float absDist);
        void onRightSwipe(float absDist);

    }
} public class ActionMenu {

    private static final String TAG = "ActionMenu";

    private final MainActivity mMain;

    private boolean mUseActionMenus = false;
    private boolean mDevModeActivities = true;
    private boolean mUseDropZones = true;
    private boolean mUseExtraActions = false;

    private PopupWindow mAppinfoWindow;
    private final ScrollView mShortcutActionsPopup;
    private final LinearLayout mShortcutActionsList;
    private final Style mStyle;
    private int mAnimationDuration = 100;

    private int mOldNum = 0;
    private ViewGroup mIconBar = null;
    private final boolean mUseIcons = true;
    
    private Point mScreenDim;
    private final int mIconW;


    public ActionMenu(MainActivity main) {
        mMain = main;
        mShortcutActionsPopup = mMain.findViewById(R.id.action_menu);
        mShortcutActionsList = mMain.findViewById(R.id.action_menu_items);
        mStyle = GlobState.getStyle(mMain);

        mScreenDim = mMain.getScreenDimensions();
        readActionMenuConfig();
        mIconW = mMain.getResources().getDimensionPixelSize(R.dimen.action_icon_width);
    }

    public void readActionMenuConfig() {
        mUseDropZones = true;
        mUseExtraActions = false;
        mDevModeActivities = false;

        mUseActionMenus = mMain.mAppPreferences.getBoolean(mMain.getString(R.string.pref_key_show_action_menus), Build.VERSION.SDK_INT >= 25);

        if (mUseActionMenus) {
            mUseExtraActions = mMain.mAppPreferences.getBoolean(mMain.getString(R.string.pref_key_show_action_extra), Build.VERSION.SDK_INT >= 25);
            mDevModeActivities = mMain.mAppPreferences.getBoolean(mMain.getString(R.string.pref_key_show_action_activities), false);
            if (mUseExtraActions) {
                mUseDropZones = mMain.mAppPreferences.getBoolean(mMain.getString(R.string.pref_key_show_dropzones), false);
            }
        }

    }

    public void setAnimationDuration(int animationDuration) {
        mAnimationDuration = animationDuration;
    }

    public boolean useActionMenus() {
        return mUseActionMenus;
    }

    public boolean useDropZones() {
        return mUseDropZones;
    }

    public boolean useExtraActions() {
        return mUseExtraActions;
    }

    public boolean displayActionShortcuts(final View view, final AppLauncher appitem) {

        //Log.d(TAG, appitem.getPackageName() + " " +  appitem.getBaseComponentName());

        List<ShortcutInfo> shortcutInfos = getOreoShortcutInfos(appitem);

        try {
            initializeActionMenu();

            if (appitem.isWidget()) {
                addWidgetActionsToMenu(appitem);
            }

            if (!appitem.isWidget()) {
                addActionMenuItem(appitem.getLabel(), appitem.getIconDrawable(), new Runnable() {
                    @Override
                    public void run() {
                        mMain.launchApp(appitem);
                        mMain.showButtonBar(false, true);
                    }
                });
            }


            addOreoShortcutsToMenu(shortcutInfos);

            addDevModeActivitiesToMenu(appitem);

//            addWidgetsToMenu(view, appitem);

            addExtraActionsToMenu(view, appitem);

            addCancelToMenu();

            showBuiltActionMenu(view);

            return true;


        } catch (Exception e) {
            Log.e(TAG, "Couldn't create menu", e);
        }


        return false;
    }

    private void addWidgetActionsToMenu(final AppLauncher appitem) {
        addActionMenuItem(mMain.getString(R.string.resize), android.R.drawable.arrow_up_float, new Runnable() {
            @Override
            public void run() {
                mMain.showWidgetResize(appitem);
                mMain.showButtonBar(false, true);
            }
        });


        final Widget wh = GlobState.getWidgetHelper(mMain);

        if (wh.getConfigure(appitem.getComponentName())!=null) {
            addActionMenuItem(mMain.getString(R.string.configure_widget), android.R.drawable.ic_menu_preferences, new Runnable() {
                @Override
                public void run() {
                    try {
                        wh.configureWidget(mMain, appitem.getComponentName());
                        mMain.showButtonBar(false, true);
                    } catch (Throwable t) {
                        Log.e(TAG, "Couldn't add configure", t);
                    }
                }
            });
        }
    }

    private void addCancelToMenu() {
        addActionMenuItem(mMain.getString(android.R.string.cancel),
                android.R.drawable.ic_menu_close_clear_cancel,
                mUseIcons && mIconBar!=null,
                new Runnable() {
                    @Override
                    public void run() {
                        dismissActionPopup();
                    }
                });
    }

    public void showCatagoryActionMenu(TextView categoryTab) {
        initializeActionMenu();
        addExtraActionsToMenu(categoryTab);
        addCancelToMenu();
        showBuiltActionMenu(categoryTab);
    }

    private int mItemBGColor;

    private void initializeActionMenu() {
        mScreenDim = mMain.getScreenDimensions();

        mShortcutActionsList.removeAllViews();

        mItemBGColor = mStyle.getCattabBackground();
        int textcolor = mStyle.getCattabTextColor();

        int bgcolor = mStyle.getCalculatedWallpaperColor();

        if (Color.alpha(bgcolor) > 90) {

            int red = Color.red(bgcolor);
            int green = Color.green(bgcolor);
            int blue = Color.blue(bgcolor);
            int alpha = 200;
            if (Color.alpha(mItemBGColor)<120) alpha = 235;

            bgcolor = Color.argb(alpha, red, green, blue);
        } else {
            bgcolor = Color.argb(235, 128, 128, 128);
            mItemBGColor = Color.argb(240, 255-Color.red(textcolor), 255-Color.green(textcolor), 255-Color.blue(textcolor));

        }

        int red = Color.red(bgcolor);
        int green = Color.green(bgcolor);
        int blue = Color.blue(bgcolor);
        bgcolor = Color.argb(220, red<200?red+10:red, green<200?green+10:green, blue<200?blue+20:blue);


        mShortcutActionsPopup.setBackgroundColor(bgcolor);

        if (mStyle.isRoundedTabs()) {
            mShortcutActionsPopup.setBackground(mStyle.getBgDrawableFor(mShortcutActionsPopup, bgcolor));
        }
        mIconBar = null;
    }

    private void showBuiltActionMenu(View view) {
        //mShortcutActionsPopup.setVisibility(View.VISIBLE);

        //FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams)mShortcutActionsPopup.getLayoutParams();

        int width = (int)(mMain.getResources().getDimension(R.dimen.action_menu_width) * 1.1);

        int height = (int)(mShortcutActionsList.getChildCount()
                * (mMain.getResources().getDimension(R.dimen.action_icon_width)*1.4 + 28)) + 28;

        int [] viewpos = new int[2];
        view.getLocationOnScreen(viewpos);

        boolean islefthand = mStyle.isLeftHandCategories();

        //try to put it above and centered-ish.
        int top = viewpos[1] - height - 20;
        int left = viewpos[0] + view.getWidth()/2 - width/2;

        //if too high, push to side
        if (top <= 0) {

            top = viewpos[1] + view.getHeight()/2 - height/2;

            if (top<=0) {
                top = 10;
            }


            if (islefthand) {
                if (mScreenDim.x - (viewpos[0] + view.getWidth()) > width || viewpos[0] - width < 0 || view.getWidth()>mScreenDim.x/2) {
                    left = viewpos[0] + view.getWidth();
                } else {
                    left = viewpos[0] - width;
                }
            } else {
                if (viewpos[0]+view.getWidth()/3 > width || viewpos[0] + view.getWidth()+width>mScreenDim.x || view.getWidth()>mScreenDim.x/2) {
                    left = viewpos[0] - width;
                } else {
                    left = viewpos[0] + view.getWidth();
                }
            }
        }
//
//        if (top+height>=mScreenDim.y) {
//            top = mScreenDim.y - height - 10;
//            if (left>mScreenDim.x/2) {
//                left = viewpos[0] - width;
//            } else if (left<mScreenDim.x/2) {
//                left = viewpos[0] + view.getWidth();
//            }
//        }
//


        FrameLayout.LayoutParams flp = (FrameLayout.LayoutParams)mShortcutActionsPopup.getLayoutParams();
        if (height>=mScreenDim.y-top) {
            flp.height = mScreenDim.y-100;
        } else {
            flp.height = ViewGroup.LayoutParams.WRAP_CONTENT;
        }
        mShortcutActionsPopup.setLayoutParams(flp);



        if (left <= 0) {
            left = 10;
        } else if (left + width >= mScreenDim.x) {
            left = mScreenDim.x - (int)(width*1.1);
        }

        if (mShortcutActionsPopup.getVisibility()!=View.VISIBLE) {
            mShortcutActionsPopup.setX(left);
            mShortcutActionsPopup.setY(viewpos[1]);
            mShortcutActionsPopup.setScaleY(0f);
            mShortcutActionsPopup.setAlpha(0f);


        }
        //mShortcutActionsPopup.setTop(0);
        //mShortcutActionsPopup.setLeft(0);

        mShortcutActionsPopup.setVisibility(View.VISIBLE);
        if (mAnimationDuration>0) {
            mShortcutActionsPopup.animate()
                    .x(left)
                    .y(top)
                    .alpha(1)
                    .scaleY(1)
                    .setDuration(mAnimationDuration)
                    .setListener(null)
                    .start();
        } else {
            mShortcutActionsPopup.setX(left);
            mShortcutActionsPopup.setY(top);
            mShortcutActionsPopup.setAlpha(1f);
            mShortcutActionsPopup.setScaleY(1f);
        }

        mOldNum = mShortcutActionsList.getChildCount();


//        if (mIconBar != null) {
//            int count = mIconBar.getChildCount();
//            if (count>3) {
//                LinearLayout.LayoutParams ilp = (LinearLayout.LayoutParams)mIconBar.getChildAt(0).getLayoutParams();
//                ilp.weight=.1f;
//                mIconBar.getChildAt(0).setLayoutParams(ilp);
//
//                LinearLayout.LayoutParams ilp2 = (LinearLayout.LayoutParams)mIconBar.getChildAt(count-1).getLayoutParams();
//                ilp2.weight=.1f;
//                mIconBar.getChildAt(count-1).setLayoutParams(ilp2);
//
//
//            }
//        }
    }


    public void dismissActionPopup() {
        if (mAnimationDuration>0) {
            mShortcutActionsPopup.animate()
                    //.yBy(-mShortcutActionsPopup.getHeight())
                    .scaleY(0)
                    .alpha(0)
                    .setDuration(mAnimationDuration)
                    .setListener(new AnimatorListenerAdapter() {
                        @Override
                        public void onAnimationCancel(Animator animation) {
                            mShortcutActionsPopup.setVisibility(View.GONE);
                        }

                        @Override
                        public void onAnimationEnd(Animator animation) {
                            mShortcutActionsPopup.setVisibility(View.GONE);
                        }
                    });
        } else {
            mShortcutActionsPopup.setVisibility(View.GONE);
        }
//        if (mShortcutActionsPopup!=null) {
//            //mShortcutActionsPopup.setVisibility(View.GONE);
//            mShortcutActionsPopup = null;
//
//        }
    }



    private void addExtraActionsToMenu(final TextView categoryTab) {
        final String category = (String)categoryTab.getTag();

        if (!mMain.getCurrentCategory().equals(category)) {
            addActionMenuItem(categoryTab.getText().toString(), android.R.drawable.ic_menu_compass, new Runnable() {
                @Override
                public void run() {
                    mMain.switchCategory(category);
                }
            });
        }

        if (mUseExtraActions) {

            addActionMenuItem(mMain.getString(R.string.add_category), android.R.drawable.ic_menu_add, new Runnable() {
                @Override
                public void run() {
                    mMain.promptAddCategory();
                }
            });

            if (!Categories.isNoDropCategory((String) categoryTab.getTag())) {
                addActionMenuItem(mMain.getString(R.string.add_widgets), android.R.drawable.ic_input_add, new Runnable() {
                    @Override
                    public void run() {
                        mMain.setupWidget();
                    }
                });

                addActionMenuItem(mMain.getString(R.string.sort_category), android.R.drawable.ic_menu_sort_alphabetically, new Runnable() {
                    @Override
                    public void run() {
                        mMain.promptSortCategory(category);
                    }
                });

                addActionMenuItem(mMain.getString(R.string.cat_recategorize), android.R.drawable.ic_menu_agenda, new Runnable() {
                    @Override
                    public void run() {
                        mMain.promptRecategorize(category);
                    }
                });
            }

            if (!Categories.isHiddenCategory((String) categoryTab.getTag())) {
                int action = R.string.hide;
                if (mMain.db().isHiddenCategory(category)) action = R.string.show;
                addActionMenuItem(mMain.getString(action), android.R.drawable.ic_menu_view, new Runnable() {
                    @Override
                    public void run() {
                        mMain.hideCategory(category);

                    }
                });
            }



            addActionMenuItem(mMain.getString(R.string.rename_category), android.R.drawable.ic_menu_edit, mUseIcons, new Runnable() {
                @Override
                public void run() {
                    mMain.promptRenameCategory(category);
                }
            });

            if (!Categories.isSpeacialCategory((String) categoryTab.getTag())) {

                addActionMenuItem(mMain.getString(R.string.remove), R.drawable.trash, mUseIcons, new Runnable() {
                    @Override
                    public void run() {
                        mMain.promptDeleteCategory(category);
                    }
                });
            }
        }

    }

//    private void addWidgetsToMenu(final View view, final AppLauncher appitem) {
//        final Widget wh = GlobState.getWidgetHelper(mMain);
//        for (AppWidgetProviderInfo prov: wh.getWidgetsForPackage(appitem.getPackageName())) {
//            String label;
//            Drawable icon;
//            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
//                label = prov.loadLabel(mMain.getPackageManager());
//                icon = prov.loadIcon(mMain, 0);
//            } else {
//                label = prov.label;
//                try {
//                    icon = mMain.getPackageManager().getActivityIcon(appitem.getBaseComponentName());
//                } catch (PackageManager.NameNotFoundException e) {
//                    icon=null;
//                    e.printStackTrace();
//                }
//            }
//
//
//
//            addActionMenuItem("W-" + label, icon, false, new Runnable() {
//                @Override
//                public void run() {
//                    mMain.setupWidget();
//                }
//            });
//        }
//    }

    private void addExtraActionsToMenu(final View view, final AppLauncher appitem) {


        if (mUseExtraActions) {

            if (mMain.isOnQuickRow(view)) {
                addActionMenuItem(mMain.getString(R.string.remove), R.drawable.recycle, mUseIcons, new Runnable() {
                    @Override
                    public void run() {
                        mMain.removeViewFromQuickBar(view);
                    }
                });
            } else if (mMain.getCurrentCategory().equals(Categories.CAT_SEARCH) && !mMain.isOnSearchView(view)) {
                addActionMenuItem(mMain.getString(R.string.remove), R.drawable.recycle, mUseIcons, new Runnable() {
                    @Override
                    public void run() {
                        mMain.db().deleteAppLaunchedRecord(appitem.getComponentName());
                        mMain.populateRecentApps();
                    }
                });
            } else if (appitem.isNormalApp()) {
                addActionMenuItem(mMain.getString(R.string.uninstall_app), R.drawable.trash, new Runnable() {
                    @Override
                    public void run() {
                        mMain.launchUninstallIntent(appitem, view);
                    }
                });
                if (!Categories.isNoDropCategory(mMain.getCurrentCategory())) {
                    addActionMenuItem(mMain.getString(R.string.link), R.drawable.link, mUseIcons, new Runnable() {
                        @Override
                        public void run() {
                            mMain.makeAppLink(appitem);
                        }
                    });
                }
            } else {
                addActionMenuItem(mMain.getString(R.string.remove), R.drawable.recycle, mUseIcons, new Runnable() {
                    @Override
                    public void run() {
                        mMain.removeAppFromIconSheet(appitem);
                    }
                });
            }

        }

        addActionMenuItem(mMain.getString(R.string.appinfo_label), android.R.drawable.ic_menu_info_details, mUseIcons, new Runnable() {
            @Override
            public void run() {
                mAppinfoWindow = AppInfo.showAppinfo(mMain, view, appitem);
                mAppinfoWindow.setOnDismissListener(new PopupWindow.OnDismissListener() {
                    @Override
                    public void onDismiss() {
                        dismissAppinfo();
                    }
                });
            }
        });


    }

    private void addDevModeActivitiesToMenu(AppLauncher appitem) {
        if (mDevModeActivities) {

            class Record implements Comparable<Record>{
                private String label;
                private ComponentName component;
                private Drawable icon;

                @Override
                public int compareTo(@NonNull Record other) {
                    return this.label.compareTo(other.label);
                }
            }

            List<Record> activityItems = new ArrayList<>();

            try {
                Intent intent = new Intent(Intent.ACTION_MAIN, null);
                intent.setPackage(appitem.getPackageName());
                //intent.addCategory(Intent.CATEGORY_DEFAULT);
                final List<ResolveInfo> activities = mMain.getPackageManager().queryIntentActivities(intent, PackageManager.GET_META_DATA | PackageManager.GET_RESOLVED_FILTER);

                //ActivityInfo[] list = getPackageManager().getPackageInfo(appitem.getPackageName(), PackageManager.GET_ACTIVITIES).activities;

                List<String> names = new ArrayList<>();
                names.add(appitem.getLabel());

                List<String> bannedActivities = Arrays.asList(
                        "com.android.settings.BandMode",
                        "com.android.settings.sim.SimDialogActivity",
                        "com.android.settings.FallbackHome"
                );
                for (ResolveInfo ri : activities) {

                    try {
                        if (ri == null || ri.activityInfo == null  || !ri.activityInfo.enabled || !ri.activityInfo.exported) {
                            continue;
                        }

                        if (ri.activityInfo.name.equals(appitem.getActivityName())) continue;

                        if (ri.activityInfo.permission != null
                                && ContextCompat.checkSelfPermission(mMain.getApplicationContext(), ri.activityInfo.permission)
                                == PackageManager.PERMISSION_DENIED) {
                            continue;
                        }

                        if (bannedActivities.contains(ri.activityInfo.name)) continue;


                        CharSequence label = ri.activityInfo.loadLabel(mMain.getPackageManager());

                        ComponentName cn = new ComponentName(ri.activityInfo.packageName, ri.activityInfo.name);

                        //Log.d(TAG, label + " " + ri.activityInfo.packageName + " " + ri.activityInfo.name + " " + ri.activityInfo.permission);

                        if (label.toString().trim().equals("") || names.contains(label.toString().trim())) {
                            label = ri.activityInfo.name
                                    .replaceAll("^.*[.$]|Activity", "")
                                    .replaceAll("(\\P{Lu})(\\p{Lu})", "$1 $2");
                        }

                        names.add(label.toString().trim());

                        label = "{" + label + "}";

//                        IntentFilter fi = ri.filter;
//                        if (fi != null) {
//                            for (Iterator<String> it = fi.actionsIterator(); it != null && it.hasNext(); ) {
//                                Log.d(TAG, "  action: " + it.next());
//                            }
//                            for (Iterator<String> it = fi.categoriesIterator(); it != null && it.hasNext(); ) {
//                                Log.d(TAG, "  cat: " + it.next());
//                            }
//                            for (Iterator<String> it = fi.schemesIterator(); it != null && it.hasNext(); ) {
//                                Log.d(TAG, "  scheme: " + it.next());
//                            }
//
//                        }


                        Record item = new Record();
                        item.label = label.toString();
                        item.component = cn;
                        item.icon = ri.activityInfo.loadIcon(mMain.getPackageManager());
                        activityItems.add(item);

                    } catch (Exception e) {
                        Log.e(TAG, e.getMessage(), e);
                        Toast.makeText(mMain, "Error: " + e.getLocalizedMessage(), Toast.LENGTH_LONG).show();
                    }
                }

                Collections.sort(activityItems);

                for (Record item: activityItems) {
                    final Intent launchIntent = new Intent(Intent.ACTION_MAIN);
                    launchIntent.setComponent(item.component);
                    addActionMenuItem(item.label, item.icon, new Runnable() {
                        @Override
                        public void run() {
                            try {
                                mMain.startActivity(launchIntent);
                            } catch (Exception e) {
                                Log.e(TAG, e.getMessage(), e);
                                Toast.makeText(mMain, "Error: " + e.getLocalizedMessage(), Toast.LENGTH_LONG).show();
                            }
                        }
                    });

                }

            } catch (Exception e) {
                Log.e(TAG, "Couldn't query activities", e);
            }
        }
    }

    private void addOreoShortcutsToMenu(List<ShortcutInfo> shortcutInfos) {
        if (Build.VERSION.SDK_INT >= 25) {
            if (shortcutInfos != null && shortcutInfos.size()>0) {
                final LauncherApps launcherApps = mMain.getSystemService(LauncherApps.class);
                if (launcherApps == null) return;

                sortShorcutsByRank(shortcutInfos);

                for (final ShortcutInfo shortcutInfo : shortcutInfos) {
                    if (shortcutInfo.isDynamic()) {
                        addShortcutToActionPopup(launcherApps, shortcutInfo);
                    }
                }

                for (final ShortcutInfo shortcutInfo : shortcutInfos) {
                    if (shortcutInfo.isDeclaredInManifest()) {
                        addShortcutToActionPopup(launcherApps, shortcutInfo);
                    }
                }
            }
        }

    }

    private void sortShorcutsByRank(List<ShortcutInfo> shortcutInfos) {
        Collections.sort(shortcutInfos, new Comparator<ShortcutInfo>() {
            @Override
            public int compare(ShortcutInfo a, ShortcutInfo b) {
                if (Build.VERSION.SDK_INT >= 25) {
                    return Integer.compare(a.getRank(), b.getRank());
                }
                return 0;
            }
        });
    }

    @Nullable
    private List<ShortcutInfo> getOreoShortcutInfos(AppLauncher appitem) {
        List<ShortcutInfo> shortcutInfos = null;
        if (Build.VERSION.SDK_INT>=25) {
            final LauncherApps launcherApps = (LauncherApps) mMain.getSystemService(Context.LAUNCHER_APPS_SERVICE);

            if (launcherApps!=null && launcherApps.hasShortcutHostPermission()) {
                try {

                    LauncherApps.ShortcutQuery q = new LauncherApps.ShortcutQuery();
                    q.setQueryFlags(LauncherApps.ShortcutQuery.FLAG_MATCH_DYNAMIC | LauncherApps.ShortcutQuery.FLAG_MATCH_MANIFEST);

                    if (appitem.isShortcut()) {
                        Intent launchIntent = Intent.parseUri(appitem.getLinkUri(), 0);
                        q.setPackage(launchIntent.getPackage());
                        q.setActivity(launchIntent.getComponent());
                    } else if (appitem.isOreoShortcut()) {
                        LauncherApps.ShortcutQuery query = new LauncherApps.ShortcutQuery();
                        query.setPackage(appitem.getPackageName());
                        query.setShortcutIds(Collections.singletonList(appitem.getLinkUri()));
                        query.setQueryFlags(LauncherApps.ShortcutQuery.FLAG_MATCH_PINNED);
                        List<ShortcutInfo> shortcuts = launcherApps.getShortcuts(query,android.os.Process.myUserHandle());
                        q.setPackage(appitem.getPackageName());
                        if (shortcuts!=null && shortcuts.size()>0) {
                            q.setActivity(shortcuts.get(0).getActivity());
                        }
                    } else {
                        q.setPackage(appitem.getPackageName());
                        q.setActivity(appitem.getBaseComponentName());
                    }

                    shortcutInfos = launcherApps.getShortcuts(q, android.os.Process.myUserHandle());

                    //Log.d(TAG, "Queried shortcuts");

                } catch (Exception e) {
                    Log.e(TAG, "Couldn't query shortcuts", e);
                }
            }
        }
        return shortcutInfos;
    }

    private void addActionMenuItem(String label, int iconResource, final Runnable action) {
        addActionMenuItem(label, mMain.getResources().getDrawable(iconResource), false, action);
    }

    private void addActionMenuItem(String label, int iconResource, boolean onIconBar, final Runnable action) {
        addActionMenuItem(label, mMain.getResources().getDrawable(iconResource), onIconBar, action);
    }


    private void addActionMenuItem(String label, Drawable icon,  final Runnable action) {
        addActionMenuItem(label, icon, false, action);
    }



    private void addActionMenuItem(String label, Drawable icon, boolean onIconBar, final Runnable action) {
        final ViewGroup item;
        ImageView itemIcon;

        View.OnClickListener clickAction = new View.OnClickListener() {
            @Override
            public void onClick(View item) {
                action.run();
                dismissActionPopup();
                mMain.clearDragPotential(true);
            }
        };



        if (onIconBar && mShortcutActionsList.getChildCount()>=2) {
            if (mIconBar == null) {
                mIconBar = new LinearLayout(mMain);

                initializeMenuline(mIconBar);

            }
            itemIcon = new ImageView(mMain);
            LinearLayout.LayoutParams iilp = new LinearLayout.LayoutParams(mIconW, mIconW, 1);
//            iilp.leftMargin = mIconW;
//            iilp.rightMargin = mIconW;

            itemIcon.setLayoutParams(iilp);
            itemIcon.setOnClickListener(clickAction);
            itemIcon.setImageDrawable(icon);


            mIconBar.addView(itemIcon);
        } else {

            if (label == null) return;

            label = label.replaceAll("\\s+|\\r|\\n", " ");
            item = (ViewGroup) LayoutInflater.from(mMain).inflate(R.layout.action_menu_entry, null);


            TextView itemText = item.findViewById(R.id.action_menu_text);


//            int oldbgc = mStyle.getCattabBackground();
//            int shadowc = Color.BLACK;
//            if ((Color.red(oldbgc) + Color.green(oldbgc) + Color.blue(oldbgc)/3)<128) shadowc = Color.WHITE;
//
//            itemText.setShadowLayer(4,1,1, shadowc);
//

//            int alpha = Color.alpha(oldbgc);
//            if (alpha<128) alpha=128;
//            int newbgc = Color.argb(alpha, Color.red(oldbgc), Color.green(oldbgc), Color.blue(oldbgc));


            //itemText.setTypeface(null, Typeface.BOLD);
            itemText.setTextColor(mStyle.getCattabTextColor());
            itemText.setTextSize(TypedValue.COMPLEX_UNIT_SP, mStyle.getCategoryTabFontSize()-1);


            if (label.length()>30) label = label.substring(0,28) + "...";
            itemText.setText(label);
            itemIcon = item.findViewById(R.id.action_menu_icon);
            itemIcon.setImageDrawable(icon);

            initializeMenuline(item);

            item.setOnClickListener(clickAction);
        }


    }


    private void initializeMenuline(final ViewGroup item) {


        item.setBackgroundColor(mItemBGColor);
        if (mStyle.isRoundedTabs()) {
            item.setBackground(mStyle.getBgDrawableFor(item, mItemBGColor));
        }
        LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams((int)mMain.getResources().getDimension(R.dimen.action_menu_width), ViewGroup.LayoutParams.WRAP_CONTENT);
        lp.setMargins(12,13,12,13);

        item.setLayoutParams(lp);
        mShortcutActionsList.addView(item);
        if (mAnimationDuration>0) {

            if (mShortcutActionsList.getChildCount() > mOldNum) {
                item.setVisibility(View.GONE);
            }
            item.setScaleY(.1f);
            item.animate()
                    .scaleY(1f)
                    .setDuration(mAnimationDuration)
                    .setStartDelay(mShortcutActionsList.getChildCount() * 10 + 10)
                    .withStartAction(new Runnable() {
                        @Override
                        public void run() {
                            item.setVisibility(View.VISIBLE);
                        }
                    });
        }
    }

    private void addShortcutToActionPopup(final LauncherApps launcherApps, final ShortcutInfo shortcutInfo) {
        if (Build.VERSION.SDK_INT>=25) {
            if (shortcutInfo != null && shortcutInfo.getActivity() != null) {
                //Log.d(TAG, shortcutInfo.getShortLabel() + " " + shortcutInfo.getActivity().getClassName());

                if (shortcutInfo.isEnabled()) {

                    String label = "";
                    if (shortcutInfo.getShortLabel() != null)
                        label += shortcutInfo.getShortLabel();

                    if (shortcutInfo.getLongLabel() != null && !label.contentEquals(shortcutInfo.getLongLabel()))
                        label = shortcutInfo.getLongLabel() + "";

                    Drawable icon = launcherApps.getShortcutIconDrawable(shortcutInfo, DisplayMetrics.DENSITY_DEFAULT);
                    addActionMenuItem(label.trim(), icon, new Runnable() {
                        @Override
                        public void run() {
                            if (Build.VERSION.SDK_INT >= 25) {
                                try {
                                    launcherApps.startShortcut(shortcutInfo, null, null);
                                } catch (Exception e) {
                                    Log.e(TAG, "Couldn't Launch shortcut", e);
                                }
                            }
                            dismissActionPopup();
                        }
                    });

                }
            }
        }
    }


    public void dismissAppinfo() {
        if (mAppinfoWindow!=null) {
            if (mAppinfoWindow.isShowing()) mAppinfoWindow.dismiss();
            mAppinfoWindow = null;
        }
    }

} public class Categories {

    private static Resources resources;

    //Don't change these values here.  Change their displays in strings.xml
    public static final String CAT_SEARCH = "Search";
    public static final String CAT_TALK = "Communicate";
    private static final String CAT_GAMES = "Games";
    private static final String CAT_INTERNET = "Internet";
    private static final String CAT_MEDIA = "Media";
    private static final String CAT_GRAPHICS = "Graphics";
    private static final String CAT_Utilities = "Utilities";
    public static final String CAT_OTHER = "Other";
    private static final String CAT_SETTINGS = "Settings";
    public static final String CAT_HIDDEN = "Hidden";
    public static final String CAT_DUMB = "Dumb__";

    //public static final String[] CAT_TINY = {CAT_OTHER, CAT_SETTINGS, CAT_HIDDEN};
    private static final String[] CAT_TINY = {CAT_HIDDEN, CAT_DUMB};
    private static final String[] CAT_HIDDENS = {CAT_HIDDEN};
    private static final String[] CAT_SPECIALS = {CAT_OTHER, CAT_TALK, CAT_HIDDEN, CAT_SEARCH};
    private static final String[] CAT_NODROP = {CAT_SEARCH};

    public static final String[] DefCategoryOrder = {
            CAT_TALK,
            CAT_GAMES,
            CAT_INTERNET,
            CAT_MEDIA,
            CAT_GRAPHICS,
            CAT_Utilities,
            CAT_SETTINGS,
            CAT_OTHER,
            CAT_SEARCH,
            CAT_HIDDEN
    };

    private static Map<String, String[]> mCategorKeywords;

    public static void init(Context context) {
        resources = context.getResources();

        mCategorKeywords = getCategoryKeywords();
    }

    public static String unabbreviate(String abbr) {
        if (abbr==null) return null;
        for (String cat: DefCategoryOrder) {
            if (cat.startsWith(abbr)) {
                return cat;
            }
        }
        return null;
    }

    public static String getCategoryForAction(Context context, String action) {
        String category = null;
        if (action.contains("CALL") || action.contains("SEND")
                || action.contains("DIAL") || action.contains("CONTACT")
                || action.contains("MAIL") || action.contains("MESSAG")) {
            category = CAT_TALK;
        } else if (action.contains("MUSIC")) {
            category = CAT_MEDIA;
        } else if (action.contains("WEB")) {
            category = CAT_INTERNET;
        }

        return checkCat(context, category);
    }

    public static String getCategoryForUri(Context context, String uri) {
        String category = null;
        if (uri.contains("sms") || uri.contains("call") || uri.contains("tel:") || uri.contains("contact")) {
            category = CAT_TALK;
        } else if (uri.contains("aud") || uri.contains("snd")) {
            category = CAT_MEDIA;
        } else if (uri.contains("http")) {
            category = CAT_INTERNET;
        }

        return checkCat(context, category);
    }


    public static String getCategoryForPackage(Context context, String pkgname, boolean guess) {
        DB db = GlobState.getGlobState(context).getDB();

        String category = db.getCategoryForPackage(pkgname);
        if (guess && (category == null || category.equals(CAT_OTHER))) {
            category = guessCategoryForPackage(context,pkgname);
        }
        return checkCat(context, category);
    }

    private static String getCategoryForActivity(Context context, String actvname, boolean guess) {
        DB db = GlobState.getGlobState(context).getDB();

        String category = db.getCategoryForActivity(actvname);
        if (guess && (category == null || category.equals(CAT_OTHER))) {
            category = guessCategoryForPackage(context,actvname);
        }
        return checkCat(context, category);
    }

    public static String getCategoryForComponent(Context context, ComponentName activity, boolean guess, ApplicationInfo ai) {
        return getCategoryForComponent(context, activity.getClassName(), activity.getPackageName(), guess, ai);
    }

    public static String getCategoryForComponent(Context context, String actvname, String pkgname, boolean guess, ApplicationInfo ai) {

        String catact = getCategoryForActivity(context, actvname, false);
        String catpack = getCategoryForPackage(context, pkgname, false);

        String category = catact;
        if (category==null || category.equals(CAT_OTHER)) category = catpack;

        if (category==null || category.equals(CAT_OTHER)) {
            category = getCategoryFromPiCat(ai);
        }

        if (guess && (category == null || category.equals(CAT_OTHER))) {
            category = guessCategoryForPackage(context,pkgname);
        }


        return checkCat(context, category);

    }
//    CAT_TALK,
//    CAT_GAMES,
//    CAT_INTERNET,
//    CAT_MEDIA,
//    CAT_GRAPHICS,
//    CAT_Utilities,
//    CAT_SETTINGS,
//    CAT_OTHER,
//    CAT_HIDDEN,
//    CAT_SEARCH

    public static String getCategoryFromPiCat(ApplicationInfo appinfo) {
        if (appinfo==null) return null;
        String cat = null;
        if (Build.VERSION.SDK_INT >= 26) {
            int category = appinfo.category;
            switch (category) {

                case ApplicationInfo.CATEGORY_AUDIO:
                    cat = CAT_MEDIA;
                    break;

                case ApplicationInfo.CATEGORY_GAME:
                    cat = CAT_GAMES;
                    break;

                case ApplicationInfo.CATEGORY_IMAGE:
                    cat = CAT_GRAPHICS;
                    break;

                case ApplicationInfo.CATEGORY_MAPS:
                    cat = CAT_INTERNET;
                    break;

                case ApplicationInfo.CATEGORY_NEWS:
                    cat = CAT_INTERNET;
                    break;

                case ApplicationInfo.CATEGORY_PRODUCTIVITY:
                    cat = CAT_Utilities;
                    break;

                case ApplicationInfo.CATEGORY_SOCIAL:
                    cat = CAT_TALK;
                    break;

                case ApplicationInfo.CATEGORY_VIDEO:
                    cat = CAT_GRAPHICS;
                    break;

                case ApplicationInfo.CATEGORY_UNDEFINED:
                default:
                    cat = null;
                    break;

            }
        } else if ((appinfo.flags & ApplicationInfo.FLAG_IS_GAME) == ApplicationInfo.FLAG_IS_GAME) {
            cat = CAT_GAMES;
        }
        return cat;
    }


    private static String guessCategoryForPackage(Context context, String pkgname) {
        String category = null;
        OUTER:
        for (String cat : mCategorKeywords.keySet()) {
            for (String pkg : mCategorKeywords.get(cat)) {
                if (pkgname.contains(pkg)) {
                    category = cat;
                    break OUTER;
                }
            }
        }

        return checkCat(context, category);
    }



    private static String checkCat(Context context, String category) {
        if (category == null) {
            category = CAT_OTHER;
        } else if (!isSpeacialCategory(category)){
            DB db = GlobState.getGlobState(context).getDB();
            String dbcat = db.getCategoryDisplay(category);
            if (dbcat == null) {
                category = Categories.CAT_OTHER;  //the user deleted the category
            }
        }

        return category;
    }

    public static boolean isTinyCategory(String category) {
        //none are tiny by default
        //return false;
        return Arrays.asList(Categories.CAT_TINY).contains(category);
    }

    public static boolean isHiddenCategory(String category) {
        return Arrays.asList(Categories.CAT_HIDDENS).contains(category);
    }

    public static boolean isSpeacialCategory(String category) {
        return Arrays.asList(Categories.CAT_SPECIALS).contains(category);
    }

    public static boolean isNoDropCategory(String category) {
        return Arrays.asList(Categories.CAT_NODROP).contains(category);
    }

    public static String getCatLabel(Context context, String category) {
        Map<String, Integer> catmap = new HashMap<>();
        catmap.put(CAT_SEARCH, R.string.category_Search);
        catmap.put(CAT_TALK, R.string.category_Talk);
        catmap.put(CAT_GAMES, R.string.category_Games);
        catmap.put(CAT_INTERNET, R.string.category_Internet);
        catmap.put(CAT_MEDIA, R.string.category_Media);
        catmap.put(CAT_GRAPHICS, R.string.category_Graphics);
        catmap.put(CAT_Utilities, R.string.category_Utilities);
        catmap.put(CAT_OTHER, R.string.category_Other);
        catmap.put(CAT_SETTINGS, R.string.category_Settings);
        catmap.put(CAT_HIDDEN, R.string.category_Hidden);
        catmap.put(CAT_DUMB, R.string.category_Dumb);
        return context.getString(catmap.get(category));
    }

    public static String getCatFullLabel(Context context, String category) {
        Map<String, Integer> catmap = new HashMap<>();
        catmap.put(CAT_SEARCH, R.string.category_Search_full);
        catmap.put(CAT_TALK, R.string.category_Talk_full);
        catmap.put(CAT_GAMES, R.string.category_Games_full);
        catmap.put(CAT_INTERNET, R.string.category_Internet_full);
        catmap.put(CAT_MEDIA, R.string.category_Media_full);
        catmap.put(CAT_GRAPHICS, R.string.category_Graphics_full);
        catmap.put(CAT_Utilities, R.string.category_Utilities_full);
        catmap.put(CAT_OTHER, R.string.category_Other_full);
        catmap.put(CAT_SETTINGS, R.string.category_Settings_full);
        catmap.put(CAT_HIDDEN, R.string.category_Hidden_full);
        catmap.put(CAT_DUMB, R.string.category_Dumb_full);
        return context.getString(catmap.get(category));
    }

    private static Map<String, String[]> getCategoryKeywords() {
        Map<String, String[]> keywordsDict = new LinkedHashMap<>();
        
        keywordsDict.put(CAT_TALK, resources.getStringArray(R.array.CAT_TALK));
        keywordsDict.put(CAT_GAMES, resources.getStringArray(R.array.CAT_GAMES));
        keywordsDict.put(CAT_INTERNET, resources.getStringArray(R.array.CAT_INTERNET));
        keywordsDict.put(CAT_MEDIA, resources.getStringArray(R.array.CAT_MEDIA));
        keywordsDict.put(CAT_GRAPHICS, resources.getStringArray(R.array.CAT_GRAPHICS));
        keywordsDict.put(CAT_Utilities, resources.getStringArray(R.array.CAT_ACCESSORIES));
        keywordsDict.put(CAT_SETTINGS, resources.getStringArray(R.array.CAT_SETTINGS));


        return keywordsDict;
    }


} public class ExceptionHandler implements
        java.lang.Thread.UncaughtExceptionHandler {
    private final Context myContext;
    private final String LINE_SEPARATOR = "\n";

    public ExceptionHandler(Context context) {
        myContext = context;
    }

    public void uncaughtException(Thread thread, Throwable exception) {
        StringWriter stackTrace = new StringWriter();
        exception.printStackTrace(new PrintWriter(stackTrace));
        StringBuilder errorReport = new StringBuilder();
        errorReport.append("************ CAUSE OF ERROR ************\n\n");
        errorReport.append(stackTrace.toString());

        try {
            errorReport.append("\n************ DEVICE INFORMATION ***********\n");
            errorReport.append("Brand: ");
            errorReport.append(Build.BRAND);
            errorReport.append(LINE_SEPARATOR);
            errorReport.append("Device: ");
            errorReport.append(Build.DEVICE);
            errorReport.append(LINE_SEPARATOR);
            errorReport.append("Model: ");
            errorReport.append(Build.MODEL);
            errorReport.append(LINE_SEPARATOR);
            //errorReport.append("Id: ");
            //errorReport.append(Build.ID);
            //errorReport.append(LINE_SEPARATOR);
            //errorReport.append("Product: ");
            //errorReport.append(Build.PRODUCT);
            //errorReport.append(LINE_SEPARATOR);
            errorReport.append("\n************ FIRMWARE ************\n");
            errorReport.append("SDK: ");
            errorReport.append(Build.VERSION.SDK_INT);
            errorReport.append(LINE_SEPARATOR);
            errorReport.append("Release: ");
            errorReport.append(Build.VERSION.RELEASE);
            errorReport.append(LINE_SEPARATOR);
            errorReport.append("Incremental: ");
            errorReport.append(Build.VERSION.INCREMENTAL);
            errorReport.append(LINE_SEPARATOR);
        } catch (Exception e) {
            Log.e("Exc", e.getMessage(), e);
        }

        Intent intent = new Intent(myContext, CrashReportActivity.class);
        intent.putExtra("error", errorReport.toString());
        myContext.startActivity(intent);

        android.os.Process.killProcess(android.os.Process.myPid());
        System.exit(10);
    }

} public class IconsHandler {

    private static final String TAG = "IconsHandler";

    private IconPack iconPack;

    private volatile String iconsPackPackageName;

    // map with available icons packs
    private Map<String, String> iconsPacks = new HashMap<>();

    private final PackageManager pm;
    private final Context ctx;

    public static final String DEFAULT_PACK = "default";

    private final Theme theme;

    public IconsHandler(Context ctx) {
        super();
        this.ctx = ctx;
        this.pm = ctx.getPackageManager();
        theme = new Theme(ctx, this);
        SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(ctx.getApplicationContext());
        iconsPackPackageName = prefs.getString(ctx.getString(R.string.pref_key_icons_pack), DEFAULT_PACK);
        loadAvailableIconsPacks();
        loadIconsPack();
    }


    /**
     * Load configured icons pack
     */
    private void loadIconsPack() {

        loadIconsPack(iconsPackPackageName);

    }

    /**
     * Parse icons pack metadata
     *
     * @param packageName Android package ID of the package to parse
     */
    public void loadIconsPack(String packageName) {

        theme.saveUserColors();

        iconsPackPackageName = packageName;

        cacheClear();

        boolean hasusercolors = theme.restoreUserColors();

        // inbuilt theme icons, nothing to do
        if (theme.isBuiltinTheme(iconsPackPackageName)) {
            if (!hasusercolors) theme.getBuiltinTheme(iconsPackPackageName).applyTheme();
            iconPack = null;
            return;
        }
        iconPack = new IconPack(ctx,iconsPackPackageName);
    }

//    public boolean isIconTintable() {
//        return theme.isBuiltinThemeIconTintable(iconsPackPackageName);
//    }


    public IconPack getIconPack() {
        return iconPack;
    }

    public boolean isIconTintable(String packageName) {
        return theme.isBuiltinThemeIconTintable(packageName);
    }

    public boolean isIconTintable() {
        return theme.isBuiltinThemeIconTintable(iconsPackPackageName);
    }


    public Drawable getCustomIcon(ComponentName componentName) {
        Drawable app_icon = null;
        Bitmap custombitmap = SpecialIconStore.loadBitmap(ctx, componentName, SpecialIconStore.IconType.Custom);
        if (custombitmap != null) {
            app_icon = new BitmapDrawable(ctx.getResources(), custombitmap);
        }
        return  app_icon;
    }

    public Drawable getDefaultAppDrawable(AppLauncher app) {
        return getDefaultAppDrawable(app, false);
    }

    private Drawable getDefaultAppDrawable(AppLauncher app, boolean nodefault) {

        Drawable app_icon = null;

        try {
            app_icon = getCustomIcon(app.getComponentName());
            if (app_icon!=null) return app_icon;

            ComponentName baseComponentName = app.getBaseComponentName();

            try {
                if (!app.isOreoShortcut()) {
                    Intent intent = LaunchApp.getAppIntent(app);
                    app_icon = pm.getActivityIcon(intent);
                }
            } catch (Exception | OutOfMemoryError e) {
                Log.e("IconLookup", "Couldn't get icon for " + baseComponentName.getClassName(), e);
            }

            if (app_icon == null) {
                try {
                    if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
                        LauncherApps launcher = (LauncherApps) ctx.getSystemService(Context.LAUNCHER_APPS_SERVICE);
                        if (launcher!=null) {
                            LauncherActivityInfo info = launcher.getActivityList(baseComponentName.getPackageName(), android.os.Process.myUserHandle()).get(0);
                            app_icon = info.getBadgedIcon(0);
                        }
                    } else {
                        app_icon = pm.getActivityIcon(baseComponentName);
                    }
                } catch (NameNotFoundException | IndexOutOfBoundsException | OutOfMemoryError e) {
                    Log.e(TAG, "Unable to find component " + baseComponentName.toString() + e);
                    return null;
                }
            }

            if (app_icon == null && !nodefault) {
                app_icon = pm.getDefaultActivityIcon();
            }

            Bitmap bitmap = SpecialIconStore.loadBitmap(ctx, app.getComponentName(), SpecialIconStore.IconType.Shortcut);

            if (bitmap != null && app_icon!=null) {
                //Log.d(TAG, "Got special icon for " + app.getComponentName().getClassName());
                try {
                    Bitmap newbm = Bitmap.createBitmap(bitmap.getWidth(), bitmap.getHeight(), bitmap.getConfig());
                    Canvas canvas = new Canvas(newbm);
                    canvas.drawBitmap(bitmap, 0, 0, null);
                    app_icon.setBounds(canvas.getWidth() / 2, 0, canvas.getWidth(), canvas.getHeight() / 2);
                    app_icon.draw(canvas);
                    app_icon = new BitmapDrawable(ctx.getResources(), newbm);
                    //Log.d("loadAppIconAsync", " yo");
                } catch (Exception | OutOfMemoryError e) {
                    Log.e("loadAppIconAsync", "couldn't make special icon", e);
                }
            }
        } catch (Exception | OutOfMemoryError e) {
            Log.e(TAG, "Exception getting app icon for " + app.getComponentName() , e);
        }
        if (app_icon == null && !nodefault)  {
            app_icon = pm.getDefaultActivityIcon();
        }

        return app_icon;
    }


    /**
     * Get or generate icon for an app
     */
    public Drawable getDrawableIconForPackage(AppLauncher app) {

        for (String key: theme.getBuiltinIconThemes().keySet()) {

            if (iconsPackPackageName.equalsIgnoreCase(key)) {
                return theme.getBuiltinTheme(key).getDrawable(app);
            }
        }

        ComponentName componentName = app.getComponentName();

        Drawable icon = getCustomIcon(componentName);

        if (icon!=null) return  icon;

        icon = iconPack.get(componentName);
        if (icon!=null) return  icon;

        //search first in cache
        Drawable systemIcon = cacheGetDrawable(componentName.toString());
        if (systemIcon != null)
            return systemIcon;

        systemIcon = this.getDefaultAppDrawable(app);
        if (systemIcon instanceof BitmapDrawable) {
            Drawable generated = iconPack.generateBitmap(systemIcon);
            cacheStoreDrawable(componentName.toString(), generated);
            return generated;
        }
        return systemIcon;
    }


    public String getIconsPackPackageName() {
        return iconsPackPackageName;
    }


    public Theme getTheme() {
        return theme;
    }


    /**
     * Scan for installed icons packs
     */
    public void loadAvailableIconsPacks() {

        iconsPacks = IconPack.listAvailableIconsPacks(ctx);

    }



    private Map<String, String> getIconsPacks() {
        return iconsPacks;
    }

    public Map<String, String> getAllIconsThemes() {
        Map<String, String> iconsThemes = new LinkedHashMap<>();

        iconsThemes.put(DEFAULT_PACK, theme.getBuiltinTheme(DEFAULT_PACK).getPackName());

        iconsThemes.putAll(getIconsPacks());

        for (Theme.BuiltinTheme ic: theme.getBuiltinIconThemes().values()) {
            if ( !ic.getPackKey().equals(DEFAULT_PACK)) {
                iconsThemes.put(ic.getPackKey(), ic.getPackName() + " (sys)");
            }
        }

        return iconsThemes;
    }

    private boolean isDrawableInCache(String key) {
        File drawableFile = cacheGetFileName(key);
        return drawableFile.isFile();
    }

    private void cacheStoreDrawable(String key, Drawable drawable) {
        if (drawable instanceof BitmapDrawable) {
            File drawableFile = cacheGetFileName(key);
            FileOutputStream fos;
            try {
                fos = new FileOutputStream(drawableFile);
                ((BitmapDrawable) drawable).getBitmap().compress(CompressFormat.PNG, 100, fos);
                fos.flush();
                fos.close();
            } catch (Exception e) {
                Log.e(TAG, "Unable to store drawable in cache " + e);
            }
        }
    }

    private Drawable cacheGetDrawable(String key) {

        if (!isDrawableInCache(key)) {
            return null;
        }

        FileInputStream fis=null;
        try {
            fis = new FileInputStream(cacheGetFileName(key));

            return new BitmapDrawable(this.ctx.getResources(), BitmapFactory.decodeStream(fis));
        } catch (Exception e) {
            Log.e(TAG, "Unable to get drawable from cache " + e);
        } finally {
            if (fis!=null) try {
                fis.close();
            } catch (IOException e) {
                Log.e(TAG, "Unable close fh " + e);
            }
        }

        return null;
    }

    /**
     * create path for icons cache like this
     * {cacheDir}/icons/{icons_pack_package_name}_{key_hash}.png
     */
    private File cacheGetFileName(String key) {
        return new File(getIconsCacheDir() + iconsPackPackageName + "_" + key.hashCode() + ".png");
    }

    private File getIconsCacheDir() {
        return new File(this.ctx.getCacheDir().getPath() + "/icons/");
    }

    /**
     * Clear cache
     */
    private void cacheClear() {
        File cacheDir = this.getIconsCacheDir();

        if (!cacheDir.isDirectory())
            return;

        for (File item : cacheDir.listFiles()) {
            if (!item.delete()) {
                Log.w(TAG, "Failed to delete file: " + item.getAbsolutePath());
            }
        }
    }

    public static Bitmap drawableToBitmap (Drawable drawable) {
        if (drawable instanceof BitmapDrawable) {
            return ((BitmapDrawable)drawable).getBitmap();
        }

        int width = drawable.getIntrinsicWidth();
        width = width > 0 ? width : 1;
        int height = drawable.getIntrinsicHeight();
        height = height > 0 ? height : 1;

        Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
        Canvas canvas = new Canvas(bitmap);
        drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
        drawable.draw(canvas);

        return bitmap;
    }




    private static Drawable currentLinkSymbol;
    private static int currentIconTint;


    public static Drawable drawLinkSymbol(Drawable app_icon, Context context) {
        try {
            Bitmap newbm = Bitmap.createBitmap(app_icon.getIntrinsicWidth(), app_icon.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
            Canvas canvas = new Canvas(newbm);
            app_icon.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
            app_icon.draw(canvas);

            int tint = GlobState.getStyle(context).getIconTint();
            if (tint!=currentIconTint || currentLinkSymbol==null) {
                Drawable linkSymbol;
                if (Build.VERSION.SDK_INT >= 21) {
                    linkSymbol = context.getResources().getDrawable(R.drawable.link, context.getTheme());
                } else {
                    linkSymbol = context.getResources().getDrawable(R.drawable.link);
                }

                if (linkSymbol.getConstantState() == null) {
                    currentLinkSymbol = linkSymbol.mutate();
                } else {
                    currentLinkSymbol = linkSymbol.getConstantState().newDrawable().mutate();
                }

                applyIconTint(currentLinkSymbol, tint);

                currentIconTint = tint;

            }
            currentLinkSymbol.setBounds(canvas.getWidth() * 3 / 4, canvas.getHeight() * 3 / 4, canvas.getWidth(), canvas.getHeight());
            currentLinkSymbol.draw(canvas);

            app_icon = new BitmapDrawable(context.getResources(), newbm);
            //Log.d("loadAppIconAsync", " yo");
        } catch (Exception | OutOfMemoryError e) {
            Log.e("loadAppIconAsync", "couldn't make link icon", e);
        }
        return app_icon;
    }


    public static void applyIconTint(final Drawable app_icon, int mask_color) {
        if (Color.alpha(mask_color) > 10) {

            app_icon.mutate();

            //int avg = (Color.red(mask_color) + Color.green(mask_color) + Color.blue(mask_color) ) / 3;

            if (Color.red(mask_color)>5 && Color.red(mask_color) == Color.green(mask_color) && Color.red(mask_color) == Color.blue(mask_color) ) {
                setSaturation(app_icon, (255f-Color.red(mask_color))/255f, Color.alpha(mask_color)/255f);
            } else {
                PorterDuff.Mode mode = PorterDuff.Mode.MULTIPLY;
                app_icon.setColorFilter(mask_color, mode);
            }
        }
    }

    private static ColorMatrixColorFilter colorMatrixColorFilter;
    private static float filterSaturation;
    private static float filterAlpha;

    public static void setSaturation(final Drawable drawable, float saturation, float alpha) {

        if (colorMatrixColorFilter==null || saturation!=filterSaturation || filterAlpha!=alpha) {
            filterSaturation = saturation;
            filterAlpha = alpha;

            ColorMatrix matrixA = new ColorMatrix();
            matrixA.setSaturation(filterSaturation);

            float[] matrixBItems =
                    new float[] {
                            1, 0, 0, 0, 0,
                            0, 1, 0, 0, 0,
                            0, 0, 1, 0, 0,
                            0, 0, 0, alpha,0};

            ColorMatrix matrixB = new ColorMatrix(matrixBItems);

            matrixA.setConcat(matrixB, matrixA);

            colorMatrixColorFilter = new ColorMatrixColorFilter(matrixA);

        }

//        ColorMatrix matrix = new ColorMatrix();
//        matrix.setSaturation(saturation);
//
//        ColorMatrixColorFilter filter = new ColorMatrixColorFilter(matrix);

        drawable.setColorFilter(colorMatrixColorFilter);

    }

} public class MsgBox {


    public static void alert(Context context, String title, String message, boolean ok, boolean cancel, final Runnable action) {
        final AlertDialog.Builder builder = new AlertDialog.Builder(context);
        builder.setTitle(title);
        builder.setMessage(message);

        if (ok){
            builder.setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() {
                @Override
                public void onClick(DialogInterface dialog, int which) {
                    if (action!=null) action.run();
                    dialog.dismiss();
                }
            });
        }
        if (cancel){
            builder.setNegativeButton(android.R.string.no, new DialogInterface.OnClickListener() {
                @Override
                public void onClick(DialogInterface dialog, int which) {
                    dialog.dismiss();
                }
            });
        }


        AlertDialog dialog = builder.create();
        dialog.show();
    }

//    public static void show(Context context, String title, String message, final Runnable run) {
//        final AlertDialog.Builder builder = new AlertDialog.Builder(context);
//        builder.setTitle(title);
//        builder.setMessage(message);
//
//        builder.setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() {
//            @Override
//            public void onClick(DialogInterface dialog, int which) {
//                if (run!=null) {
//                    run.run();
//                }
//                dialog.dismiss();
//            }
//        });
//        builder.setNegativeButton(android.R.string.cancel, null);
//
//        AlertDialog dialog = builder.create();
//        dialog.show();
//    }
//


    public static void showNewsMessage(final Context context, SharedPreferences prefs) {


        final int newsnum = 83;
        final int news = prefs.getInt("seennews", 0);
        if (news < newsnum) {
            new Handler().postDelayed(new Runnable() {
                @Override
                public void run() {

                    showNews(context);

                }
            }, 3000);
            prefs.edit().putInt("seennews", newsnum).apply();
        }

    }

    private static void showNews(final Context context) {
        StringWriter sw = new StringWriter();
        BufferedReader in = new BufferedReader(new InputStreamReader(context.getResources().openRawResource(R.raw.news)));
        try {

            String line;
            while ((line = in.readLine()) != null) {
                sw.append(line);
                sw.append("\n");
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                in.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }


        ScrollView scrll = new ScrollView(context);
        LinearLayout viewg = new LinearLayout(context);
        viewg.setOrientation(LinearLayout.VERTICAL);

        Button b = new Button(context);
        b.setText(R.string.prompt_config_features);
        LinearLayout.LayoutParams blp = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
        blp.gravity = Gravity.END;
        b.setLayoutParams(blp);


        b.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                promptNewFeatures(context, true, null);
            }
        });


        viewg.addView(b);
        TextView ms = new TextView(context);
        ms.setText(sw.toString());
        ms.setPadding(10, 10, 10, 10);
        viewg.addView(ms);

        scrll.addView(viewg);

        final AlertDialog.Builder builder = new AlertDialog.Builder(context);
        builder.setTitle("What's new!");
        builder.setView(scrll);

        builder.setNeutralButton(android.R.string.yes, new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                dialog.dismiss();
            }
        });

        AlertDialog dialog = builder.create();
        dialog.show();
    }

    public static void promptNewFeatures(final Context context, boolean startup, final Runnable after) {


        ViewGroup content = (ViewGroup) LayoutInflater.from(context).inflate(R.layout.new_features, null);

        final CheckBox hideCatsCheck = content.findViewById(R.id.hide_cats_check);
        TextView hideCatsText = content.findViewById(R.id.hide_cats_text);

        final CheckBox actionMenuCheck = content.findViewById(R.id.action_menu_check);
        TextView actionMenuText = content.findViewById(R.id.action_menu_text);

        final CheckBox extraMenuCheck = content.findViewById(R.id.extra_menu_check);
        TextView extraMenuText = content.findViewById(R.id.extra_menu_text);

        final CheckBox resetColorsCheck = content.findViewById(R.id.reset_colors_check);
        //TextView resetColorsText = content.findViewById(R.id.reset_colors_text);


        final SharedPreferences appPreferences = PreferenceManager.getDefaultSharedPreferences(context.getApplicationContext());


        if (!appPreferences.getString(context.getString(R.string.pref_key_autohide_cats_timeout), "-1").equals("-1")) {
            hideCatsCheck.setChecked(true);
            if (startup) {
                hideCatsCheck.setVisibility(View.GONE);
                hideCatsText.setVisibility(View.GONE);
            }

        }

        if (appPreferences.getBoolean(context.getString(R.string.pref_key_show_action_menus), false)) {
            actionMenuCheck.setChecked(true);
            if (startup) {
                actionMenuCheck.setVisibility(View.GONE);
                actionMenuText.setVisibility(View.GONE);
            }


            if (appPreferences.getBoolean(context.getString(R.string.pref_key_show_action_extra), false)) {
                extraMenuCheck.setChecked(true);
                if (startup) {
                    extraMenuCheck.setVisibility(View.GONE);
                    extraMenuText.setVisibility(View.GONE);
                }

            }
        }

        actionMenuCheck.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
            @Override
            public void onCheckedChanged(CompoundButton compoundButton, boolean b) {
                extraMenuCheck.setEnabled(b);
                extraMenuCheck.setChecked(b);
            }
        });

        extraMenuCheck.setEnabled(actionMenuCheck.isChecked());




        new AlertDialog.Builder(context)
                .setIcon(android.R.drawable.ic_menu_manage)
                .setView(content)
                .setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        SharedPreferences.Editor editor = appPreferences.edit();
                        editor.putString(context.getString(R.string.pref_key_autohide_cats_timeout), hideCatsCheck.isChecked()?"1500":"-1");

                        editor.putBoolean(context.getString(R.string.pref_key_show_action_menus), actionMenuCheck.isChecked());

                        editor.putBoolean(context.getString(R.string.pref_key_show_action_extra), extraMenuCheck.isChecked());


                        if (resetColorsCheck.isChecked()) {
                            editor.putString(context.getString(R.string.pref_key_icons_pack), IconsHandler.DEFAULT_PACK);
                        }
                        editor.apply();

                        if (resetColorsCheck.isChecked()) {

                            IconsHandler ich = GlobState.getIconsHandler(context);
                            ich.getTheme().resetUserColors();
                        }
                        if (after!=null) {
                            after.run();
                        }

                    }

                })
                .setNegativeButton(android.R.string.no, null)
                .show();

    }



} @SuppressWarnings({"TryFinallyCanBeTryWithResources", "WeakerAccess"})
public class DB extends SQLiteOpenHelper {

    private static final String DATABASE_NAME = "db";
    private static final int DATABASE_VERSION = 11;

    private static final String ACTVNAME = "actvname";
    private static final String PKGNAME = "pkgname";
    private static final String LABEL = "label";
    private static final String LABELFULL = "labelfull";
    private static final String CATID = "catID";
    private static final String ISWIDGET = "iswidget";
    private static final String ISUNINSTALLED = "isuninstalled";
    private static final String CUSTOMLABEL = "customlabel";

    private static final String INDEX = "pos";
    private static final String TIME = "time";
    private static final String FLAGS = "tiny";
    private static final String LEVEL = "level";


    private static final int FLAGS_ISTINY = 1;
    private static final int FLAGS_ISHIDDEN = 2;


    private static final String APP_TABLE_OLD = "apps";
    private static final String APP_TABLE = "apps2";
    private static final String[] appcolumns = {ACTVNAME, PKGNAME, LABEL, CATID, ISWIDGET, ISUNINSTALLED, CUSTOMLABEL};
    private static final String[] appcolumntypes = {"TEXT", "TEXT", "TEXT", "TEXT", "SHORT", "SHORT", "TEXT"};
    private static final String APP_TABLE_CREATE = buildCreateTableStmt(APP_TABLE, appcolumns, appcolumntypes);

    private static final String[] appcolumnsindex = {ACTVNAME, PKGNAME, CATID, ISUNINSTALLED};

    private static final String APP_ORDER_TABLE = "apps_order";
    private static final String[] appordercolumns = {CATID, ACTVNAME, INDEX, PKGNAME};
    private static final String[] appordercolumntypes = {"TEXT", "TEXT", "INT", "TEXT"};
    private static final String APP_ORDER_TABLE_CREATE = buildCreateTableStmt(APP_ORDER_TABLE, appordercolumns, appordercolumntypes);

    private static final String[] appordercolumnsindex = {CATID + ", " + ACTVNAME, INDEX};


    private static final String CATEGORIES_TABLE = "tab_order";
    private static final String[] categoriescolumns = {CATID, LABEL, LABELFULL, FLAGS, INDEX};
    private static final String[] categoriescolumntypes = {"TEXT primary key", "TEXT", "TEXT", "SHORT", "INT"};
    private static final String CATEGORIES_TABLE_CREATE = buildCreateTableStmt(CATEGORIES_TABLE, categoriescolumns, categoriescolumntypes);

    private static final String[] categoriescolumnsindex = {INDEX};


    private static final String APP_HISTORY_TABLE = "apps_hist";
    private static final String[] apphistorycolumns = {ACTVNAME, TIME, PKGNAME};
    private static final String[] apphistorycolumntypes = {"TEXT", "DATETIME", "TEXT"};
    private static final String APP_HISTORY_TABLE_CREATE = buildCreateTableStmt(APP_HISTORY_TABLE, apphistorycolumns, apphistorycolumntypes);

    private static final String[] apphistorycolumnsindex = {ACTVNAME, PKGNAME};


    private static final String APP_CAT_MAP_TABLE = "app_category_map";
    private static final String[] appcatmapcolumns = {ACTVNAME, PKGNAME, CATID, LEVEL};
    private static final String[] appcatmapcolumntypes = {"TEXT", "TEXT", "TEXT", "SHORT"};
    private static final String APP_CAT_MAP_TABLE_CREATE = buildCreateTableStmt(APP_CAT_MAP_TABLE, appcatmapcolumns, appcatmapcolumntypes);


    private static final String[] appcatmapcolumnsindex = {"ACTVNAME, LEVEL", "PKGNAME, LEVEL"};


    private static final String APP_CURSOR_SQL = "select " + ACTVNAME + " _id, " + PKGNAME + " pkg,  app." + LABEL + " label, tab." + LABEL + " category " + ", app." + CUSTOMLABEL + " customlabel" +
            " from " + APP_TABLE + " as app " +
            " inner join " + CATEGORIES_TABLE + " as tab on app." + CATID + "=tab." + CATID +
            " where (app." + LABEL + " like ? or app." + CUSTOMLABEL + " like ?) and " +  ISWIDGET + "=0 and (" + ISUNINSTALLED+"=0)" +
            " order by LOWER(case when customlabel is null then app.label else customlabel end) ";

    private boolean firstRun;

    private final Context mContext;

    private final DBClosedListener mDBClosedListener;

    private ComponentName latestInstall;
    private long latestInstallTime;

    public static DB openDB(Context context, DBClosedListener dBClosedListener) {
         return new DB(context, dBClosedListener);
    }

    private DB(Context context, DBClosedListener dBClosedListener ) {
        super(context, DATABASE_NAME, null, DATABASE_VERSION);
        mContext = context;
        mDBClosedListener = dBClosedListener;


        try {
            this.getWritableDatabase(); //force onCreate();
        } catch (SQLiteException se) {
            Log.e("db", se.getMessage(), se);
            try {
                Thread.sleep(1000);
                this.getWritableDatabase();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        if (isFirstRun()) {
            Log.d("db", "first run: creating categories");
            for (int i = 0; i < Categories.DefCategoryOrder.length; i++) {
                String cat = Categories.DefCategoryOrder[i];
                addCategory(cat, Categories.getCatLabel(mContext, cat), Categories.getCatFullLabel(mContext, cat), Categories.isTinyCategory(cat), Categories.isHiddenCategory(cat), i);
            }
        } else {
            Log.d("db", "opening database");
        }


    }

    private static String buildCreateTableStmt(String tablename, String[] cols, String[] coltypes) {

        String create = "CREATE TABLE " + tablename + " (";
        for (int i = 0; i < cols.length; i++) {
            if (i != 0) create += ", ";
            create += cols[i] + " " + coltypes[i];
        }
        create += ");";
        return create;

    }

    private static String buildIndexStmt(String tablename, String col) {


        return "CREATE INDEX " + ((col + tablename).replaceAll("\\W+", "_")) + " on " + tablename + "(" + col + ");";


    }

    @Override
    public void onCreate(SQLiteDatabase sqLiteDatabase) {
        firstRun = true;
        Log.i("db", "create database");
        sqLiteDatabase.execSQL(APP_TABLE_CREATE);
        for (String createind : appcolumnsindex) {
            sqLiteDatabase.execSQL(buildIndexStmt(APP_TABLE, createind));
        }

        sqLiteDatabase.execSQL(APP_ORDER_TABLE_CREATE);
        for (String createind : appordercolumnsindex) {
            sqLiteDatabase.execSQL(buildIndexStmt(APP_ORDER_TABLE, createind));
        }

        sqLiteDatabase.execSQL(CATEGORIES_TABLE_CREATE);
        for (String createind : categoriescolumnsindex) {
            sqLiteDatabase.execSQL(buildIndexStmt(CATEGORIES_TABLE, createind));
        }

        sqLiteDatabase.execSQL(APP_HISTORY_TABLE_CREATE);
        for (String createind : apphistorycolumnsindex) {
            sqLiteDatabase.execSQL(buildIndexStmt(APP_HISTORY_TABLE, createind));
        }

        buildCatTable(sqLiteDatabase);


    }

    private void buildCatTable(SQLiteDatabase sqLiteDatabase) {
        Log.i("db", "creating category table");
        sqLiteDatabase.execSQL("drop table if exists " + APP_CAT_MAP_TABLE);
        sqLiteDatabase.execSQL(APP_CAT_MAP_TABLE_CREATE);
        for (String createind : appcatmapcolumnsindex) {
            sqLiteDatabase.execSQL(buildIndexStmt(APP_CAT_MAP_TABLE, createind));
        }

        loadCategories(sqLiteDatabase, true, R.raw.submitted_activities,1);
        loadCategories(sqLiteDatabase, false, R.raw.submitted_packages, 2);
        loadCategories(sqLiteDatabase, false, R.raw.packages1,3);
        loadCategories(sqLiteDatabase, false, R.raw.packages2,4);
    }

    @Override
    public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {

    }

    @Override
    public void onUpgrade(SQLiteDatabase sqLiteDatabase, int oldVersion, int newVersion) {
        Log.i("db", "upgrade database");

        if (oldVersion==1) {
            sqLiteDatabase.execSQL("alter table " + APP_TABLE_OLD + " add column " + ISUNINSTALLED + " SHORT");
            //sqLiteDatabase.execSQL(buildIndexStmt(APP_TABLE_OLD,  ISUNINSTALLED));

            ContentValues values = new ContentValues();
            values.put(ISUNINSTALLED, 0);
            sqLiteDatabase.update(APP_TABLE_OLD, values, null, null);
        }
        if (oldVersion<=2) {

            upgradeTo2(sqLiteDatabase);
        }

        if (oldVersion<6) {
            sqLiteDatabase.execSQL("alter table " + APP_TABLE + " add column " + CUSTOMLABEL + " TEXT");
        }


        if (oldVersion<10) {
            ContentValues values = new ContentValues();
            values.put(INDEX, 101);
            sqLiteDatabase.update(CATEGORIES_TABLE, values, CATID + "=?", new String[]{Categories.CAT_SEARCH});
        }

        if (oldVersion<11) {
            buildCatTable(sqLiteDatabase);
        }

        sqLiteDatabase.delete(APP_ORDER_TABLE, PKGNAME + " is null", null);
    }
    public boolean isFirstRun() {
        return firstRun;
    }


    public List<ComponentName> getAppNames() {
        List<ComponentName> actvnames = new ArrayList<>();

        SQLiteDatabase db = this.getReadableDatabase();

        Cursor cursor = db.query(APP_TABLE, new String[]{ACTVNAME, PKGNAME}, ISUNINSTALLED+"=0", null, null, null, LABEL);
        try {
            while (cursor.moveToNext()) {
                String actv = cursor.getString(cursor.getColumnIndex(ACTVNAME));
                String pkg = cursor.getString(cursor.getColumnIndex(PKGNAME));
                try {
                    ComponentName cn = new ComponentName(pkg, actv);
                    actvnames.add(cn);
                } catch (Exception e) {
                    Log.e("LaunchDB", e.getMessage(), e);
                }
            }
        } finally {
            cursor.close();
        }
        return actvnames;
    }

    public AppLauncher getApp(ComponentName appname) {

        String actvname = appname.getClassName();
        String pkgname =  appname.getPackageName();

        AppLauncher appLauncher = null;
        SQLiteDatabase db = this.getReadableDatabase();

        Cursor cursor = db.query(APP_TABLE, appcolumns, ACTVNAME + "=? and " + PKGNAME + "=?", new String[]{actvname, pkgname}, null, null, null);
        try {
            if (cursor.moveToNext()) { //ACTVNAME, PKGNAME, LABEL, CATID
                //pkgname = cursor.getString(1);
                String label = cursor.getString(cursor.getColumnIndex(LABEL));
                String catID = cursor.getString(cursor.getColumnIndex(CATID));
                boolean widget = cursor.getShort(cursor.getColumnIndex(ISWIDGET)) == 1;
                String customlabel = cursor.getString(cursor.getColumnIndex(CUSTOMLABEL));

                // Log.d("LaunchDB", "getApp " + pkgname + " " + catID);
                appLauncher = AppLauncher.createAppLauncher(actvname, pkgname, customlabel==null?label:customlabel, catID, widget);
            }
        } finally {
            cursor.close();
        }
        return appLauncher;
    }

    public boolean appHasCustomLabel(ComponentName appname) {
        String actvname = appname.getClassName();
        String pkgname =  appname.getPackageName();

        SQLiteDatabase db = this.getReadableDatabase();
        Cursor cursor = db.query(APP_TABLE, new String [] {CUSTOMLABEL}, ACTVNAME + "=? and " + PKGNAME + "=?", new String[]{actvname, pkgname}, null, null, null);
        try {
            if (cursor.moveToNext()) { //ACTVNAME, PKGNAME, LABEL, CATID
                String customlabel = cursor.getString(cursor.getColumnIndex(CUSTOMLABEL));
                if (customlabel!=null && !customlabel.isEmpty()) {
                    return true;
                }
            }
        } finally {
            cursor.close();
        }
        return false;
    }

    public List<AppLauncher> getAppsForPackage(String pkgname) {


        List<AppLauncher> appLaunchers = new ArrayList<>();

        SQLiteDatabase db = this.getReadableDatabase();

        Cursor cursor = db.query(APP_TABLE, appcolumns, PKGNAME + "=?", new String[]{pkgname}, null, null, null);
        try {
            while (cursor.moveToNext()) { //ACTVNAME, PKGNAME, LABEL, CATID
                String actvname = cursor.getString(cursor.getColumnIndex(ACTVNAME));
                //pkgname = cursor.getString(1);
                String label = cursor.getString(cursor.getColumnIndex(LABEL));
                String catID = cursor.getString(cursor.getColumnIndex(CATID));
                boolean widget = cursor.getShort(cursor.getColumnIndex(ISWIDGET)) == 1;
                String customlabel = cursor.getString(cursor.getColumnIndex(CUSTOMLABEL));

                // Log.d("LaunchDB", "getApp " + pkgname + " " + catID);
                appLaunchers.add(AppLauncher.createAppLauncher(actvname, pkgname, customlabel==null?label:customlabel, catID, widget));
            }
        } finally {
            cursor.close();
        }
        return appLaunchers;
    }



    public String getAppCategory(ComponentName appname) {
        String actvname = appname.getClassName();
        String pkgname =  appname.getPackageName();

        String catID = null;
        SQLiteDatabase db = this.getReadableDatabase();

        Cursor cursor = db.query(APP_TABLE, new String[]{CATID}, ACTVNAME + "=? and " + PKGNAME + "=?", new String[]{actvname, pkgname}, null, null, null);
        try {
            if (cursor.moveToNext()) { //ACTVNAME, PKGNAME, LABEL, CATID
                catID = cursor.getString(cursor.getColumnIndex(CATID));
            }
        } finally {
            cursor.close();
        }
        return catID;
    }

    public boolean isAppInstalled(ComponentName appname) {
        String actvname = appname.getClassName();
        String pkgname =  appname.getPackageName();

        boolean installed = false;
        SQLiteDatabase db = this.getReadableDatabase();

        Cursor cursor = db.query(APP_TABLE, new String[]{ISUNINSTALLED}, ACTVNAME + "=? and " + PKGNAME + "=?", new String[]{actvname, pkgname}, null, null, null);
        try {
            if (cursor.moveToNext()) { //ACTVNAME, PKGNAME, LABEL, CATID
                installed = cursor.getShort(cursor.getColumnIndex(ISUNINSTALLED)) != 1;
            }
        } finally {
            cursor.close();
        }
        return installed;
    }


    public int getAppCount(String catID) {
        int count = 0;
        SQLiteDatabase db = this.getReadableDatabase();

        Cursor cursor = db.query(APP_TABLE,  new String[]{"count(*)"}, CATID + "=? and ("+ISUNINSTALLED+"=0)", new String[]{catID}, null, null, null);
        try {
            if (cursor.moveToNext()) {
                count = cursor.getInt(0);
            }
        } finally {
            cursor.close();
        }
        return count;
    }

    public List<AppLauncher> getApps(String catID) {

        List<AppLauncher> apps = new ArrayList<>();

        SQLiteDatabase db = this.getReadableDatabase();

        Cursor cursor = db.query(APP_TABLE, appcolumns, CATID + "=? and ("+ISUNINSTALLED+"=0)", new String[]{catID}, null, null, CUSTOMLABEL + " ," + LABEL);
        try {
            while (cursor.moveToNext()) {
                String actv = cursor.getString(cursor.getColumnIndex(ACTVNAME));
                String pkg = cursor.getString(cursor.getColumnIndex(PKGNAME));
                String label = cursor.getString(cursor.getColumnIndex(LABEL));
                boolean widget = cursor.getShort(cursor.getColumnIndex(ISWIDGET)) == 1;
                String customlabel = cursor.getString(cursor.getColumnIndex(CUSTOMLABEL));

                //if (widget) {
                   // Log.d("db", "Found widget: " + actvname + " " + pkgname);
                //}
                apps.add(AppLauncher.createAppLauncher(actv, pkg, customlabel==null?label:customlabel, catID, widget));
            }
        } finally {
            cursor.close();
        }

        return apps;
    }

    public boolean addApp(AppLauncher launcher) {
        return addApp(launcher.getActivityName(), launcher.getPackageName(), launcher.getLabel(), launcher.getCategory(), launcher.isWidget());
    }

    public void addApps(List<AppLauncher> launchers) {
        SQLiteDatabase db = this.getWritableDatabase();
        for (AppLauncher launcher : launchers) {
            addApp(db, launcher.getActivityName(), launcher.getPackageName(), launcher.getLabel(), launcher.getCategory(), launcher.isWidget());
        }
    }

    public boolean addApp(String actvname, String pkgname, String label, String catID, boolean widget) {
        SQLiteDatabase db = this.getWritableDatabase();
       return addApp(db, actvname, pkgname, label, catID, widget);
    }

    public long getLatestInstallTime() {
        return latestInstallTime;
    }

    public ComponentName getLatestInstall() {
        return latestInstall;
    }

    private boolean addApp(SQLiteDatabase db, String actvname, String pkgname, String label, String catID, boolean widget) {
        try {

            if (pkgname!=null && actvname!=null) {
                latestInstall = new ComponentName(pkgname, actvname);
                latestInstallTime = System.currentTimeMillis();
            }

            String dbcat = getCategoryDisplay(catID);
            if (dbcat==null) {
                Log.i("LaunchDB", "Use of category not in the database: " + catID, new Throwable());
                catID = Categories.CAT_OTHER;  //the user deleted the category
            }

            //Log.d("LaunchDB", "actvname " + actvname + " pkgname "  +pkgname + " added to db");
            ContentValues values = new ContentValues();
            values.put(LABEL, label);
            values.put(CATID, catID);
            values.put(ISWIDGET, widget ? 1 : 0);
            values.put(ISUNINSTALLED, 0);

            if (db.update(APP_TABLE, values, ACTVNAME + "=? and " + PKGNAME + "=?", new String[]{actvname, pkgname})==0) {
                values.put(ACTVNAME, actvname);
                values.put(PKGNAME, pkgname);
                db.insert(APP_TABLE, null, values);
               // Log.i("LaunchDB", "inserted " + actvname + " " + pkgname);
            }
            AppLauncher.removeAppLauncher(actvname,pkgname);

            return true;
        } catch (Exception e) {
            Log.e("LaunchDB", "Can't insert package " + pkgname, e);
        }
        return false;
    }

    public void updateAppLabel(String actvname, String pkgname, String label) {
        SQLiteDatabase db = this.getWritableDatabase();
        ContentValues values = new ContentValues();
        values.put(LABEL, label);
        db.update(APP_TABLE, values, ACTVNAME + "=? and " + PKGNAME + "=?", new String[]{actvname, pkgname});
        AppLauncher.removeAppLauncher(actvname,pkgname);
    }

    public void setAppCustomLabel(String actvname, String pkgname, String customlabel) {
        SQLiteDatabase db = this.getWritableDatabase();
        ContentValues values = new ContentValues();
        if (customlabel==null) {
            values.putNull(CUSTOMLABEL);
        } else {
            values.put(CUSTOMLABEL, customlabel);
        }
        db.update(APP_TABLE, values, ACTVNAME + "=? and " + PKGNAME + "=?", new String[]{actvname, pkgname});
        AppLauncher.removeAppLauncher(actvname,pkgname);

    }


    public Cursor getAppCursor(String filter) {
        SQLiteDatabase db = this.getReadableDatabase();

        //Cursor cursor = db.query(APP_TABLE, new String[]{CATID}, null, null, null, null, INDEX, null);
        Cursor cursor = db.rawQuery(
                APP_CURSOR_SQL,
                new String[]{filter, filter});

        return cursor;
    }

    public void updateAppCategory(ComponentName comp, String catID) {
        updateAppCategory(comp.getClassName(), comp.getPackageName(), catID);
    }


    public void updateAppCategory(String actvname, String pkgname, String catID) {
        SQLiteDatabase db = this.getWritableDatabase();


        ContentValues values = new ContentValues();
        values.put(CATID, catID);

        db.update(APP_TABLE, values, ACTVNAME + "=? and " + PKGNAME + "=?", new String[]{actvname, pkgname});

        // Log.d("LaunchDB", "update " + pkgname + " " + catID);
    }



//    public boolean deleteApp(ComponentName appname) {
//        return deleteApp(actvorpkgname, false);
//    }

    public void deleteApp(ComponentName appname) {

        String actvname = appname.getClassName();

        String pkgname = appname.getPackageName();

        try {
            AppLauncher app = getApp(appname);
            if (app != null && (app.isLink() || app.isWidget())) {
                SQLiteDatabase db = this.getWritableDatabase();

                db.delete(APP_TABLE, ACTVNAME + "=? and " + PKGNAME + "=?", new String[]{actvname, pkgname});
                AppLauncher.removeAppLauncher(actvname,pkgname);

                return;
            }
        } catch (Exception e) {
            Log.e("LaunchDB", "Can't delete app " + actvname, e);
        }


        deleteApp(actvname, pkgname);
    }

    public boolean deleteApp(String actvname, String pkgname) {

        SQLiteDatabase db = this.getWritableDatabase();

        try {
            if (actvname==null) {

                List<AppLauncher> apps = getAppsForPackage(pkgname);
                if (apps != null) {
                    for (AppLauncher app: apps) {
                        if (app.isWidget() || app.isLink() || app.isActionLink()) {
                            db.delete(APP_TABLE, ACTVNAME + "=?", new String[]{app.getActivityName()});
                        }

                    }
                }
                ContentValues values = new ContentValues();
                values.put(ISUNINSTALLED, 1);

                db.update(APP_TABLE, values, PKGNAME + "=?", new String[]{pkgname});
            } else {


                ContentValues values = new ContentValues();
                values.put(ISUNINSTALLED, 1);

                db.update(APP_TABLE, values, ACTVNAME + "=? and " + PKGNAME + "=?", new String[]{actvname, pkgname});
            }
            AppLauncher.removeAppLauncher(actvname,pkgname);

            //db.delete(APP_TABLE, (isPackagename?PKGNAME:ACTVNAME) + "=?", new String[]{actvorpkgname});
        } catch (Exception e) {
            Log.e("LaunchDB", "Can't delete app " + actvname, e);
            return false;
        }
        return true;
    }

    public boolean addCategory(String catID, String displayName, String displayNameFull) {
        return addCategory(catID, displayName, displayNameFull, -1);
    }

    public boolean addCategory(String catID, String displayName, String displayNameFull, int index) {
        return addCategory(catID, displayName, displayNameFull, false, false, index);
    }

    public boolean addCategory(String catID, String displayName, String displayNameFull, boolean isTiny, boolean isHidden) {
        return addCategory(catID, displayName, displayNameFull, isTiny, isHidden, -1);
    }

    public boolean addCategory(String catID, String displayName, String displayNameFull, boolean isTiny, boolean isHidden, int index) {
        if (catID==null || catID.isEmpty()) {
            return false;
        }
        try {

            for (String existcat: getCategories()) {
                if (existcat.equalsIgnoreCase(catID)) return false;
            }

            SQLiteDatabase db = this.getWritableDatabase();

            int flags = 0;
            if (isTiny) flags |= FLAGS_ISTINY;
            if (isHidden) flags |= FLAGS_ISHIDDEN;

            // Log.d("DB", "adding catID " + catID);
            ContentValues values = new ContentValues();
            values.put(CATID, catID);
            values.put(LABEL, displayName);
            values.put(LABELFULL, displayNameFull);
            values.put(FLAGS, flags);
            values.put(INDEX, index);

            db.insert(CATEGORIES_TABLE, null, values);
        } catch (Exception e) {
            Log.e("LaunchDB", "Can't add catID " + catID, e);
            return false;
        }
        return true;
    }


    public boolean updateCategory(String catID, String displayName, String displayNameFull) {
        return updateCategory(catID, displayName, displayNameFull, false, false);
    }

    public boolean updateCategory(String catID, String displayName, String displayNameFull, boolean isTiny, boolean isHidden) {
        if (catID==null || catID.isEmpty()) {
            return false;
        }
        try {
            SQLiteDatabase db = this.getWritableDatabase();

            // Log.d("DB", "adding catID " + catID);
            ContentValues values = new ContentValues();

            int flags = 0;
            if (isTiny) flags |= FLAGS_ISTINY;
            if (isHidden) flags |= FLAGS_ISHIDDEN;

            values.put(LABEL, displayName);
            values.put(LABELFULL, displayNameFull);
            values.put(FLAGS, flags);

            db.update(CATEGORIES_TABLE, values, CATID + "=?", new String[]{catID});
        } catch (Exception e) {
            Log.e("LaunchDB", "Can't select catID " + catID, e);
            return false;
        }
        return true;
    }

    public void hideCategory(String catID, boolean isHidden) {
        if (catID==null || catID.isEmpty()) {
            return;
        }
        boolean isTiny = isTinyCategory(catID);
        try {
            SQLiteDatabase db = this.getWritableDatabase();

            // Log.d("DB", "adding catID " + catID);
            ContentValues values = new ContentValues();

            int flags = 0;
            if (isTiny) flags |= FLAGS_ISTINY;
            if (isHidden) flags |= FLAGS_ISHIDDEN;

            values.put(FLAGS, flags);

            db.update(CATEGORIES_TABLE, values, CATID + "=?", new String[]{catID});
        } catch (Exception e) {
            Log.e("LaunchDB", "Can't select catID " + catID, e);

        }

    }

    public boolean deleteCategory(String catID) {
        if (catID==null || catID.isEmpty()) {
            return false;
        }

        SQLiteDatabase db = this.getWritableDatabase();
        try {

            db.beginTransaction();

            {
                //delete the shortcuts we made
                if (catID.equals(Categories.CAT_DUMB)) {
                    db.delete(APP_TABLE, CATID + "=? and " + ACTVNAME + " like ?", new String[]{Categories.CAT_DUMB, AppLauncher.OLDSHORTCUT + "%"});
                }
                //Update existing apps with the deleted category to category other
                ContentValues values = new ContentValues();
                values.put(CATID, Categories.CAT_OTHER);
                db.update(APP_TABLE, values, CATID + "=?", new String[]{catID});
            }

            {
                //Move old cat app order to category other
                List<ComponentName> oldAppOrder = getAppCategoryOrder(catID);
                List<ComponentName> otherAppOrder = getAppCategoryOrder(Categories.CAT_OTHER);

                otherAppOrder.addAll(oldAppOrder);
                setAppCategoryOrder(Categories.CAT_OTHER, otherAppOrder, true);
            }

            db.delete(CATEGORIES_TABLE, CATID + "=?", new String[]{catID});
            db.setTransactionSuccessful();
        } catch (Exception e) {
            Log.e("LaunchDB", "Can't delete catID " + catID, e);
            return false;
        } finally {
            db.endTransaction();
        }
        return true;
    }

    public List<String> getCategories() {
        List<String> categories = new ArrayList<>();

        SQLiteDatabase db = this.getReadableDatabase();

        Cursor cursor = db.query(true, CATEGORIES_TABLE, new String[]{CATID}, null, null, null, null, INDEX, null);
        try {
            //Log.d("DB", "getting catagories");
            while (cursor.moveToNext()) {
                String cat = cursor.getString(cursor.getColumnIndex(CATID));
                if (cat!=null) {
                    categories.add(cat);
                }
                //          Log.d("DB", "got catID " + cursor.getString(0));
            }
        } finally {
            cursor.close();
        }
        cursor.close();
        return categories;
    }

    public String getCategoryDisplay(String catID) {
        String display = null;
        SQLiteDatabase db = this.getReadableDatabase();

        Cursor cursor = db.query(CATEGORIES_TABLE, new String[]{LABEL}, CATID + "=?", new String[]{catID}, null, null, null, null);
        try {
            if (cursor.moveToNext()) {
                display = cursor.getString(cursor.getColumnIndex(LABEL));
            }
        } finally {
            cursor.close();
        }
        return display;
    }

    public String getCategoryDisplayFull(String catID) {
        String display = null;
        SQLiteDatabase db = this.getReadableDatabase();

        Cursor cursor = db.query(CATEGORIES_TABLE, new String[]{LABELFULL}, CATID + "=?", new String[]{catID}, null, null, null, null);
        try {
            if (cursor.moveToNext()) {
                display = cursor.getString(cursor.getColumnIndex(LABELFULL));
            }
        } finally {
            cursor.close();
        }
        return display;
    }

    public boolean isTinyCategory(String catID) {

        SQLiteDatabase db = this.getReadableDatabase();

        Cursor cursor = db.query(CATEGORIES_TABLE, new String[]{FLAGS}, CATID + "=?", new String[]{catID}, null, null, null, null);
        boolean tiny = false;
        try {
            if (cursor.moveToNext()) {
                tiny = ((cursor.getShort(cursor.getColumnIndex(FLAGS)) & FLAGS_ISTINY) == FLAGS_ISTINY);
            }
        } catch (Exception e) {
            Log.e("LaunchDB", "tiny error.", e);
        } finally {
            cursor.close();
        }
        return tiny;
    }


    public boolean isHiddenCategory(String catID) {

        SQLiteDatabase db = this.getReadableDatabase();

        Cursor cursor = db.query(CATEGORIES_TABLE, new String[]{FLAGS}, CATID + "=?", new String[]{catID}, null, null, null, null);
        boolean hidden = false;
        try {
            if (cursor.moveToNext()) {
                hidden = ((cursor.getShort(cursor.getColumnIndex(FLAGS)) & FLAGS_ISHIDDEN) == FLAGS_ISHIDDEN);
            }
        } catch (Exception e) {
            Log.e("LaunchDB", "tiny error.", e);
        } finally {
            cursor.close();
        }
        return hidden;
    }


    public void setCategoryOrder(ViewGroup container) {

        List<String> cats = new ArrayList<>();

        for (int i = 0; i < container.getChildCount(); i++) {
            Object tag = container.getChildAt(i).getTag();
            if (tag instanceof String) {
                cats.add((String) tag);
            }
        }

        setCategoryOrder(cats);
    }

    public void setCategoryOrder(List<String> cats) {
        SQLiteDatabase db = this.getWritableDatabase();

        try {
            db.beginTransaction();

            for (int i = 0; i < cats.size(); i++) {

                ContentValues values = new ContentValues();

                values.put(INDEX, i);

                db.update(CATEGORIES_TABLE, values, CATID + "=?", new String[]{cats.get(i)});
            }

            db.setTransactionSuccessful();
        } catch (Exception e) {
            Log.e("LaunchDB", "Can't setCategoryOrder ", e);

        } finally {
            db.endTransaction();
        }
    }

    public void setAppCategoryOrder(String catID, ViewGroup container) {

        if (container==null) return;

        List<AppLauncher> apps = new ArrayList<>();

        for (int i = 0; i < container.getChildCount(); i++) {
            Object tag = container.getChildAt(i).getTag();
            if (tag instanceof AppLauncher) {
                apps.add((AppLauncher) tag);
            }
        }

        setAppCategoryOrder(catID, apps);
    }

    public void setAppCategoryOrder(String catID, List<AppLauncher> apps) {
        List<ComponentName> actvnames = new ArrayList<>();
        for (AppLauncher app : apps) {
            actvnames.add(new ComponentName(app.getPackageName(), app.getActivityName()));
        }
        setAppCategoryOrder(catID, actvnames, true);
    }

    public void setAppCategoryOrder(String catID, List<ComponentName> actvnames, boolean dummy) {
        SQLiteDatabase db = this.getWritableDatabase();
       // Log.d("db", "setAppCategoryOrder " + catID);
        try {
            db.beginTransaction();
            db.delete(APP_ORDER_TABLE, CATID + "=?", new String[]{catID}); //CATID, PKGNAME, INDEX};

            for (int i = 0; i < actvnames.size(); i++) {
                try {
                    ContentValues values = new ContentValues();
                    values.put(CATID, catID);
                    values.put(ACTVNAME, actvnames.get(i).getClassName());
                    values.put(PKGNAME, actvnames.get(i).getPackageName());
                    values.put(INDEX, i);
                    db.insert(APP_ORDER_TABLE, null, values);
                } catch (Exception e) {
                    Log.e("LaunchDB", "Can't setAppCategoryOrder for catID " + catID, e);
                }
                // Log.d("db", "  " + i + " " + actvnames.get(i).getPackageName());
            }

            db.setTransactionSuccessful();
        } catch (Exception e) {
            Log.e("LaunchDB", "Can't setAppCategoryOrder for catID " + catID, e);

        } finally {
            if (db.inTransaction()) {
                db.endTransaction();
            }
        }
    }

    public void addAppCategoryOrder(String catID, ComponentName actvname) {
        SQLiteDatabase db = this.getWritableDatabase();

        try {

            ContentValues values = new ContentValues();
            values.put(CATID, catID);
            values.put(ACTVNAME, actvname.getClassName());
            values.put(PKGNAME, actvname.getPackageName());
            values.put(INDEX, 100);
            db.insert(APP_ORDER_TABLE, null, values);

        } catch (Exception e) {
            Log.e("LaunchDB", "Can't setAppCategoryOrder for catID " + catID, e);

        }
    }

    public List<ComponentName> getAppCategoryOrder(String catID) {
        List<ComponentName> actvnames = new ArrayList<>();
        SQLiteDatabase db = this.getReadableDatabase();
        //Log.d("db", "getAppCategoryOrder " + catID);

        Cursor cursor = db.query(APP_ORDER_TABLE, new String[]{ACTVNAME, PKGNAME}, CATID + "=?", new String[]{catID}, null, null, INDEX);
        try {
           // int i=0;
            while (cursor.moveToNext()) {
                try {
                    actvnames.add(new ComponentName(cursor.getString(cursor.getColumnIndex(PKGNAME)), cursor.getString(cursor.getColumnIndex(ACTVNAME))));
                } catch (Exception e) {
                    Log.e("LaunchDB", e.getMessage(), e);
                }
              //  Log.d("db", "  " + i + " " + cursor.getInt(2) + " " + cursor.getString(1));
              //  i++;
            }
        } finally {
            cursor.close();
        }
        return actvnames;

    }

    public void appLaunched(ComponentName activityname) {

        SQLiteDatabase db = this.getWritableDatabase();

        ContentValues values = new ContentValues();
        values.put(ACTVNAME, activityname.getClassName());
        values.put(PKGNAME, activityname.getPackageName());
        values.put(TIME, System.currentTimeMillis());
        db.insert(APP_HISTORY_TABLE, null, values);


        //delete old apps
        Calendar old = Calendar.getInstance();
        old.add(Calendar.MONTH, -2);

        db.delete(APP_HISTORY_TABLE, TIME+"<?", new String[] { old.getTimeInMillis() + ""});

    }

    public void deleteAppLaunchedRecord(ComponentName appname) {

        String actvname = appname.getClassName();
        String pkgname =  appname.getPackageName();

        SQLiteDatabase db = this.getWritableDatabase();


        db.delete(APP_HISTORY_TABLE, ACTVNAME + "=? and " + PKGNAME + "=?", new String[]{actvname, pkgname});

    }

    public void deleteAppLaunchedRecords() {

        SQLiteDatabase db = this.getWritableDatabase();


        db.delete(APP_HISTORY_TABLE, null, null);

    }

    public List<ComponentName> getAppLaunchedList() {

        List<ComponentName> activitynames = new ArrayList<>();

        SQLiteDatabase db = this.getReadableDatabase();

        Cursor cursor = db.query(
                false,
                APP_HISTORY_TABLE,
                new String[]{ACTVNAME, PKGNAME},
                null, null, null, null, TIME + " desc", null);

        try {
            while (cursor.moveToNext()) {
                if (!cursor.isNull(0) && !cursor.isNull(1) ) {
                    ComponentName cn = new ComponentName(cursor.getString(cursor.getColumnIndex(PKGNAME)), cursor.getString(cursor.getColumnIndex(ACTVNAME)));
                    if (!activitynames.contains(cn)) {
                        activitynames.add(cn);
                    }
                }
            }
        } finally {
            cursor.close();
        }
        return activitynames;

    }

    public List<ComponentName> getAppLaunchedListUsage() {

        List<ComponentName> activitynames = new ArrayList<>();

        SQLiteDatabase db = this.getReadableDatabase();

        Cursor cursor = db.query(
                true,
                APP_HISTORY_TABLE,
                new String[]{ACTVNAME, PKGNAME},
                null, null, "1,2", null, "count(*) desc", null);

        try {
            while (cursor.moveToNext()) {
                if (!cursor.isNull(0) && !cursor.isNull(1) ) {
                    activitynames.add(new ComponentName(cursor.getString(cursor.getColumnIndex(PKGNAME)), cursor.getString(cursor.getColumnIndex(ACTVNAME))));
                }
            }
        } finally {
            cursor.close();
        }
        return activitynames;

    }


    public int getAppLaunchedCount(ComponentName activityname) {
        return getAppLaunchedCount(activityname, new Date(0));
    }

    public int getAppLaunchedCount(ComponentName activityname, Date after) {
        SQLiteDatabase db = this.getReadableDatabase();

        Cursor cursor = db.query(
                APP_HISTORY_TABLE,
                new String[]{"count(*)"},
                ACTVNAME + "=? and " + PKGNAME + "=? and "+ TIME + ">?",
                new String[]{activityname.getClassName(), activityname.getPackageName(), after.getTime() + ""},
                null, null, null, null);

        int count = 0;
        try {
            if (cursor.moveToNext()) {
                count = cursor.getInt(0);
            }
        } finally {
            cursor.close();
        }
        return count;

    }

//    private String condensePackname(String packageOrActivityname) {
//
//        packageOrActivityname = packageOrActivityname
//                .replaceAll("\\bcom\\b", "c")
//                .replaceAll("\\borg\\b", "o")
//                .replaceAll("\\bmobi\\b", "m")
//                .replaceAll("\\bmobile\\b", "mi")
//                .replaceAll("\\bnet\\b", "n")
//                .replaceAll("\\bair\\b", "a")
//                .replaceAll("\\bandroid\\b", "an")
//        ;
//
//        return packageOrActivityname;
//    }


    private String condensePackname(String packageOrActivityname) {

        packageOrActivityname = packageOrActivityname
                .replaceAll("\\bcom\\b", "")
        ;

        return packageOrActivityname;
    }

    public String getCategoryForPackage(String packagename) {
        SQLiteDatabase db = this.getReadableDatabase();

        // {ACTVNAME, PKGNAME, CATID, LEVEL};

        Cursor cursor = db.query(
                APP_CAT_MAP_TABLE, new String[]{CATID},
                PKGNAME + "=?",   new String[]{condensePackname(packagename)}, null, null, LEVEL + "");

        String catid = null;
        try {
            if (cursor.moveToNext()) {
                catid = cursor.getString(cursor.getColumnIndex(CATID));
                catid = Categories.unabbreviate(catid);
            }
        } finally {
            cursor.close();
        }
        return catid;
    }


    public String getCategoryForActivity(String activityname) {
        SQLiteDatabase db = this.getReadableDatabase();

        // {ACTVNAME, PKGNAME, CATID, LEVEL};

        Cursor cursor = db.query(
                APP_CAT_MAP_TABLE, new String[]{CATID},
                ACTVNAME + "=?",   new String[]{condensePackname(activityname)}, null, null, LEVEL + "");

        String catid = null;
        try {
            if (cursor.moveToNext()) {
                catid = cursor.getString(cursor.getColumnIndex(CATID));
                catid = Categories.unabbreviate(catid);
            }
        } finally {
            cursor.close();
        }
        return catid;
    }

    private void loadCategories(SQLiteDatabase db, boolean isactivity, int set, int level) {

        Log.d("LaunchDB", "loadCategories " + level + " " + set);

        InputStream inputStream = mContext.getResources().openRawResource(set);
        String line;
        String[] lineSplit;

        int count = 0;
        try {
            db.beginTransaction();
            BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, "UTF-8"));
            String sql = "INSERT INTO " + APP_CAT_MAP_TABLE + " values(?,?,?,?)";
            SQLiteStatement statement = db.compileStatement(sql);

            while ((line = reader.readLine()) != null) {
                if (!line.isEmpty() && !line.startsWith("#")) {
                    lineSplit = line.split("=",2);
                    if (lineSplit.length==2) {  // {ACTVNAME, PKGNAME, CATID, LEVEL};
                        statement.clearBindings();
                        if (isactivity) {
                            statement.bindString(1, lineSplit[0]);
                            statement.bindString(2, "");
                        } else {
                            statement.bindString(1, "");
                            statement.bindString(2, lineSplit[0]);
                        }
                        statement.bindString(3, lineSplit[1]);
                        statement.bindLong(4, level);
                        try {
                            statement.executeInsert();
                        } catch (Exception e) {
                            Log.d("LaunchDB", "Can't add category", e);
                        }

                        if (count++%1000==0) {
                            db.setTransactionSuccessful();
                            db.endTransaction();
                            db.beginTransaction();
                        }
                    }
                }
            }
            db.setTransactionSuccessful();
        } catch (Exception ex) {
            ex.printStackTrace();
        } finally {
            db.endTransaction();
            try {
                if (inputStream != null) inputStream.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        Log.d("LaunchDB", " loaded " + count + " rows");

    }




    //synchronize these so that we can also synchronize on the backup and restore, just in case

    @Override
    public synchronized SQLiteDatabase getReadableDatabase() {
        return super.getReadableDatabase();
    }

    @Override
    public synchronized SQLiteDatabase getWritableDatabase() {
        return super.getWritableDatabase();
    }



    public static final String BK_PRE = "db_bak.";


    public List<String> listBackups() {
        List<String> list = new ArrayList<>();
        Pattern bakpat = Pattern.compile("^" + Pattern.quote(BK_PRE)  + "(.+)");
        for (String file: mContext.fileList()) {
            Matcher m = bakpat.matcher(file);
            if (m.matches()) {
                list.add(m.group(1));
            }
        }
        Collections.sort(list);
        Collections.reverse(list);
        return list;
    }

    public boolean hasBackup(String backupName) {
        return listBackups().contains(backupName);
    }

    public File pullBackup(String backupName) {
        return mContext.getFileStreamPath(BK_PRE + backupName);
    }

    public synchronized File backup(String optionalName) {

        close();

        if (optionalName!=null && optionalName.length()>0) {
            optionalName =  "." + optionalName.replaceAll("[./(){}\"*|\\\\$]", "_");
            if (optionalName.length()>20) {
                optionalName = optionalName.substring(0,20);
            }
        } else {
            optionalName = "";
        }

        List<File> files = SpecialIconStore.getAllIcons(mContext);

        files.add(mContext.getDatabasePath(DATABASE_NAME));

        files.add(FsTools.saveSharedPreferencesToFile(mContext.getSharedPreferences("theme", Context.MODE_PRIVATE), mContext.getFileStreamPath("themes.prefs")));

        files.add(FsTools.saveSharedPreferencesToFile(mContext.getSharedPreferences("default", Context.MODE_PRIVATE), mContext.getFileStreamPath("default.prefs")));

        files.add(FsTools.saveSharedPreferencesToFile(PreferenceManager.getDefaultSharedPreferences(mContext.getApplicationContext()), mContext.getFileStreamPath("main.prefs")));


        File destFile = mContext.getFileStreamPath(BK_PRE + (new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss", Locale.getDefault()).format(new Date())) + optionalName + ".zip");

        return FsTools.compressFiles(destFile, files.toArray(new File[0]));
    }



    public boolean restoreFullpathBackup( String filePath) {
        File srcFile = new File(filePath);

        return restore(srcFile);
    }

    public boolean restoreFullpathBackup(File file) {
        File srcFile = new File(file.getPath());

        return restore(srcFile);
    }
    public boolean restoreBackup(String backupName) {

        File srcFile = mContext.getFileStreamPath(BK_PRE + backupName);

        return restore(srcFile);
    }

    @SuppressLint("ApplySharedPref")
    private synchronized boolean restore(File srcFile) {

        boolean ret = false;
        if (srcFile.exists() && srcFile.canRead()) {

            File destFile = mContext.getDatabasePath(DATABASE_NAME);

            if (srcFile.getName().toLowerCase().endsWith(".zip")) {

                List<File> files = FsTools.uncompressFiles(srcFile, mContext.getFilesDir());


                FsTools.loadSharedPreferencesFromFile(mContext.getSharedPreferences("theme", Context.MODE_PRIVATE), mContext.getFileStreamPath("themes.prefs"));
                FsTools.loadSharedPreferencesFromFile(mContext.getSharedPreferences("default", Context.MODE_PRIVATE), mContext.getFileStreamPath("default.prefs"));

                SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(mContext.getApplicationContext());
                prefs.edit().putBoolean(Theme.PREFS_UPDATE_KEY, true).commit();
                FsTools.loadSharedPreferencesFromFile(prefs, mContext.getFileStreamPath("main.prefs"));
                prefs.edit().remove(Theme.PREFS_UPDATE_KEY).commit();


                close();
                FsTools.copyFile(mContext.getFileStreamPath(destFile.getName()), destFile);

                ret = true;

            } else {

                close();
                ret = FsTools.decompressFile(srcFile, destFile) != null;

            }
            try {
                getWritableDatabase().close();
            } catch (Exception e) {
                Log.e("LaunchDB", "couldn't load db after restore", e);
                return false;
            }
        }
        return ret;
    }


    public boolean deleteBackup(String backupName) {

        File srcFile = mContext.getFileStreamPath(BK_PRE + backupName);

        return srcFile.delete();

    }

    public synchronized void deleteDatabase() {
        close();
        Thread.yield();
        mContext.deleteDatabase(DB.DATABASE_NAME);
        Thread.yield();
        File destFile = mContext.getDatabasePath(DATABASE_NAME);
        if (destFile!=null && destFile.exists()) {
            if (destFile.delete()) {
                Log.d("LaunchTime", "db was there");
            }
        }

    }

    @Override
    public synchronized void close() {
        if (mDBClosedListener!=null) {
            mDBClosedListener.onDBClosed();
        }
        super.close();
    }

    public interface DBClosedListener {
        void onDBClosed();
    }

///


    public void upgradeTo2(SQLiteDatabase sqLiteDatabase) {
        sqLiteDatabase.execSQL(APP_TABLE_CREATE);
        for (String createind : appcolumnsindex) {
            sqLiteDatabase.execSQL(buildIndexStmt(APP_TABLE, createind));
        }

        boolean first = true;
        String cols = "";
        for (String col : appcolumns) {
            // if (oldVersion<6 && col.equals(CUSTOMLABEL)) continue;
            if (first) first = false;
            else cols += ", ";
            cols += col;

        }


        Log.i("db", "copy to new table");

        sqLiteDatabase.execSQL(String.format("insert into %s (%s) select %s from %s", APP_TABLE, cols, cols, APP_TABLE_OLD));
        sqLiteDatabase.execSQL("drop table " + APP_TABLE_OLD);

        sqLiteDatabase.execSQL("alter table " + APP_ORDER_TABLE + " add column " + PKGNAME + " TEXT");
        sqLiteDatabase.execSQL("alter table " + APP_HISTORY_TABLE + " add column " + PKGNAME + " TEXT");

        Cursor cursor = sqLiteDatabase.query(APP_TABLE, new String[]{ACTVNAME, PKGNAME}, null, null, null, null, ACTVNAME);
        try {
            while (cursor.moveToNext()) {
                String actv = cursor.getString(cursor.getColumnIndex(ACTVNAME));
                String pkg = cursor.getString(cursor.getColumnIndex(PKGNAME));

                ContentValues values = new ContentValues();
                values.put(PKGNAME, pkg);
                sqLiteDatabase.update(APP_ORDER_TABLE, values, ACTVNAME + "=?", new String[]{actv});
                values.put(PKGNAME, pkg);
                sqLiteDatabase.update(APP_HISTORY_TABLE, values, ACTVNAME + "=?", new String[]{actv});
            }
        } finally {
            cursor.close();
        }

        ContentValues values = new ContentValues();
        values.put(INDEX, 1);

        //move search/recent away from top to correct old order.
        //{CATID, LABEL, LABELFULL, FLAGS, INDEX};
        if (sqLiteDatabase.update(CATEGORIES_TABLE, values, CATID + "=\"" + Categories.CAT_SEARCH + "\" and " + INDEX + "=0", null)>0) {
            values.put(INDEX, 0);
            sqLiteDatabase.update(CATEGORIES_TABLE, values, CATID + "!=\"" + Categories.CAT_SEARCH + "\" and " + INDEX + "=1", null);
        }
    }



} public class QuickRow {

    public static final String QUICK_ROW_CAT = "QuickRow";
    private final GridLayout mQuickRow;
    private final HorizontalScrollView mQuickRowScroller;

    private final MainActivity mMainActivity;

    private final Style mStyle;

    public QuickRow(final View.OnDragListener dragListener, MainActivity mainActivity) {

        mMainActivity = mainActivity;
        mStyle = GlobState.getStyle(mMainActivity);

        mQuickRow = mMainActivity.findViewById(R.id.layout_quickrow);

        mQuickRowScroller = mMainActivity.findViewById(R.id.layout_quickrow_scroll);

        mQuickRow.setOnDragListener(dragListener);
        mQuickRowScroller.setOnDragListener(new View.OnDragListener() {
            @Override
            public boolean onDrag(View view, DragEvent dragEvent) {
                return dragListener.onDrag(mQuickRow, dragEvent);
            }
        });
    }

    public GridLayout getGridLayout() {
        return mQuickRow;
    }

    public HorizontalScrollView getScroller() {
        return mQuickRowScroller;
    }

    public int getScrollPos() {
        return mQuickRowScroller.getScrollX();
    }

    public void scrollToStart() {
        mQuickRowScroller.smoothScrollTo(0, 0);
    }


    public boolean appAlreadyHere(AppLauncher app) {
        //prevent copies of the same app on the quickrow
        for (int i = 0; i < mQuickRow.getChildCount(); i++) {

            AppLauncher inbar = (AppLauncher) mQuickRow.getChildAt(i).getTag();
            if (app.getLinkBaseActivityName().equals(inbar.getLinkBaseActivityName())) {
                return true;
            }
        }
        return false;
    }

    public void clearIcons() {
        for (int i = 0; i < mQuickRow.getChildCount(); i++) {

            AppLauncher inbar = (AppLauncher) mQuickRow.getChildAt(i).getTag();
            inbar.clearDrawable();
        }
    }


    public void setCenterIcons(final boolean center) {
        mQuickRow.postDelayed(new Runnable() {
            @Override
            public void run() {
                float num = mMainActivity.getScreenDimensions().x / (float)mStyle.getLauncherSize() - 1;

                //Log.d("Quick", "cols = " + num);
                FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams)mQuickRow.getLayoutParams();
                if (center && mQuickRow.getChildCount()<=num) {
                    lp.gravity = Gravity.CENTER_HORIZONTAL;
                } else {
                    lp.gravity = Gravity.START;
                }
                mQuickRow.setLayoutParams(lp);

            }
        },1000);

    }

    public boolean isSelf(View other) {
        return other == mQuickRow;
    }

    public void repopulate() {
        final List<ComponentName> quickRowOrder = db().getAppCategoryOrder(QUICK_ROW_CAT);

        mQuickRow.postDelayed(new Runnable() {
            @Override
            public void run() {
                mQuickRow.removeAllViews();
                for (ComponentName actvname : quickRowOrder) {
                    AppLauncher app = db().getApp(actvname);
                    if (!appAlreadyHere(app)) {
                        ViewGroup item = mMainActivity.getLauncherView(app, true);
                        if (item != null) {
                            GridLayout.LayoutParams lp = new GridLayout.LayoutParams();
                            lp.columnSpec = GridLayout.spec(GridLayout.UNDEFINED, GridLayout.TOP);
                            mQuickRow.addView(item, lp);
                        }
                    }
                }

            }
        }, 400);

    }


    public void processQuickApps(List<AppLauncher> launchers) {
        List<AppLauncher> quickRowApps = new ArrayList<>();
        final List<ComponentName> quickRowOrder = db().getAppCategoryOrder(QUICK_ROW_CAT);

        boolean newstuff = DefaultApps.checkDefaultApps(mQuickRow.getContext(), launchers, quickRowOrder);

        for (ComponentName compname: quickRowOrder) {

            //Log.d("Quick", compname.flattenToString());
            AppLauncher app = null;
            for (AppLauncher a: launchers) {
                if (compname.equals(a.getComponentName())) {
                    app = a;
                    break;
                }
            }
            if (app==null) {
                //Log.d("Quick", "Not found: " + compname.flattenToString());
              newstuff = true; //app could have been uninstalled
            } else if (compname.equals(app.getComponentName())) {
                //Log.d("Quick", "Found: " + compname.flattenToString());
               // AppLauncher qapp = AppLauncher.createAppLauncher(app, true);
                quickRowApps.add(app);

            }

        }
        if (newstuff) db().setAppCategoryOrder(QUICK_ROW_CAT, quickRowApps);
    }


//    public void fillQuickApps(List<AppLauncher> quickRowApps, PackageManager packageMan) {
//
//        final List<ComponentName> quickRowOrder = db().getAppCategoryOrder(QUICK_ROW_CAT);
//
//
//        mQuickRow.removeAllViews();
//        for (ComponentName actvname : quickRowOrder) {
//            for (AppLauncher app : quickRowApps) {
//                if (app.getComponentName().equals(actvname)) {
//                    ViewGroup item = mMainActivity.getLauncherView(app, true);
//                    if (item!=null) {
//                        app.loadAppIconAsync(mQuickRow.getContext(), packageMan);
//                        GridLayout.LayoutParams lp = new GridLayout.LayoutParams();
//                        lp.columnSpec = GridLayout.spec(GridLayout.UNDEFINED, GridLayout.TOP);
//                        mQuickRow.addView(item, lp);
//                    }
//                }
//            }
//        }
//
//    }

    public void removeFromQuickApps(ComponentName actvname) {
        for (int i = mQuickRow.getChildCount()-1; i>=0; i--) {
            AppLauncher app = (AppLauncher) mQuickRow.getChildAt(i).getTag();
            if (app != null && actvname.equals(app.getComponentName())) {
                mQuickRow.removeView(mQuickRow.getChildAt(i));
            }
        }
    }

    private DB db() {
        return GlobState.getGlobState(mQuickRow.getContext()).getDB();
    }
} public class SearchBox {
    private final ViewGroup mSearchView;

    private int mSearchRememberScrollPos;

    private final AppCursorAdapter mSearchAdapter;
    private final EditText mSearchbox;

    private final MainActivity mMainActivity;
    private final InteractiveScrollView mIconSheetScroller;

    public SearchBox(final MainActivity mainActivity, InteractiveScrollView iconSheetScroller) {

        mMainActivity = mainActivity;
        mIconSheetScroller = iconSheetScroller;


        mSearchView = (ViewGroup) LayoutInflater.from(mainActivity).inflate(R.layout.search_layout, null);


        ImageView quickasett = mSearchView.findViewById(R.id.quick_settings_android);
        quickasett.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                launchAction(Settings.ACTION_SETTINGS, "settings");
            }
        });

        ImageView quicklsett = mSearchView.findViewById(R.id.quick_settings_launchtime);
        quicklsett.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                MainActivity.openSettings(mainActivity);
            }
        });


        ImageView quickbsett = mSearchView.findViewById(R.id.quick_settings_bt);
        quickbsett.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                launchActivity("com.android.settings",
                        "com.android.settings.bluetooth.BluetoothSettings",
                        "BluetoothSettings",
                        new Runnable() {
                            @Override
                            public void run() {
                                launchAction(Settings.ACTION_BLUETOOTH_SETTINGS, "BluetoothSettings");
                            }
                        });
            }
        });


        ImageView quickvsett = mSearchView.findViewById(R.id.quick_settings_vol);
        quickvsett.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                launchActivity("com.android.settings",
                        "com.android.settings.SoundSettings",
                        "VolumeSettings",
                        new Runnable() {
                            @Override
                            public void run() {
                                launchAction(Settings.ACTION_SOUND_SETTINGS, "VolumeSettings");
                            }
                        });
            }
        });

        ImageView quickwisett = mSearchView.findViewById(R.id.quick_settings_wifi);
        quickwisett.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                launchActivity("com.android.settings",
                        "com.android.settings.wifi.WifiSettings",
                        "WifiSettings",
                        new Runnable() {
                            @Override
                            public void run() {
                                launchAction(Settings.ACTION_WIFI_SETTINGS, "WifiSettings");
                            }
                        });
            }
        });

        ImageView quickdevsett = mSearchView.findViewById(R.id.quick_settings_dev);
        quickdevsett.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                MsgBox.alert(mMainActivity, mMainActivity.getString(android.R.string.dialog_alert_title),
                        mMainActivity.getString(R.string.devops_warn),
                        true,
                        true,
                        new Runnable() {
                    @Override
                    public void run() {
                        launchActivity("com.android.settings",
                                "com.android.settings.DevelopmentSettings",
                                "DevOps",
                                new Runnable() {
                                    @Override
                                    public void run() {
                                        launchAction(Settings.ACTION_APPLICATION_DEVELOPMENT_SETTINGS, "DevOps", new Runnable() {
                                            @Override
                                            public void run() {
                                                launchAction(Settings.ACTION_DEVICE_INFO_SETTINGS, "DevOps");
                                            }
                                        });
                                    }
                                });

                    }
                });
            }
        });

        mSearchbox = mSearchView.findViewById(R.id.search_box);

        mSearchAdapter = new AppCursorAdapter(mainActivity, mSearchbox, R.layout.search_item, 0);
        StaticListView list = mSearchView.findViewById(R.id.search_dropdownarea);

        list.setAdapter(mSearchAdapter);
        list.setOnItemClickListener(mSearchAdapter);

        list.setOnLoadCompleteListener(new StaticListView.OnLoadCompleteListener() {
            @Override
            public void loadComplete() {
                mIconSheetScroller.scrollTo(0, mSearchRememberScrollPos);
                mIconSheetScroller.postDelayed(new Runnable() {
                    @Override
                    public void run() {
                        mIconSheetScroller.scrollTo(0, mSearchRememberScrollPos);

                    }
                }, 100);
            }
        });


        mSearchView.findViewById(R.id.btn_clear_searchbox).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                mSearchbox.setText("");
                mSearchRememberScrollPos = 0;
            }
        });


        mSearchView.findViewById(R.id.btn_clear_recents).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {

                AlertDialog.Builder builder = new AlertDialog.Builder(mMainActivity)
                        .setTitle(R.string.clear_recent)
                        .setPositiveButton(R.string.clear, new DialogInterface.OnClickListener() {
                            @Override
                            public void onClick(DialogInterface dialogInterface, int i) {
                                db().deleteAppLaunchedRecords();
                                mMainActivity.populateRecentApps();
                            }
                        }).setNegativeButton(R.string.cancel, null);
                builder.show();

            }
        });

        refreshSearch(false);

    }

    private void launchActivity(String packageName, String classname, String name, Runnable failed) {
        try {
            Intent l = new Intent(Intent.ACTION_VIEW);
            l.setClassName(packageName, classname);
            l.setPackage(packageName);
            l.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
            mMainActivity.startActivity(l);
        } catch (Throwable t) {
            Log.e("short", t.getMessage(), t);
            if (failed!=null) {
                failed.run();
            } else {
                Toast.makeText(mMainActivity, "Couldn't start setting " + name, Toast.LENGTH_SHORT).show();
            }
        }
    }
    private void launchAction(String action, String name) {
        launchAction(action, name, null);
    }

    private void launchAction(String action, String name, Runnable failed) {
        try {
            Intent l = new Intent(action);
            l.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);

            mMainActivity.startActivity(l);
        } catch (Throwable t) {
            Log.e("short", t.getMessage(), t);
            if (failed!=null) {
                failed.run();
            } else {
                Toast.makeText(mMainActivity, "Couldn't start setting " + name, Toast.LENGTH_SHORT).show();
            }
        }
    }


    public void refreshSearch(boolean rememberPos) {
        if (rememberPos) mSearchRememberScrollPos = mIconSheetScroller.getScrollY();
        if (mSearchAdapter!=null) {
            mSearchAdapter.refreshCursor();
        }
    }

    public void closeSeachAdapter() {
        //close our search cursor, if needed
        if (mSearchAdapter!=null){
            mSearchAdapter.close();
        }
    }


    public ViewGroup getSearchView() {
        return mSearchView;
    }

    public String getSeachText() {
        return mSearchbox.getText().toString();
    }

    public void setSearchText(CharSequence text) {
        mSearchbox.setText(text);
    }

    private DB db() {
        return GlobState.getGlobState(mMainActivity).getDB();
    }
} public class Style {
    private int cattabTextColor;
    private int cattabTextColorInvert;
    private int cattabBackground;
    private int cattabSelectedBackground;
    private int cattabSelectedText;
    private int dragoverBackground;
    private int textColor;

    private final int backgroundDefault = Color.TRANSPARENT;

    private int wallpaperColor = Color.TRANSPARENT;
    private int calculatedWallpaperColor = Color.TRANSPARENT;


    private int iconTint = Color.TRANSPARENT;

    private boolean leftHandCategories;
    private float categoryTabFontSize = 16;
    private int categoryTabPaddingHeight = 25;

    private int cattabBackgroundHighContrast;
    private int cattabSelectedBackgroundHighContrast;

    private boolean centeredIcons;

    private int launcherIconSize = 55;
    private int launcherSize = 80;
    private int launcherFontSize = 12;

    private int mAnimationDuration;

    private final SharedPreferences mAppPreferences;

    private final Context mContext;

    public Style(Context context, SharedPreferences appPreferences) {

        mContext = context;
        mAppPreferences = appPreferences;
        readPrefs();

    }

    public int getWallpaperColor() {
        return wallpaperColor;
    }


    public enum CategoryTabStyle {Default, Normal, Selected, DragHover, Tiny, Hidden, None}

    public void styleCategoryStyle(final TextView categoryTab, CategoryTabStyle catstyle, boolean highContrast) {

        LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams)categoryTab.getLayoutParams();

        boolean isOnLeft = isLeftHandCategories();

        int padRight = 2;
        int padLeft = 6;

        if (isOnLeft) {
            padRight = 6;
            padLeft = 2;
        }
        lp.leftMargin = 2;
        lp.rightMargin = 2;


        Rect pad  = new Rect();

        int bgcolor = 0;
        categoryTab.setBackground(null);
        categoryTab.setShadowLayer(0, 0, 0, 0);

        switch (catstyle) {
            case Hidden:
                bgcolor = cattabBackground;
                if (isOnLeft) {
                    lp.rightMargin = 36;
                } else {
                    lp.leftMargin = 36;
                }

            case Tiny:
                categoryTab.setTextColor(cattabTextColor);

                if (bgcolor==0) {
                    bgcolor = highContrast ? cattabBackgroundHighContrast : cattabBackground;
                    if (isOnLeft) {
                        lp.rightMargin = 22;
                    } else {
                        lp.leftMargin = 22;
                    }
                }
                pad.set(padLeft, categoryTabPaddingHeight/5, padRight, categoryTabPaddingHeight/5);
                categoryTab.setTextSize(categoryTabFontSize-3);

                break;
            case DragHover:
                categoryTab.setTextColor(cattabTextColor);

                bgcolor = dragoverBackground;

                pad.set(padLeft, categoryTabPaddingHeight, padRight, categoryTabPaddingHeight);
                categoryTab.setTextSize(categoryTabFontSize + 1);
                categoryTab.setTextSize(categoryTabFontSize);
                if (isOnLeft) {
                    lp.rightMargin = 6;
                } else {
                    lp.leftMargin = 6;
                }
                break;
            case Selected:
                categoryTab.setTextColor(cattabSelectedText);

                bgcolor = highContrast?cattabSelectedBackgroundHighContrast:cattabSelectedBackground;

                pad.set(padLeft, categoryTabPaddingHeight+2, padRight, categoryTabPaddingHeight+2);
                categoryTab.setTextSize(categoryTabFontSize + 1);
                categoryTab.setShadowLayer(8, 4, 4, cattabTextColorInvert);

                categoryTab.clearAnimation();
                if (mAnimationDuration >0) {
                    categoryTab.animate().scaleX(1.3f).scaleY(1.3f)
                            .setInterpolator(new CycleInterpolator(1))
                            .setDuration(mAnimationDuration)
                            .setListener(new AnimatorListenerAdapter() {
                                @Override
                                public void onAnimationEnd(Animator animation) {
                                    categoryTab.setScaleX(1);
                                    categoryTab.setScaleY(1);
                                }

                                @Override
                                public void onAnimationCancel(Animator animation) {
                                    categoryTab.setScaleX(1);
                                    categoryTab.setScaleY(1);
                                }
                            }).start();
                } else {
                    categoryTab.setScaleX(1);
                    categoryTab.setScaleY(1);
                }
                lp.leftMargin = 1;
                lp.rightMargin = 1;
                break;

            case Normal:
            default:
                categoryTab.setTextColor(cattabTextColor);

                bgcolor = highContrast?cattabBackgroundHighContrast:cattabBackground;
                pad.set(padLeft, categoryTabPaddingHeight, padRight, categoryTabPaddingHeight);
                categoryTab.setTextSize(categoryTabFontSize);
                if (isOnLeft) {
                    lp.rightMargin = 22;
                } else {
                    lp.leftMargin = 22;
                }
        }

        categoryTab.setBackgroundColor(bgcolor);
        if (isRoundedTabs()) {
            categoryTab.setBackground(getBgDrawableFor(categoryTab,catstyle,highContrast));
        }
        categoryTab.setPadding(pad.left, pad.top, pad.right, pad.bottom);

        if (highContrast) {
            if (categoryTab.getAlpha()<.89f) {
                categoryTab.setAlpha(.9f);
            }
            lp.topMargin=2;
            lp.bottomMargin=2;
        } else {
            lp.topMargin=3;
            lp.bottomMargin=3;
        }

        categoryTab.setLayoutParams(lp);
    }

    public int getLauncherIconSize() {
        return launcherIconSize;
    }

    public int getLauncherSize() {
        return launcherSize;
    }

    public int getLauncherFontSize() {
        return launcherFontSize;
    }

    public void readPrefs() {
        //Checks application preferences and adjust accordingly

        leftHandCategories = mAppPreferences.getString(mContext.getString(R.string.pref_key_categories_loc), "right").equals("left");

        centeredIcons  = mAppPreferences.getBoolean(mContext.getString(R.string.pref_key_center_sheet), true);

        float density = mContext.getResources().getDisplayMetrics().density / 2.0f;

        if (density<.75f) density = .75f;
        if (density>1.5f) density = 1.5f;

        int tabsizePref = Integer.parseInt(mAppPreferences.getString(mContext.getString(R.string.pref_key_tabsize), "1"));
        switch (tabsizePref) {
            case 0:  //small
                categoryTabPaddingHeight = (int)(16 * density);
                categoryTabFontSize = 14;
                break;
            case 1:  //medium
                categoryTabPaddingHeight = (int)(20 * density);
                categoryTabFontSize = 16;
                break;
            case 2:  //large
                categoryTabPaddingHeight = (int)(25 * density);
                categoryTabFontSize = 18;
                break;
            case 3: //x-large
                categoryTabPaddingHeight = (int)(25 * density);
                categoryTabFontSize = 20;
                break;
        }

        mAnimationDuration = Integer.parseInt(mAppPreferences.getString(mContext.getString(R.string.pref_key_animate_duration), "150"));


        float iconsize = mContext.getResources().getDimension(R.dimen.icon_width);
        float iconfontsize = mContext.getResources().getDimension(R.dimen.launcher_fontsize);


        int iconsizePref = Integer.parseInt(mAppPreferences.getString(mContext.getString(R.string.pref_key_iconsize), "1"));
        switch (iconsizePref) {
            case 0:  //small
                launcherIconSize = (int)(iconsize*.74);
                launcherFontSize = (int)(iconfontsize*.87);
                launcherSize = (int)(launcherIconSize*1.3);
                break;
            case 1:  //medium
                launcherIconSize = (int)(iconsize*.95);
                launcherFontSize = (int)iconfontsize;
                launcherSize = (int)(launcherIconSize*1.32);
                break;
            case 2:  //large
                launcherIconSize = (int)(iconsize*1.3);
                launcherFontSize = (int)(iconfontsize*1.3);
                launcherSize = (int)(launcherIconSize*1.33);
                break;
            case 3: //x-large
                launcherIconSize = (int)(iconsize*1.8);
                launcherFontSize = (int)(iconfontsize*1.7);
                launcherSize = (int)(launcherIconSize*1.3);
                break;
        }
        //Log.d("style", "launcherFontSize = " + launcherFontSize);

        cattabBackground = mAppPreferences.getInt(mContext.getString(R.string.pref_key_cattab_background), getResColor(R.color.cattab_background));
        cattabSelectedBackground = mAppPreferences.getInt(mContext.getString(R.string.pref_key_cattabselected_background), getResColor(R.color.cattabselected_background));
        cattabSelectedText = mAppPreferences.getInt(mContext.getString(R.string.pref_key_cattabselected_text), getResColor(R.color.cattabselected_text));

        dragoverBackground = mAppPreferences.getInt(mContext.getString(R.string.pref_key_dragover_background), getResColor(R.color.dragover_background));

        cattabTextColor =  mAppPreferences.getInt(mContext.getString(R.string.pref_key_cattabtextcolor), getResColor(R.color.textcolor));
        cattabTextColorInvert = mAppPreferences.getInt(mContext.getString(R.string.pref_key_cattabtextcolorinv), getResColor(R.color.textcolorinv));

        textColor = mAppPreferences.getInt(mContext.getString(R.string.pref_key_textcolor), getResColor(R.color.textcolor));

        wallpaperColor = mAppPreferences.getInt(mContext.getString(R.string.pref_key_wallpapercolor), getResColor(R.color.wallpaper_color));

        iconTint = mAppPreferences.getInt(mContext.getString(R.string.pref_key_icon_tint), Color.TRANSPARENT);

        cattabBackgroundHighContrast = cattabBackground;

        int alpha; // = Color.alpha(cattabBackground);
        //if (alpha<80) alpha=80;
       // cattabBackgroundHighContrast = Color.argb(alpha, Color.red(cattabBackground), Color.green(cattabBackground), Color.blue(cattabBackground));

        alpha = Color.alpha(cattabBackground);
        if (alpha<80) {
            alpha=80;
            cattabBackgroundHighContrast = Color.argb(alpha, Color.red(cattabBackground)*5/6, Color.green(cattabBackground)*5/6, Color.blue(cattabBackground)*5/6);
        } else {
            cattabBackgroundHighContrast = cattabBackground;
        }

        alpha = Color.alpha(cattabSelectedBackground);
        if (alpha<80) {
            alpha=80;
            cattabSelectedBackgroundHighContrast = Color.argb(alpha, Color.red(cattabSelectedBackground)*5/6, Color.green(cattabSelectedBackground)*5/6, Color.blue(cattabSelectedBackground)*5/6);
        } else {
            cattabSelectedBackgroundHighContrast = cattabSelectedBackground;
        }

        bgDrawables.clear();

        calculateWallpaperColor();
    }

    public void calculateWallpaperColor() {
        try {
            WallpaperManager wm = (WallpaperManager) mContext.getSystemService(Context.WALLPAPER_SERVICE);
            mWallpaper = null;
            if (wm != null) {
                WallpaperInfo wi = wm.getWallpaperInfo();
                if (wi != null) {
                    mWallpaper = wi.loadThumbnail(mContext.getPackageManager());
                }
                if (mWallpaper == null) {
                    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O_MR1 || mContext.checkSelfPermission("android.permission.READ_EXTERNAL_STORAGE") == PackageManager.PERMISSION_GRANTED) {
                        mWallpaper = wm.getDrawable();
                    } else {
                        WallpaperColors wpc = wm.getWallpaperColors(WallpaperManager.FLAG_SYSTEM);

                        if (wpc != null) {
                            mWallpaper = new ColorDrawable(wpc.getPrimaryColor().toArgb());
                        }
                    }
                }
                if (mWallpaper == null) {
                    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
                        mWallpaper = wm.getBuiltInDrawable();
                    }
                }
            }


            if (mWallpaper != null) {
                Drawable wallp = mWallpaper.mutate();
                wallp.setColorFilter(wallpaperColor, PorterDuff.Mode.SRC_ATOP);
                //wallp = wallp.mutate();
                Bitmap bm = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888);
                Canvas c = new Canvas();
                //c.drawColor(Color.TRANSPARENT, PorterDuff.Mode.SRC);
                c.setBitmap(bm);
                wallp.setBounds(0, 0, bm.getWidth(), bm.getHeight());
                wallp.draw(c);
                calculatedWallpaperColor = bm.getPixel(0, 0);
            } else {
                calculatedWallpaperColor = wallpaperColor;
            }
        } catch (Exception e) {
            if (mWallpaper==null) {
                mWallpaper = new ColorDrawable(wallpaperColor);
            }
            if (calculatedWallpaperColor == Color.TRANSPARENT) {
                calculatedWallpaperColor = wallpaperColor;
            }
        }
    }

    public int getCalculatedWallpaperColor() {
        return calculatedWallpaperColor;
    }

    public Drawable getWallpaperDrawable() {
        return mWallpaper;
    }

    private Drawable mWallpaper;


    private final Map<String,Drawable> bgDrawables = new WeakHashMap<>();

    public Drawable getBgDrawableFor(View view, CategoryTabStyle catstyle, boolean isHighContrast) {
        int color = -1;

        switch (catstyle) {
            case Selected:
                color = isHighContrast ? cattabSelectedBackgroundHighContrast : cattabSelectedBackground;
                break;
            case DragHover:
                color = dragoverBackground;
                break;
            case Hidden:
                color = cattabBackground;
                break;
            case Tiny:
            case Normal:
            case Default:
                color = isHighContrast ? cattabBackgroundHighContrast : cattabBackground;
                break;

            case None:
            default:

        }

        return getBgDrawableFor(view, color);
    }

    public Drawable getBgDrawableFor(View view, int color) {

        String key = view.toString() + color;

        Drawable newbg = bgDrawables.get(key);

        if (newbg==null) {

            Drawable base = mContext.getResources().getDrawable(R.drawable.rounded);
            if (base.getConstantState()==null) {
                newbg = base.mutate();  //shouldn't ever get here
                Log.d("Style", "base drawable had a null getConstantState");
            } else {
                newbg = base.getConstantState().newDrawable().mutate();
            }
            bgDrawables.put(key, newbg);
        }

        if (color!=-1) {
            newbg.setColorFilter(color, PorterDuff.Mode.MULTIPLY);
        }

        return newbg;
    }

    public boolean isRoundedTabs() {
        return mAppPreferences.getBoolean(mContext.getString(R.string.pref_key_rounded_tabs), true);
    }

    public int getMaxWCells() {
        int iconsizePref = Integer.parseInt(mAppPreferences.getString(mContext.getString(R.string.pref_key_iconsize), "1"));

        return 6-iconsizePref;

    }

//    public float getWidgetWidth(float minPixWidth) {
//
//        return minPixWidth * getRatio();
//    }

//    private float getRatio() {
//        float iconsize = mContext.getResources().getDimension(R.dimen.icon_width);
//
//        return iconsize / launcherIconSize;
//    }

    private int getResColor(int res) {
        if (Build.VERSION.SDK_INT >= 23) {
            return mContext.getColor(res);
        } else {
            return mContext.getResources().getColor(res);
        }
    }
    public int getCattabTextColor() {
        return cattabTextColor;
    }

    public int getCattabTextColorInvert() {
        return cattabTextColorInvert;
    }

    public int getCattabBackground() {
        return cattabBackground;
    }

    public int getCattabSelectedBackground() {
        return cattabSelectedBackground;
    }

    public int getCattabSelectedText() {
        return cattabSelectedText;
    }

    public int getDragoverBackground() {
        return dragoverBackground;
    }

    public int getTextColor() {
        return textColor;
    }

    public int getBackgroundDefault() {
        return backgroundDefault;
    }

    public boolean isLeftHandCategories() {
        return leftHandCategories;
    }

    public boolean isCenteredIcons() {
        return centeredIcons;
    }

    public float getCategoryTabFontSize() {
        return categoryTabFontSize;
    }

    public int getCategoryTabPaddingHeight() {
        return categoryTabPaddingHeight;
    }

    public int getIconTint() {
        return iconTint;
    }



    public void animateUpShow(View view) {
        animateShow(view,AnimateDirection.Down);
    }

    public void animateDownHide(View view) {
        animateHide(view, AnimateDirection.Down,false, true);
    }

    public enum AnimateDirection {Left, Up, Right, Down}

    private final Map<View,Long> aniHideStarted = new HashMap<>();

    public void animateHide(final View view, final AnimateDirection towards) {
        animateHide(view,towards, false, true);
    }

    public void animateHide(final View view, final AnimateDirection towards, final boolean andBack, final boolean bounce) {

//        Log.d(TAG, "animateHide " + view);
        if (mAnimationDuration==0) {
            view.clearAnimation();

            if (andBack) {
                ensureVisibleNoAni(view);

            } else {
                view.setVisibility(View.GONE);
            }
            return;
        }

        long now = System.currentTimeMillis();
        float fac = andBack?2.5f:1;
        Long then = aniHideStarted.get(view);
        if (then!=null && now - then < mAnimationDuration*fac) return;
        aniHideStarted.put(view,now);

        ViewPropertyAnimator animate = view.animate()
                .setDuration(mAnimationDuration)
                .setInterpolator(new AccelerateInterpolator())
                .alpha(0)
                .scaleY(.6f)
                .scaleX(.6f)

                .setListener(new AnimatorListenerAdapter() {
                    @Override
                    public void onAnimationEnd(Animator animation) {
                        super.onAnimationEnd(animation);

                        if (andBack) {
                            animateShow(view, towards, !bounce);
                        } else {
                            view.setVisibility(View.GONE);
                        }

                    }

                    @Override
                    public void onAnimationCancel(Animator animation) {
                        super.onAnimationCancel(animation);
                        if (andBack) {
                            ensureVisibleNoAni(view);
                        } else {
                            view.setVisibility(View.GONE);
                        }
                    }
                });

        switch(towards) {
            case Down:
                animate.translationY(view.getHeight());
                break;
            case Up:
                animate.translationY(-view.getHeight());
                break;
            case Right:
                animate.translationX(view.getWidth());
                break;
            case Left:
                animate.translationX(-view.getHeight());
                break;
        }
        animate.setStartDelay(0).start();
    }

    private void ensureVisibleNoAni(View view) {
        view.setAlpha(1);
        view.setScaleX(1);
        view.setScaleY(1);
        view.setTranslationX(0);
        view.setTranslationY(0);
        view.setVisibility(View.VISIBLE);
    }

    public void animateShow(final View view, AnimateDirection from) {
        animateShow(view, from,false);
    }


    private void animateShow(final View view, AnimateDirection from, boolean reverse) {

        if (mAnimationDuration==0) {
            view.clearAnimation();
            ensureVisibleNoAni(view);
            //Log.d(TAG, "animateShow " + view);

            return;
        }

        if (reverse) {
            switch(from) {
                case Down:
                    view.setTranslationY(-view.getHeight());
                    break;
                case Up:
                    view.setTranslationY(view.getHeight());
                    break;
                case Right:
                    view.setTranslationX(-view.getWidth());
                    break;
                case Left:
                    view.setTranslationX(view.getWidth());
                    break;
            }
        }

        view.setVisibility(View.VISIBLE);

        ViewPropertyAnimator animate = view.animate()
                .setDuration(mAnimationDuration)
                .setInterpolator(new DecelerateInterpolator())
                .alpha(1)
                .scaleY(1)
                .scaleX(1)
                .setListener(new AnimatorListenerAdapter() {
                    @Override
                    public void onAnimationEnd(Animator animation) {
                        super.onAnimationEnd(animation);
                        ensureVisibleNoAni(view);
                    }
                    @Override
                    public void onAnimationCancel(Animator animation) {
                        super.onAnimationCancel(animation);
                        ensureVisibleNoAni(view);
                    }
                });

        switch(from) {
            case Down:
            case Up:
                animate.translationY(0);
                break;
            case Right:
            case Left:
                animate.translationX(0);
                break;
        }


    }


    public void animateChangingSize(final View view, int startsize, int newsize, final Runnable before, final Runnable after) {
        final ViewGroup.LayoutParams lp = view.getLayoutParams();
        lp.height = startsize;
        view.setLayoutParams(lp);

        ValueAnimator o = ValueAnimator.ofInt(startsize,newsize);
        o.setDuration(mAnimationDuration);
        o.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator valueAnimator) {
                lp.height = (int)valueAnimator.getAnimatedValue();
                view.setLayoutParams(lp);
            }
        });
        o.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationStart(Animator animation) {
                if (before!=null) before.run();
            }

            @Override
            public void onAnimationEnd(Animator animation) {
                lp.height = ViewGroup.LayoutParams.WRAP_CONTENT;
                view.setLayoutParams(lp);
                if (after!=null) after.run();
            }
        });


        o.start();
    }


} public enum AnimateDirection {Left, Up, Right, Down} public class Widget {
    private static final int WIDGET_HOST_ID = 3455;
    final private int REQUEST_PICK_APPWIDGET = 3535;
    final private int REQUEST_CREATE_APPWIDGET = 6756;
    final private int REQUEST_BIND_APPWIDGET = 5645;
    private AppWidgetManager mAppWidgetManager;
    private LaunchAppWidgetHost mAppWidgetHost;
    private final Context mContext;

    private final SharedPreferences mPrefs;


    private final static String TAG = "Widget";
    
    private final Map<ComponentName, AppWidgetHostView> mLoadedWidgets = new HashMap<>();

    public Widget(Context context) {
        mContext = context;
        mPrefs = context.getSharedPreferences("widgets", Context.MODE_PRIVATE);

        setupHost(context);

        //mAppWidgetHost.deleteHost();

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            for (int oid: mAppWidgetHost.getAppWidgetIds()) {
                AppWidgetProviderInfo provider = mAppWidgetManager.getAppWidgetInfo(oid);
                if (provider==null) continue;
                Log.d(TAG, "Widget is allocated: " + provider.provider);
            }
        }

    }

    private void setupHost(Context context) {
        for (int i=0; i<2; i++) {
            try {
                mAppWidgetManager = AppWidgetManager.getInstance(mContext);
                mAppWidgetHost = new LaunchAppWidgetHost(mContext.getApplicationContext(), WIDGET_HOST_ID);
                mAppWidgetHost.startListening();
                break;
            } catch (RuntimeException | Error e) {
                Log.e("LaunchWidget", "Couldn't start appwidgethost", e);
                if (i==1) {
                    Toast.makeText(context, "System error: widgets not available", Toast.LENGTH_LONG).show();
                    break;
                }
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e1) {
                    Log.e(TAG, e1.getMessage());
                }
            }
        }
    }


    public AppWidgetHostView getOrCreateWidget(Activity parent, ComponentName provider) {
        AppWidgetHostView hostView = getLoadedAppWidgetHostView(provider);
        if (hostView == null) {
            int id = getWidgetId(provider);
            if (id!=-1) {
                Log.d(TAG, "loading widget from id " + provider);
                hostView =createWidgetFromId(id);
                if (hostView==null) {
                    removeWidget(provider);
                    return null;
                }
            }

            if (hostView==null) {
                Log.d(TAG, "creating new widget " + provider);
                hostView = loadWidget(parent, provider);
            }

            if (hostView==null) {
                Log.d(TAG, "AppWidgetHostView was null for " + provider);
                // db().deleteApp(app.getActivityName());
                return null;
            }
            saveLoadedWidget(provider, hostView);
        }
        return hostView;
    }

    public AppWidgetHostView getAppWidgetHostView(AppLauncher appitem) {
        return getLoadedAppWidgetHostView(appitem.getComponentName());
    }

    public AppWidgetHostView getLoadedAppWidgetHostView(ComponentName cn) {
        return mLoadedWidgets.get(cn);
    }

    private int getWidgetId(ComponentName cn) {
        return mPrefs.getInt(cn.toShortString(), -1);
    }

    private void saveWidgetId(ComponentName cn, int id) {
        mPrefs.edit().putInt(cn.toShortString(), id).apply();
    }


    public void saveLoadedWidget(ComponentName cn, AppWidgetHostView hostView) {
        saveWidgetId(cn, hostView.getAppWidgetId());
        mLoadedWidgets.put(cn, hostView);
    }


    public void removeWidget(ComponentName cn) {
        int id = getWidgetId(cn);
        mAppWidgetHost.deleteAppWidgetId(id);
        mPrefs.edit().remove(cn.toShortString()).apply();
        AppWidgetHostView widv = mLoadedWidgets.remove(cn);

        for (WidgetChangedListener wl: mWidgetChangedListeners) {
            wl.onWidgetRemoved(cn, widv);
        }

    }


    public void updateWidgetId(int oldId, int newId) {
        AppWidgetProviderInfo provider = mAppWidgetManager.getAppWidgetInfo(newId);
        AppWidgetHostView w = mLoadedWidgets.get(provider.provider);
        if (w!=null) {
            w.setAppWidget(newId, provider);
        }
        saveWidgetId(provider.provider, newId);
        mAppWidgetHost.deleteAppWidgetId(oldId);
    }

    public void done() {
        mWidgetChangedListeners.clear();
        mAppWidgetHost.stopListening();
    }

    public void delete() {
        for (ComponentName cn: new ArrayList<>(mLoadedWidgets.keySet())) {
            Log.d(TAG, "removing widget " + cn);
            removeWidget(cn);
        }
        mLoadedWidgets.clear();
        mAppWidgetHost.stopListening();
        mAppWidgetHost.deleteHost();

        setupHost(mContext);
    }

    public void popupSelectWidget(Activity parent) {
        try {
            // Allocate widget id and start widget selection activity
            int appWidgetId = this.mAppWidgetHost.allocateAppWidgetId();
            Intent pickIntent = new Intent(AppWidgetManager.ACTION_APPWIDGET_PICK);
            pickIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
            addEmptyData(pickIntent); // This is needed work around some weird bug.
            parent.startActivityForResult(pickIntent, REQUEST_PICK_APPWIDGET);
        } catch (Throwable t) {
            Log.e(TAG, t.getMessage(), t);
        }
    }

    private int getAppWidgetIdFromIntent(Intent data) {
        // Get the widget id
        Bundle extras = data.getExtras();
        if (extras==null) return -1;

        return extras.getInt(AppWidgetManager.EXTRA_APPWIDGET_ID, -1);

    }

    private AppWidgetHostView createWidget(Intent data) {

        int appWidgetId = getAppWidgetIdFromIntent(data);

        return createWidgetFromId(appWidgetId);
    }

    private AppWidgetHostView createWidgetFromId(int widget_id) {
        AppWidgetProviderInfo appWidgetInfo = mAppWidgetManager.getAppWidgetInfo(widget_id);

        if (appWidgetInfo==null) {
            return null;
        }
        // if (checkBindPermission(widget_id, appWidgetInfo.provider)) return null;

        AppWidgetHostView hostView = getLoadedAppWidgetHostView(appWidgetInfo.provider);

        if (hostView==null || hostView.getAppWidgetId()!=widget_id) {
            // Create the host view
            hostView = mAppWidgetHost.createView(mContext, widget_id, appWidgetInfo);
            hostView.setAppWidget(widget_id, appWidgetInfo);
        }

        return hostView;
    }


    public AppWidgetHostView loadWidget(Activity parent, AppLauncher app) {
        //ComponentName cn = new ComponentName(app.getPackageName(), app.getActivityName());

        return loadWidget(parent, app.getComponentName());
    }

    private AppWidgetHostView loadWidget(Activity parent, ComponentName cn) {


        //Log.d(TAG, "Loaded from db: " + cn.getClassName() + " - " + cn.getPackageName());
        // Check that there actually is a widget in the database
        if (cn.getPackageName().isEmpty() && cn.getClassName().isEmpty()) {
            Log.d(TAG, "DB was empty");
            return null;
        }

        final List<AppWidgetProviderInfo> infos = mAppWidgetManager.getInstalledProviders();

        // Get AppWidgetProviderInfo
        AppWidgetProviderInfo appWidgetInfo = null;

        // Iterate through all infos, trying to find the desired one
        for (final AppWidgetProviderInfo info : infos) {
            if (info.provider.equals(cn)) {
                // We found it!
                appWidgetInfo = info;
                break;
            }
        }
        if (appWidgetInfo == null) {
            Log.d(TAG, "app info was null");
            return null; // Stop here
        }
        return loadWidget(parent, appWidgetInfo);
    }

    private AppWidgetHostView loadWidget(Activity parent, AppWidgetProviderInfo appWidgetInfo) {

        // Allocate the hosted widget id
        int appWidgetId = mAppWidgetHost.allocateAppWidgetId();

        if (checkBindPermission(parent, appWidgetId, appWidgetInfo)) return null;

        Log.d(TAG, "Allowed to bind");
        Log.d(TAG, "creating widget");


        // Create the host view
        AppWidgetHostView hostView = mAppWidgetHost.createView(mContext, appWidgetId, appWidgetInfo);

        // Set the desired widget
        hostView.setAppWidget(appWidgetId, appWidgetInfo);

        return hostView;
    }

    private boolean checkBindPermission(final Activity parent, final int appWidgetId, final AppWidgetProviderInfo appWidgetInfo) {
        try {
            boolean allowed_to_bind = mAppWidgetManager.bindAppWidgetIdIfAllowed(appWidgetId, appWidgetInfo.provider);


            // Ask the user to allow this app to have access to their widgets
            if (!allowed_to_bind) {
                new Handler().postDelayed(new Runnable() {
                    @Override
                    public void run() {
                        Log.d(TAG, "asking for permission");
                        Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_BIND);
                        intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
                        intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_PROVIDER, appWidgetInfo.provider);

                        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
                            intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_PROVIDER_PROFILE, appWidgetInfo.getProfile());
                        }

                        addEmptyData(intent);
                        parent.startActivityForResult(intent, REQUEST_BIND_APPWIDGET);

                    }
                }, 500);
                return true;
            }
        } catch( Exception e) {
            Log.e(TAG, e.getMessage(), e);
            return false;
        }
        return false;
    }

    private AppWidgetHostView configureWidget(Activity parent, Intent data) {
        // Get the selected widget information
        try {
            int appWidgetId = getAppWidgetIdFromIntent(data);
            return configureWidget(parent, appWidgetId);
        } catch (Exception | Error e) {
            Log.e(TAG, e.getMessage(), e);
        }
        return null;
    }

    public AppWidgetHostView configureWidget(Activity parent, ComponentName provider){
        return configureWidget(parent,getWidgetId(provider));
    }

    public AppWidgetHostView configureWidget(Activity parent, int appWidgetId){
        AppWidgetProviderInfo appWidgetInfo = mAppWidgetManager.getAppWidgetInfo(appWidgetId);
        if (appWidgetInfo!=null) {
            if (appWidgetInfo.configure != null) {
                // If the widget wants to be configured then start its configuration activity
                Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_CONFIGURE);
                intent.setComponent(appWidgetInfo.configure);
                intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
                parent.startActivityForResult(intent, REQUEST_CREATE_APPWIDGET);
            } else {
                // Otherwise simply create it
                return createWidgetFromId(appWidgetId);
            }
        }
        return null;
    }

    public ComponentName getConfigure(ComponentName provider) {
        return getConfigure(getWidgetId(provider));
    }


    public ComponentName getConfigure(int appWidgetId) {
        AppWidgetProviderInfo appWidgetInfo = mAppWidgetManager.getAppWidgetInfo(appWidgetId);
        if (appWidgetInfo==null) return null;
        return appWidgetInfo.configure;
    }

    private void addEmptyData(Intent pickIntent) {
        // This is needed work around some weird bug.
        // This will simply add some empty data to the intent.
        ArrayList<Parcelable> customInfo = new ArrayList<>();
        pickIntent.putParcelableArrayListExtra(AppWidgetManager.EXTRA_CUSTOM_INFO, customInfo);
        ArrayList<Parcelable> customExtras = new ArrayList<>();
        pickIntent.putParcelableArrayListExtra(AppWidgetManager.EXTRA_CUSTOM_EXTRAS, customExtras);
    }


    public ComponentName getComponentNameFromIntent(Intent data) {
        if (data!=null) {
            int appWidgetId = getAppWidgetIdFromIntent(data);
            if (appWidgetId != -1) {

                AppWidgetProviderInfo appWidgetInfo = mAppWidgetManager.getAppWidgetInfo(appWidgetId);
                if (appWidgetInfo != null) {
                    return appWidgetInfo.provider;
                }
            }

        }
        return null;
    }


    public AppWidgetHostView onActivityResult(Activity parent, int requestCode, int resultCode, Intent data) {

        Log.d(TAG, "onActivityResult: requestCode=" + requestCode + " resultCode=" + resultCode);
        // listen for widget manager response
        if (resultCode == Activity.RESULT_OK) {
            if (requestCode == REQUEST_PICK_APPWIDGET) {
                Log.d(TAG, "configureWidget");
                return configureWidget(parent, data);
            } else if (requestCode == REQUEST_CREATE_APPWIDGET || requestCode == REQUEST_BIND_APPWIDGET) {
                Log.d(TAG, "createWidget");
                return createWidget(data);
            } else {
                Log.d(TAG, "unknown RESULT_OK");
            }
        } else if (resultCode == Activity.RESULT_CANCELED) {
            Log.d(TAG, "RESULT_CANCELED");
            if (data!=null) {
                int appWidgetId = getAppWidgetIdFromIntent(data);
                AppWidgetProviderInfo appWidgetInfo = mAppWidgetManager.getAppWidgetInfo(appWidgetId);
                if (appWidgetId != -1) {
                    if (appWidgetInfo==null || getWidgetId(appWidgetInfo.provider)==-1) {
                        mAppWidgetHost.deleteAppWidgetId(appWidgetId);
                    }
                }
            }

        }
        return null;
    }


    public List<AppWidgetProviderInfo> getWidgetsForPackage(String packageName) {
        List<AppWidgetProviderInfo> provs;
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            UserHandle user = UserHandle.getUserHandleForUid(Process.myUid());
            provs =  mAppWidgetManager.getInstalledProvidersForPackage(packageName, user);

        } else {
            provs = new ArrayList<>();
            for (AppWidgetProviderInfo prov: mAppWidgetManager.getInstalledProviders()) {
                if (prov.provider.getPackageName().equals(packageName)) {
                    provs.add(prov);
                }
            }
        }
        return provs;
    }


    private List<WidgetChangedListener> mWidgetChangedListeners = new ArrayList<>();

    public void addWidgetChangedListener(WidgetChangedListener listener) {
        if(!mWidgetChangedListeners.contains(listener)) {
            mWidgetChangedListeners.add(listener);
        }
    }


    public List<WidgetChangedListener> getWidgetChangedListeners() {
        return Collections.unmodifiableList(mWidgetChangedListeners);
    }

    public void removeWidgetChangedListener(WidgetChangedListener listener) {
        mWidgetChangedListeners.remove(listener);
    }


    public interface WidgetChangedListener {
        void onWidgetRemoved(ComponentName provider, AppWidgetHostView view);
    }

}
import android.annotation.SuppressLint;
import android.app.ActionBar;
import android.app.Activity;
import android.app.AlertDialog;
import android.appwidget.AppWidgetHostView;
import android.appwidget.AppWidgetProviderInfo;
import android.content.ActivityNotFoundException;
import android.content.ClipData;
import android.content.ComponentName;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.res.Configuration;
import android.graphics.Color;
import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.Typeface;
import android.graphics.drawable.ColorDrawable;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.preference.PreferenceManager;
import android.support.annotation.NonNull;
import android.util.DisplayMetrics;
import android.util.Log;
import android.util.TypedValue;
import android.view.Display;
import android.view.DragEvent;
import android.view.Gravity;
import android.view.InputDevice;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.Surface;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.view.ViewParent;
import android.view.WindowManager;
import android.view.animation.AccelerateDecelerateInterpolator;
import android.view.animation.Animation;
import android.view.animation.ScaleAnimation;
import android.view.inputmethod.InputMethodManager;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.EditText;
import android.widget.FrameLayout;
import android.widget.GridLayout;
import android.widget.HorizontalScrollView;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.PopupWindow;
import android.widget.ProgressBar;
import android.widget.ScrollView;
import android.widget.SeekBar;
import android.widget.Spinner;
import android.widget.TextView;
import android.widget.Toast;
import com.quaap.launchtime.apps.AppLauncher;
import com.quaap.launchtime.apps.Badger;
import com.quaap.launchtime.apps.DefaultApps;
import com.quaap.launchtime.apps.LaunchApp;
import com.quaap.launchtime.components.Theme;
import com.quaap.launchtime.ui.InteractiveScrollView;
import com.quaap.launchtime.ui.ActionMenu;
import com.quaap.launchtime.components.Categories;
import com.quaap.launchtime.components.ExceptionHandler;
import com.quaap.launchtime.components.IconsHandler;
import com.quaap.launchtime.ui.MsgBox;
import com.quaap.launchtime.db.DB;
import com.quaap.launchtime.ui.QuickRow;
import com.quaap.launchtime.ui.SearchBox;
import com.quaap.launchtime.ui.Style;
import com.quaap.launchtime.ui.Style.AnimateDirection;
import com.quaap.launchtime.widgets.Widget;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import static android.view.Gravity.LEFT;
                    scrollToCategoryTab();
                    showButtonBar(false, true);

                }
            }, 100);
        }

        //rerun our query if needed
        if (mCategory.equals(Categories.CAT_SEARCH)) {
            mSearchBox.refreshSearch(false);
        }

        hideRemoveDropzone();

        hideCatsIfAutoHide(true);
        showButtonBar(false, true);
        //lock things up if it was in toddler mode
        checkChildLock();

//        PopupWindow pw = new PopupWindow(this);
//        int color = Color.parseColor("#CCCCCCCC");
//        ColorChooser cc = new ColorChooser(this, color);
//        cc.setColor(color);
//        pw.setContentView(cc);
//        pw.showAsDropDown(mIconSheetScroller);

    }



    private long mPauseTime = 0;

    @SuppressLint("ApplySharedPref")
    @Override
    protected void onPause() {
        Log.d(TAG, "onPause");
        mBackPressedSessionCount=0;
        prefsUpdate = 0;
        checkChildLock();

//        try {
//            if (mStartupTask!=null && !mStartupTask.isCancelled()) {
//                mStartupTask.cancel(true);
//                //mStartupTask = null;
//            }
//        } catch (Throwable t) {
//            Log.e(TAG, t.getMessage(), t);
//        }

        //save a few items
        mPrefs.edit()
                .putInt("scrollpos" + mCategory, mIconSheetScroller.getScrollY())
                .putString("category", mCategory)
                .putLong("pausetime", System.currentTimeMillis())
                .commit();

        //close our search cursor, if needed
        mSearchBox.closeSeachAdapter();
        mPauseTime = System.currentTimeMillis();



        super.onPause();
    }

    private static class StartupTask extends AsyncTask<Void,Integer,List<AppLauncher>> {

        private final WeakReference<MainActivity> mMain;
        private final boolean mShowProgress;

        final private int mStartAtStage;
        int mCompletedStage = 0;
        final int mStages = 6;

        StartupTask(MainActivity main, boolean showProgress, int startAtStage) {
            mMain = new WeakReference<>(main);
            mShowProgress = showProgress;
            main.mInitCalling = true;
            mStartAtStage = startAtStage;

        }

        boolean isCompleted() {
            return mCompletedStage == mStages;
        }

        @Override
        protected List<AppLauncher> doInBackground(Void... voids) {
            final MainActivity main = mMain.get();
            if (main == null) return null;
            try {

                if (main.mInitCalled) return null;

                if (mShowProgress) {
                    main.showProgressBar(100);
                    main.incProgressBar(1);
                    Thread.yield();
                }

                if (mStartAtStage<1) {
                    stage1(main);
                    mCompletedStage = 1;
                }

                if (mStartAtStage<2) {
                    stage2(main);
                    mCompletedStage = 2;
                }

                return main.processActivities(mShowProgress);
            } catch (Throwable t) {
                Log.e(TAG, t.getMessage(), t);
            } finally {
                main.mInitCalled = true;
            }
            return null;
        }

        private void stage1(MainActivity main) {
            DB db = GlobState.getGlobState(main).getDB();