app/src/main/java/ru/henridellal/emerald/listener/OnAppUnlockLongClickListener.java
public class OnAppUnlockLongClickListener implements View.OnLongClickListener {
	private SoftReference<Context> contextRef;
	public OnAppUnlockLongClickListener(Context context) {
		contextRef = new SoftReference<Context>(context);
	}
	
	@Override
	public boolean onLongClick(final View arg0) {
		AlertDialog.Builder builder = new AlertDialog.Builder(contextRef.get());
		builder.setTitle(contextRef.get().getResources().getString(R.string.type_password));
		final EditText inputBox = new EditText(contextRef.get());
		inputBox.setInputType(InputType.TYPE_CLASS_TEXT|InputType.TYPE_TEXT_VARIATION_PASSWORD);
		builder.setView(inputBox);
		builder.setPositiveButton(android.R.string.yes, 
			new DialogInterface.OnClickListener() {
				@Override
				public void onClick(DialogInterface dialog, int which) {
					if (inputBox.getText().toString().equals(((Apps)contextRef.get()).options.getString(Keys.PASSWORD, ""))) {
						BaseData tag = (BaseData) arg0.getTag();
						if (tag instanceof AppData) {
							((Apps)contextRef.get()).itemContextMenu((AppData)tag);
						} else if (tag instanceof ShortcutData) {
							((Apps)contextRef.get()).itemContextMenu((ShortcutData)tag);
						}
					} else {
						Toast.makeText(contextRef.get(), contextRef.get().getResources().getString(R.string.wrong_password), Toast.LENGTH_LONG).show();
					}
				}
			});
		builder.setNegativeButton(android.R.string.cancel,
			new DialogInterface.OnClickListener() {
				@Override
				public void onClick(DialogInterface dialog, int which) {
					
				}
			});

		builder.setCancelable(true);
		builder.show();
		return false;
	}
}
app/src/main/java/ru/henridellal/emerald/data/AppData.java
public class AppData extends BaseData {
	
	//Constants for parsing
	public static final String COMPONENT = "C";
	public static final String NAME = "N";

	@Override
	public boolean equals(Object a) {
		if (! (a instanceof AppData))
			return false;
		if (component == null) {
			return a == null || ((AppData)a).component == null;
		}
		return component.equals( ((AppData)a).component );
	}

	public AppData() {
		super();
	}

	public AppData(Cursor cursor) {
		super();
		component = cursor.getString(0);
		name = cursor.getString(1);
	}

	public AppData(String component, String name) {
		super(component, name);
	}

	public void read(BufferedReader reader, String firstLineOfData) {
		try {
			this.component = firstLineOfData.substring(1).trim();
			this.name = readLine(reader, NAME).substring(1).trim();
		} catch (IOException ignored) {
		
		}
	}

	//writes app data in given file writer
	public void write(BufferedWriter writer) throws IOException {
		writer.write(new StringBuilder(COMPONENT)
			.append(this.component)
			.append("\n")
			.append(NAME)
			.append(this.name)
			.append("\n").toString());
	}

	public Intent getLaunchIntent(Context context) {
		//Log.v(APP_TAG, "User launched an app");
		if (!DatabaseHelper.hasItem(context, this, CategoryManager.HIDDEN))
			DatabaseHelper.addToHistory(context, this);
		Intent i = new Intent(Intent.ACTION_MAIN);
		i.addCategory(Intent.CATEGORY_LAUNCHER);
		i.setComponent(ComponentName.unflattenFromString(getComponent()));
		i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);

		return i;
	}
}
app/src/main/java/ru/henridellal/emerald/preference/Keys.java
public final class Keys {
	public static final String SHOW_TUTORIAL = "show_tutorial";
	public static final String MESSAGE_SHOWN = "message_shown";
	public static final String KEEP_IN_MEMORY = "keep_in_memory";
	public static final String VOLUME_BUTTONS = "volume_buttons";
	public static final String FULLSCREEN = "fullscreen";
	
	public static final String BAR_BACKGROUND = "bar_background";
	public static final String DOCK_BACKGROUND = "dock_background";
	public static final String NAV_BAR_BACKGROUND = "nav_bar_background";
	public static final String STATUS_BAR_BACKGROUND = "status_bar_background";
	public static final String APPS_WINDOW_BACKGROUND = "apps_background";
	
	public static final String ICON_PACK = "icon_pack";
	public static final String ICON_PACK_CHANGED = "icon_pack_changed";
	public static final String TRANSFORM_DRAWABLE = "transform_drawable";
	public static final String APP_SHORTCUT = "app_shortcut";
	public static final String PREV_APP_SHORTCUT = "prevApp_shortcut";
	public static final String SHOW_KEYBOARD_ON_START = "show_keyboard";
	public static final String TILE = "tile";
	public static final String HIDE_MAIN_BAR = "hide_main_bar";
	public static final String BOTTOM_MAIN_BAR = "bottom_main_bar";
	public static final String STACK_FROM_BOTTOM = "stack_from_bottom";
	public static final String SCROLLBAR = "scrollbar";
	public static final String INVERT_FONT_COLOR = "invert_font_color";
	
	public static final String ICON_SIZE = "icon_size";
	public static final String TEXT_SIZE = "text_size";
	public static final String FONT_STYLE = "font_style";
	public static final String COLUMN_WIDTH = "column_width";
	public static final String VERTICAL_SPACING = "vertical_spacing";
	
	public static final String ICON_SIZE_LANDSCAPE = "icon_size_land";
	public static final String TEXT_SIZE_LANDSCAPE = "text_size_land";
	public static final String COLUMN_WIDTH_LANDSCAPE = "column_width_land";
	public static final String VERTICAL_SPACING_LANDSCAPE = "vertical_spacing_land";
	
	public static final String DOCK_IN_LANDSCAPE = "show_dock_in_landscape";
	
	public static final String HOME = "home";
	public static final String HOME_BUTTON = "home_button";
	public static final String CATEGORY = "category";
	public static final String ORIENTATION = "orientation";
	public static final String SEARCH_PROVIDER = "search_provider";
	public static final String THEME = "theme";
	public static final String HISTORY_SIZE = "history_size";
	public static final String HIDE_HISTORY = "hide_history";
	public static final String HIDE_UNCLASSIFIED = "hide_unclassified";
	public static final String HIDE_ALL = "hide_all";
	public static final String PASSWORD = "password";
	
	public static final String[] BACKUP = {SHOW_TUTORIAL,
		KEEP_IN_MEMORY, FULLSCREEN, BAR_BACKGROUND, DOCK_BACKGROUND,
		NAV_BAR_BACKGROUND, STATUS_BAR_BACKGROUND,
		APPS_WINDOW_BACKGROUND, ICON_PACK, TRANSFORM_DRAWABLE,
		APP_SHORTCUT, PREV_APP_SHORTCUT,
		SHOW_KEYBOARD_ON_START, TILE, BOTTOM_MAIN_BAR,
		HIDE_MAIN_BAR, STACK_FROM_BOTTOM, SCROLLBAR, INVERT_FONT_COLOR,
		ICON_SIZE, TEXT_SIZE, FONT_STYLE, COLUMN_WIDTH,
		VERTICAL_SPACING, ICON_SIZE_LANDSCAPE, 
		TEXT_SIZE_LANDSCAPE, COLUMN_WIDTH_LANDSCAPE,
		VERTICAL_SPACING_LANDSCAPE, DOCK_IN_LANDSCAPE,
		HOME, HOME_BUTTON, ORIENTATION, SEARCH_PROVIDER,
		THEME, HISTORY_SIZE
	};
	
	public static final String[] restart = {MESSAGE_SHOWN, FULLSCREEN, BAR_BACKGROUND,
		NAV_BAR_BACKGROUND, STATUS_BAR_BACKGROUND, DOCK_BACKGROUND,
		APPS_WINDOW_BACKGROUND, SHOW_KEYBOARD_ON_START, TILE, BOTTOM_MAIN_BAR, HIDE_MAIN_BAR,
		STACK_FROM_BOTTOM, FONT_STYLE, SCROLLBAR, INVERT_FONT_COLOR, THEME, ORIENTATION,
		APP_SHORTCUT, KEEP_IN_MEMORY, PASSWORD, ICON_PACK_CHANGED};

}
app/src/main/java/ru/henridellal/emerald/data/ShortcutData.java
public class ShortcutData extends BaseData {
	
	public static String SHORTCUT_NAME = "S";
	public static String SHORTCUT_URI = "U";
	public static String SHORTCUT_COMPONENT = "P";
	
	
	private String uri, packageName, iconResource;

	@Override
	public String getId() {
		return uri;
	}

	public String getUri() {
		return uri;
	}

	public ShortcutData() {
		super();
	}

	public ShortcutData(String name, String uri) {
		super(null, name);
		this.uri = uri;
	}
	
	@Override
	public boolean equals(Object shortcut) {
		if (! (shortcut instanceof ShortcutData))
			return false;
		if (uri == null) {
			return shortcut == null || ((ShortcutData)shortcut).getUri() == null;
		}
		return uri.equals( ((ShortcutData)shortcut).getUri() );
	}
	
	public ShortcutData(Cursor cursor) {
		super();
		name = cursor.getString(Database.FIELD_SHORTCUT_NAME);
		uri = cursor.getString(Database.FIELD_SHORTCUT_URI);
		packageName = cursor.getString(Database.FIELD_SHORTCUT_PACKAGE);
		iconResource = cursor.getString(Database.FIELD_SHORTCUT_RESOURCE);
	}
	
	public void read(BufferedReader reader, String firstLineOfData){
		try {
			this.name = firstLineOfData.substring(1).trim();
			this.uri = readLine(reader, SHORTCUT_URI).substring(1).trim();
		} catch (IOException e) {
		
		}
	}
	
	public void write(BufferedWriter writer) throws IOException {
		writer.write(new StringBuilder(SHORTCUT_NAME)
		.append(this.name)
		.append("\n")
		.append(SHORTCUT_URI)
		.append(this.uri)
		.append("\n").toString());
	}

	@Override
	public Intent getLaunchIntent(Context context) {
		if (!DatabaseHelper.hasItem(context, this, CategoryManager.HIDDEN))
			DatabaseHelper.addToHistory(context, this);
		try {
			return Intent.parseUri(getUri(), 0);
		} catch (Exception e) {
			Toast.makeText(context, e.toString(), Toast.LENGTH_LONG).show();
			return null;
		}
	}

	@Override
	public int hashCode() {
		return getUri().hashCode();
	}
}
app/src/main/java/ru/henridellal/emerald/listener/OnAppClickListener.java
public class OnAppClickListener implements View.OnClickListener {
	private SoftReference<Apps> appsRef;
	public OnAppClickListener(Apps apps) {
		appsRef = new SoftReference<Apps>(apps);
	}
	
	@Override
	public void onClick(View arg0) {
		if (arg0.getTag() instanceof BaseData) {
			appsRef.get().launch((BaseData)arg0.getTag());
		}
	}
}
app/src/main/java/ru/henridellal/emerald/data/BaseData.java
public abstract class BaseData implements Comparable<BaseData> {
	//component is a package name
	protected String component;
	
	//name is app name
	protected String name;

	public String getId() {
		return component;
	}
	public static final Comparator<BaseData> NameComparator = 
		new Comparator<BaseData>() {

		public int compare(BaseData a, BaseData b) {
			return a.name.compareToIgnoreCase(b.name);
		}
	};
	
	@Override
	public boolean equals(Object a) {
		if (! (a instanceof BaseData))
			return false;
		if (component == null) {
			return a == null || ((BaseData)a).component == null;
		}
		return component.equals( ((BaseData)a).component );
	}
	
	public BaseData() {}
	public BaseData(String component, String name) {
		this.component = component;
		this.name = name;
	}

	public String getComponent() {
		return this.component;
	}

	public String getName() {
		return this.name;
	}

	public void read(BufferedReader reader, String firstLineOfData) {}

	public void write(BufferedWriter reader) throws IOException {
		throw new IOException();
	}

	protected String readLine(BufferedReader reader, String key) throws IOException {
		String line = reader.readLine();
		if (line == null || !line.startsWith(key))
			throw new IOException();
		return line;
	}
	
	@Override
	public int compareTo(BaseData arg0) {
		return arg0.name.compareToIgnoreCase(this.name);
	}

	abstract public Intent getLaunchIntent(Context context);
	
	@Override
	public int hashCode() {
		if (component == null)
			return "NULL null NULL".hashCode();
		else
			return (component).hashCode();
	}
}
app/src/main/java/ru/henridellal/emerald/util/IconPackManager.java
public class IconPackManager {
	private Context context;
	private String iconPackName;
	private float factor = 1.0f;
	private ArrayList<Bitmap> iconBacks;
	private Bitmap iconMask, iconUpon;
	private boolean transformDrawable = true;
	private Map<String, String> iconsData;
	private Resources iconPackRes;

	public Resources getResources() {
		return iconPackRes;
	}

	public IconPackManager(Context context, String iconPack) {
		this.context = context;
		setIconPack(iconPack);
	}

	public Map<String, String> getIcons() {
		return iconsData;
	}

	public void setIconPack(String iconPackName) {
		if (getIconPacks().containsValue(iconPackName) || "default".equals(iconPackName)) {
			this.iconPackName = iconPackName;
		} else {
			this.iconPackName = "default";
			Editor editor = PreferenceManager.getDefaultSharedPreferences(context).edit();
			editor.putString(Keys.ICON_PACK, this.iconPackName).commit();
		}
		setIcons();
	}

	private Bitmap loadBitmap(String drawableName) {
		int id = iconPackRes.getIdentifier(drawableName, "drawable", iconPackName);
		if (id > 0)
			return ((BitmapDrawable)iconPackRes.getDrawable(id)).getBitmap();
		else
			return null;
	}

	/* Returns Bitmap for default icon pack.
	 * Includes code for retrieving Oreo+ adaptive icons.
	 */
	public Bitmap getDefaultBitmap(Drawable d) {
		if (d instanceof BitmapDrawable) {
			return ((BitmapDrawable) d).getBitmap();
		} else if ((Build.VERSION.SDK_INT >= 26)
			&& (d instanceof AdaptiveIconDrawable)) {
				AdaptiveIconDrawable icon = ((AdaptiveIconDrawable)d);
				int w = icon.getIntrinsicWidth();
				int h = icon.getIntrinsicHeight();
				Bitmap result = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
				Canvas canvas = new Canvas(result);
				icon.setBounds(0, 0, w, h);
				icon.draw(canvas);
				return result;
		}
		float density = context.getResources().getDisplayMetrics().density;
		int defaultWidth = (int)(48* density);
		int defaultHeight = (int)(48* density);
		return Bitmap.createBitmap(defaultWidth, defaultHeight, Bitmap.Config.ARGB_8888);
	}

	/* transforms an Drawable object to Bitmap
	 * and adds effects from icon pack
	 */
	public Bitmap transformDrawable(Drawable d) {
		Bitmap b = getDefaultBitmap(d);
		if ((iconBacks == null && iconMask == null && iconUpon == null && factor == 1.f) || !transformDrawable)
		{
			return b;
		}
		int w, h;
		if (null != iconBacks && iconBacks.size() > 0) {
			w = iconBacks.get(0).getWidth();
			h = iconBacks.get(0).getHeight();
		} else {
			w = b.getWidth();
			h = b.getHeight();
		}
		Bitmap result = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
		Canvas canvas = new Canvas(result);
		if (null != iconBacks && iconBacks.size() > 0) {
			canvas.drawBitmap(iconBacks.get((int)(Math.random()*iconBacks.size()-1)), 0, 0, null);
		}
		Paint p = new Paint(Paint.ANTI_ALIAS_FLAG);
		Bitmap scaledBitmap = Bitmap.createScaledBitmap(b, (int)(w*factor), (int)(h*factor), false);
		canvas.drawBitmap(scaledBitmap, w*(1-factor)/2, h*(1-factor)/2, p);
		if (iconMask != null) {
			p.setXfermode(new PorterDuffXfermode(Mode.DST_OUT));
			canvas.drawBitmap(iconMask, 0.f, 0.f, p);
			p.setXfermode(null);
		}
		if (iconUpon != null) {
			canvas.drawBitmap(iconUpon, 0, 0, null);
		}
		return result;
	}

	public Bitmap getBitmap(String component) {
		if (iconsData.containsKey(component)) {
			String drawableName = iconsData.get(component);
			return loadBitmap(drawableName);
		} else {
			return null;
		}
	}

	/* used by Options class to list all icon packs available*/
	public Map<String, String> getIconPacks() {
		Map<String, String> iconPacks = new HashMap<String, String>();
		PackageManager pm = context.getPackageManager();
		List<ResolveInfo> iconPacksInfo = pm.queryIntentActivities(new Intent("org.adw.launcher.THEMES"), PackageManager.GET_META_DATA);
		String iconPackPackage = null;
		String iconPackName = null;
		for (ResolveInfo info: iconPacksInfo) {
			iconPackPackage = info.activityInfo.packageName;
			try {
				ApplicationInfo ai = pm.getApplicationInfo(iconPackPackage, PackageManager.GET_META_DATA);
				iconPackName = pm.getApplicationLabel(ai).toString();
			} catch (PackageManager.NameNotFoundException e) {
				Log.e(Apps.APP_TAG, e.toString());
			}
			iconPacks.put(iconPackName, iconPackPackage);
		}
		return iconPacks;
	}

	//sets icon from cache in ImageView
	public static void setIcon(Context c, ImageView img, BaseData a) {
		File iconFile = Cache.getCustomIconFile(c, a);
		if (!iconFile.exists()) {
			if (a instanceof AppData) {
				iconFile = Cache.getIconFile(c, a);
			} else {
				iconFile = Cache.getShortcutIconFile(c, ((ShortcutData)a).getUri());
			}
		}
		if (iconFile.exists()){
			try {
				img.setImageDrawable(Drawable.createFromStream(
					new FileInputStream(iconFile), null));
			} catch (Exception e) {
				img.setImageResource(android.R.drawable.sym_def_app_icon);
			}
		} else {
			img.setImageResource(android.R.drawable.sym_def_app_icon);
		}
	}

	/* sets icons data */
	public void setIcons() {
		iconsData = new HashMap<String, String>();
		iconBacks = null;
		iconMask = null;
		iconUpon = null;
		factor = 1.0f;
		iconPackRes = null;
		if ("default".equals(iconPackName)) {
			return;
		}
		transformDrawable = PreferenceManager.getDefaultSharedPreferences(context).getBoolean(Keys.TRANSFORM_DRAWABLE, true);
		String component = null;
		String drawable = null;
		PackageManager pm = context.getPackageManager();
		iconBacks = new ArrayList<Bitmap>();
		try {
			iconPackRes = pm.getResourcesForApplication(iconPackName);
		} catch (PackageManager.NameNotFoundException nameNotFound) {
			Log.e(Apps.APP_TAG, nameNotFound.toString());
		}
		try {
			int id = iconPackRes.getIdentifier("appfilter", "xml", iconPackName);
			XmlPullParser parser = iconPackRes.getXml(id);
			int parserEvent = parser.getEventType();
			
			while (parserEvent != XmlPullParser.END_DOCUMENT) {
				if (parserEvent == XmlPullParser.START_TAG) {
					final String parserName = parser.getName();
					if ("item".equals(parserName)) {
						for (int i = 0; i < parser.getAttributeCount(); i++) {
							if ("component".equals(parser.getAttributeName(i))) {
								component = parser.getAttributeValue(i);
								int c = component.indexOf("{");
								component = component.substring(c + 1, component.length() - 1);
							} else if ("drawable".equals(parser.getAttributeName(i))) {
								drawable = parser.getAttributeValue(i);
							}
						}
						iconsData.put(component, drawable);
					} else if ("iconback".equals(parserName)) {
						for (int i = 0; i < parser.getAttributeCount(); i++) {
							iconBacks.add(loadBitmap(parser.getAttributeValue(i)));
						}
					} else if ("iconmask".equals(parserName) && parser.getAttributeCount() > 0 && "iconmask".equals(parser.getAttributeName(0))) {
						iconMask = loadBitmap(parser.getAttributeValue(0));
					} else if ("iconupon".equals(parserName) && parser.getAttributeCount() > 0 && "iconupon".equals(parser.getAttributeName(0))) {
						iconUpon = loadBitmap(parser.getAttributeValue(0));
					} else if ("scale".equals(parserName) && "factor".equals(parser.getAttributeName(0))) {
						factor = Float.valueOf(parser.getAttributeValue(0));
					}
				}
				parserEvent = parser.next();
			}
		} catch (Exception e) {
			Log.e(Apps.APP_TAG, e.toString());
		}
	}
}
app/src/main/java/ru/henridellal/emerald/data/DatabaseHelper.java
public class DatabaseHelper {
	private static SQLiteDatabase database;
	public static Database dbOpenHelper;
	private static int mCounter;
	private static int[] defCategoriesRes = new int[] {
		R.string.category_all,
		R.string.category_unclassified,
		R.string.category_history,
		R.string.category_hidden
	};
	private static String[] defCategories = new String[] {
		CategoryManager.ALL,
		CategoryManager.UNCLASSIFIED,
		CategoryManager.HISTORY,
		CategoryManager.HIDDEN
	};

	public static synchronized SQLiteDatabase getDatabase(Context context) {
		mCounter++;
		if (mCounter == 1) {
			dbOpenHelper = new Database(context);
			database = dbOpenHelper.getWritableDatabase();
		}
		return database;
	}
	
	public static synchronized void close() {
		mCounter--;
		if (mCounter == 0)
		database.close();
	}
	
	public static Map<String, Category> getCategories(Context context) {
		SQLiteDatabase db = getDatabase(context);
		Map<String, Category> categories = new HashMap<String, Category>();
		Cursor cursor = db.rawQuery("SELECT * FROM categories", null);
		cursor.moveToFirst();
		while (!cursor.isAfterLast()) {
			String name = cursor.getString(0);
			categories.put(name, new Category(name, new ArrayList<BaseData>(), 0));
			cursor.moveToNext();
		}
		cursor.close();
		close();
		for (int i = 0; i < 4; i++) {
			categories.put(defCategories[i], new Category(defCategories[i], new ArrayList<BaseData>(), defCategoriesRes[i]));
		}
		return categories;
	}
	
	public static boolean hasCategory(SQLiteDatabase db, String categoryName) {
		String query = String.format("SELECT * FROM categories WHERE name = '%s'", categoryName);
		Cursor cursor = db.rawQuery(query, null);
		boolean categoryExists = ((cursor.getCount() != 0) || !CategoryManager.isCustom(categoryName));
		cursor.close();
		return categoryExists;
	}
	
	public static boolean addCategory(Context context, String categoryName) {
		SQLiteDatabase db = getDatabase(context);
		
		if (hasCategory(db, categoryName)) {
			close();
			return false;
		}
		ContentValues values = new ContentValues();
		values.put("name", categoryName);
		db.insert("categories", null, values);
		close();
		return true;
	}

	public static void addToCategory(Context context, BaseData data, String categoryName) {
		SQLiteDatabase db = getDatabase(context);
		String table, column;
		if (data instanceof AppData) {
			table = "apps";
			column = "component";
		} else if (data instanceof ShortcutData) {
			table = "shortcuts";
			column = "uri";
		} else {
			close();
			return;
		}

		String query = String.format("SELECT %s, categories FROM %s WHERE %s = '%s'", column, table, column, data.getId());
		Cursor cursor = db.rawQuery(query, null);
		if (!cursor.moveToFirst()) {
			cursor.close();
			close();
			return;
		}
		String categoriesList = cursor.getString(1);
		ContentValues values = new ContentValues();
		values.put("categories", categoriesList.concat("@" + categoryName + "@"));
		db.update(table, values, column + " = ?", new String[]{cursor.getString(0)});
		cursor.close();
		close();
	}
	
	public static void removeAppFromCategory(Context context, String id, String categoryName) {
		String query = String.format("SELECT component, categories FROM apps WHERE component = '%s'", id);
		SQLiteDatabase db = getDatabase(context);
		Cursor cursor = db.rawQuery(query, null);
		if (!cursor.moveToFirst()) {
			cursor.close();
			close();
			return;
		}
		String categoryNameSQL = "@" + categoryName + "@";
		String oldCategoriesList = cursor.getString(1);
		int startIndex = oldCategoriesList.indexOf(categoryNameSQL);
		String newValue = new StringBuilder(oldCategoriesList).delete(startIndex, startIndex+categoryNameSQL.length()).toString();
		ContentValues values = new ContentValues();
		values.put("categories", newValue);
		db.update("apps", values, "component = ?", new String[]{cursor.getString(0)});
		cursor.close();
		close();
	}
	
	public static void removeShortcutFromCategory(Context context, String id, String categoryName) {
		String query = String.format("SELECT uri, categories FROM shortcuts WHERE uri = '%s'", id);
		SQLiteDatabase db = getDatabase(context);
		Cursor cursor = db.rawQuery(query, null);
		if (!cursor.moveToFirst()) {
			cursor.close();
			close();
			return;
		}
		String categoryNameSQL = "@" + categoryName + "@";
		String oldCategoriesList = cursor.getString(1);
		int startIndex = oldCategoriesList.indexOf(categoryNameSQL);
		String newValue = new StringBuilder(oldCategoriesList).delete(startIndex, startIndex+categoryNameSQL.length()).toString();
		ContentValues values = new ContentValues();
		values.put("categories", newValue);
		db.update("shortcuts", values, "uri = ?", new String[]{cursor.getString(0)});
		cursor.close();
		close();
	}
	
	public static void removeFromCategory(Context context, BaseData data, String categoryName) {
		String table, column;
		if (data instanceof AppData) {
			table = "apps";
			column = "component";
		} else if (data instanceof ShortcutData) {
			table = "shortcuts";
			column = "uri";
		} else {
			return;
		}

		SQLiteDatabase db = getDatabase(context);
		String categoryNameSQL = "@" + categoryName + "@";
		String query = String.format("SELECT %s, categories FROM %s WHERE %s = '%s'", column, table, column, data.getId());
		Cursor cursor = db.rawQuery(query, null);
		cursor.moveToFirst();
		String oldCategoriesList = cursor.getString(1);
		int startIndex = oldCategoriesList.indexOf(categoryNameSQL);
		String newValue = new StringBuilder(oldCategoriesList).delete(startIndex, startIndex+categoryNameSQL.length()).toString();
		ContentValues values = new ContentValues();
		values.put("categories", newValue);
		db.update(table, values, column + " = ?", new String[]{cursor.getString(0)});
		cursor.close();
		close();
	}
	
	public static boolean renameCategory(Context context, String oldCategoryName, String newCategoryName) {
		SQLiteDatabase db = getDatabase(context);
		if (hasCategory(db, newCategoryName)) {
			close();
			return false;
		}
		String oldCategoryNameSQL = "@" + oldCategoryName + "@";
		String newCategoryNameSQL = "@" + newCategoryName + "@";
		String query, oldCategoriesList, newValue;
		String[] tableNames = new String[] {"apps", "shortcuts"};
		int[] categoryFields = new int[] {Database.FIELD_APP_CATEGORIES, Database.FIELD_SHORTCUT_CATEGORIES};
		Cursor cursor;
		for (int i = 0; i <= 1; i++) {
			query = String.format("SELECT * FROM %s WHERE categories LIKE '%%@%s@%%'", tableNames[i], oldCategoryName);
			cursor = db.rawQuery(query, null);
			cursor.moveToFirst();
			while (!cursor.isAfterLast()) {
				oldCategoriesList = cursor.getString(categoryFields[i]);
				newValue = oldCategoriesList.replace(oldCategoryNameSQL, newCategoryNameSQL);
				ContentValues values = new ContentValues();
				values.put("categories", newValue);
				db.update(tableNames[i], values, "categories = ?", new String[]{oldCategoriesList});
				cursor.moveToNext();
			}
			cursor.close();
		}
		ContentValues values = new ContentValues();
		values.put("name", newCategoryName);
		db.update("categories", values, "name = ?", new String[]{oldCategoryName});
		close();
		return true;
	}
	
	public static void deleteCategory(Context context, String categoryName) {
		SQLiteDatabase db = getDatabase(context);
		db.delete("categories", "name = ?", new String[]{categoryName});
		clearCategory(context, categoryName);
		close();
		CategoryManager cm = LauncherApp.getCategoryManager();
		if (categoryName.equals(cm.getCurCategory()))
			cm.setCurCategory(CategoryManager.ALL);
	}
	
	public static void clearCategory(Context context, String categoryName) {
		SQLiteDatabase db = getDatabase(context);
		String categoryNameSQL = "@" + categoryName + "@";
		String query, oldCategoriesList, newValue;
		String[] tableNames = new String[] {"apps", "shortcuts"};
		int[] categoryFields = new int[] {Database.FIELD_APP_CATEGORIES, Database.FIELD_SHORTCUT_CATEGORIES};
		Cursor cursor;
		for (int i = 0; i <= 1; i++) {
			query = String.format("SELECT * FROM %s WHERE categories LIKE '%%%s%%'", tableNames[i], categoryNameSQL);
			cursor = db.rawQuery(query, null);
			cursor.moveToFirst();
			while (!cursor.isAfterLast()) {
				oldCategoriesList = cursor.getString(categoryFields[i]);
				int startIndex = oldCategoriesList.indexOf(categoryNameSQL);
				newValue = new StringBuilder(oldCategoriesList).delete(startIndex, startIndex+categoryNameSQL.length()).toString();
				ContentValues values = new ContentValues();
				values.put("categories", newValue);
				db.update(tableNames[i], values, "categories = ?", new String[]{oldCategoriesList});
				cursor.moveToNext();
			}
			cursor.close();
		}
		close();
	}
	
	public static void insertApp(Context context, String component, String name) {
		SQLiteDatabase db = getDatabase(context);
		if (!hasApp(context, component)) {
			ContentValues values = new ContentValues();
			values.put("component", component);
			values.put("name", name);
			values.put("categories", "");
			db.insert("apps", null, values);
		}
		close();
	}

	public static void insertShortcut(Context context, ContentValues values) {
        SQLiteDatabase db = getDatabase(context);
        if (!hasShortcut(context, values.getAsString("uri"))) {
        	db.insert("shortcuts", null, values);
        }
        close();
    }
    
    public static void removeApp(Context context, String component) {
    	SQLiteDatabase db = getDatabase(context);
    	db.delete("apps", "component LIKE ?", new String[]{component + "%"});
    	db.delete("shortcuts", "uri LIKE ?", new String[]{"%" + component + "%"});
    	close();
		Dock.remove(component);
    	Cache.getIconFile(context, component).delete();
    	Cache.getCustomIconFile(context, component).delete();
    }
    
    public static void removeShortcut(Context context, String uri) {
        SQLiteDatabase db = getDatabase(context);
        db.delete("shortcuts", "uri = ?", new String[]{uri});
        close();
        new File(context.getCacheDir(), uri.hashCode()+".png").delete();
    }

    public static boolean isDatabaseEmpty(Context context) {
    	SQLiteDatabase db = getDatabase(context);
    	Cursor cursor = db.rawQuery("SELECT * FROM apps", null);
		boolean result = cursor.getCount() == 0;
    	cursor.close();
    	close();
    	return result;
    }
    
    public static ArrayList<BaseData> getEntries(Context context, String categoryName) {
    	
    	String whereClause;
    	if (categoryName == null) {
    		whereClause = "";
    	} else if (categoryName.equals(CategoryManager.ALL)) {
        	whereClause = " WHERE categories NOT LIKE '%@Hidden@%'";
        } else if (categoryName.equals(CategoryManager.UNCLASSIFIED)) {
        	whereClause = " WHERE categories = '@History@' OR categories = '' OR categories = NULL";
        } else if (categoryName.equals(CategoryManager.HISTORY)) {
        	whereClause = " WHERE categories LIKE '%@History@%' ORDER BY date DESC";
        } else {
        	whereClause = " WHERE categories LIKE '%@" + categoryName + "@%'";
        }
        ArrayList<BaseData> records = new ArrayList<BaseData>();
        SQLiteDatabase db = getDatabase(context);

        Cursor cursor = db.rawQuery("SELECT * FROM shortcuts" + whereClause, null);

        cursor.moveToFirst();
        while (!cursor.isAfterLast()) {
            BaseData entry = new ShortcutData(cursor);
            records.add(entry);
            cursor.moveToNext();
        }
        cursor.close();
		cursor = db.rawQuery("SELECT * FROM apps" + whereClause, null);
		cursor.moveToFirst();
        while (!cursor.isAfterLast()) {
            BaseData entry = new AppData(cursor);
            records.add(entry);
            cursor.moveToNext();
        }
        cursor.close();
        close();
        return records;
    }
    
    public static void reduceHistory(Context context) {
    	SQLiteDatabase db = getDatabase(context);
    	Cursor cursor;
    	String[] ids = new String[2];
    	String[] queries = new String[]{
    		"SELECT component, date FROM apps WHERE categories LIKE '%@History@%' ORDER BY date ASC",
    		"SELECT uri, date FROM shortcuts WHERE categories LIKE '%@History@%' ORDER BY date ASC"
    	};
    	long[] dates = new long[]{Long.MAX_VALUE, Long.MAX_VALUE};
    	int[] counts = new int[2];
    	for (int i = 0; i <= 1; i++) {
    		cursor = db.rawQuery(queries[i], null);
	    	counts[i] = cursor.getCount();
	    	cursor.moveToFirst();
	    	if (counts[i] > 0) {
	    		ids[i] = cursor.getString(0);
	    		dates[i] = cursor.getLong(1);
	    	}
    		cursor.close();
    	}
    	close();
    	// value is compared to history size - 1, because this function is executed before the addition
    	if (counts[0] + counts[1] > ((Apps)context).options.getInt(Keys.HISTORY_SIZE, 10) - 1) {
    		if (dates[0] <= dates[1]) { 
    			removeAppFromCategory(context, ids[0], CategoryManager.HISTORY);
    		} else {
    			removeShortcutFromCategory(context, ids[1], CategoryManager.HISTORY);
    		}
    	}
    }
    
    public static void addToHistory(Context context, BaseData data) {
    	if (!hasItem(context, data, CategoryManager.HISTORY)) {
    		reduceHistory(context);
    	}
    	String table, columnName;
    	if (data instanceof AppData) {
    		table = "apps";
    		columnName = "component";
    	} else {
    		table = "shortcuts";
    		columnName = "uri";
    	}
    	SQLiteDatabase db = getDatabase(context);
    	ContentValues values = new ContentValues();
    	values.put("date", System.currentTimeMillis());
    	db.update(table, values, columnName + " = ?", new String[] {data.getId()});
    	if (!hasItem(context, data, CategoryManager.HISTORY)) {
    		addToCategory(context, data, CategoryManager.HISTORY);
    	}
    	close();
    
    }
    
    public static boolean hasComponent(Context context, String component, String categoryName) {
    	SQLiteDatabase db = getDatabase(context);
    	Cursor cursor = db.rawQuery("SELECT component FROM apps WHERE component = '"+ component+ "' AND categories LIKE '%@" + categoryName+ "@%'", null);
    	boolean result = cursor.getCount() != 0;
    	cursor.close();
    	close();
    	return result;
    }
    
    public static boolean hasItem(Context context, AppData data, String categoryName) {
    	return hasComponent(context, data.getId(), categoryName);
    }
    
    public static boolean hasItem(Context context, BaseData data, String categoryName) {
    	SQLiteDatabase db = getDatabase(context);
		boolean result = false;
    	String categoryQuery = (null == categoryName) ? "'" :
    				"' AND categories LIKE '%@" + categoryName+ "@%'";
		Cursor cursor = null;
    	if (data instanceof AppData) {
    		cursor = db.rawQuery("SELECT component FROM apps WHERE component = '"+ data.getId()
    					+ categoryQuery, null);
    	} else if (data instanceof ShortcutData) {
    		cursor = db.rawQuery("SELECT uri FROM shortcuts WHERE uri = '"+ data.getId()
						+ categoryQuery, null);
		}
		if (null != cursor) {
			result = cursor.getCount() != 0;
    		cursor.close();
		}
		close();
		return result;
    }
    
    public static boolean hasApp(Context context, String component) {
		String query = String.format("SELECT component FROM apps WHERE component = '%s'", component);
		SQLiteDatabase db = getDatabase(context);
		Cursor cursor = db.rawQuery(query, null);
		boolean result = cursor.getCount() != 0;
		cursor.close();
		close();
		return result;
	}

    public static boolean hasShortcut(Context context, String uri) {
		String query = String.format("SELECT uri FROM shortcuts WHERE uri = '%s'", uri);
    	SQLiteDatabase db = getDatabase(context);
    	Cursor cursor = db.rawQuery(query, null);
    	boolean result = cursor.getCount() != 0;
    	cursor.close();
    	close();
    	return result;
    }

	public static boolean hasMenuShortcut(Context context) {
		return hasShortcut(context, Apps.ACTION_OPEN_MENU);
	}
}
app/src/main/java/ru/henridellal/emerald/listener/OnAppLongClickListener.java
public class OnAppLongClickListener implements View.OnLongClickListener {
	private SoftReference<Apps> appsRef;
	public OnAppLongClickListener(Apps apps) {
		appsRef = new SoftReference<Apps>(apps);
	}
	
	@Override
	public boolean onLongClick(View arg0) {
		if (arg0.getTag() instanceof AppData) {
			appsRef.get().itemContextMenu((AppData)arg0.getTag());
		} else if (arg0.getTag() instanceof ShortcutData) {
			appsRef.get().itemContextMenu((ShortcutData)arg0.getTag());
		}
		return false;
	}
}
app/src/main/java/ru/henridellal/emerald/activity/Apps.java
public class Apps extends Activity
{
	private GestureDetector gestureDetector;
	private CategoryManager categories;
	private ArrayList<BaseData> curCatData;
	private GridView grid;
	public SharedPreferences options;
	public static final String PREF_APPS = "apps";
	public static final String APP_TAG = "ru.henridellal.emerald";
	public static final String ACTION_OPEN_MENU = "ru.henridellal.emerald.open_menu";
	private CustomAdapter adapter;
	public static final int GRID = 0;
	public static final int LIST = 1;
	private boolean lock, searchIsOpened, homeButtonPressed, launcherUpdate;

	private void addMenuShortcut() {
		Intent menuIntent = new Intent(this, Apps.class);
		menuIntent.setAction(ACTION_OPEN_MENU);
		Intent shortcutIntent = new Intent();
		shortcutIntent.putExtra(Intent.EXTRA_SHORTCUT_INTENT, menuIntent);
		shortcutIntent.putExtra(Intent.EXTRA_SHORTCUT_NAME, getResources().getString(R.string.launcher_menu));
		shortcutIntent.putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE, Intent.ShortcutIconResource.fromContext(this, R.mipmap.icon));
		shortcutIntent.setAction("com.android.launcher.action.INSTALL_SHORTCUT");
		sendBroadcast(shortcutIntent);
	}

	public void changePrefsOnRotate() {
		Resources res = getResources();
		float density = res.getDisplayMetrics().density;
		int iconSize, textSize, verticalSpacing, columnWidth;
		boolean hideDockInLandscape = !(options.getBoolean(Keys.DOCK_IN_LANDSCAPE, true));

		if (res.getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT) {
			//Log.v(APP_TAG, "loadFilteredApps : Portrait orientation");
			iconSize = (int)(options.getInt(Keys.ICON_SIZE, res.getInteger(R.integer.icon_size_default)) * density);
			textSize = (int)(options.getInt(Keys.TEXT_SIZE, res.getInteger(R.integer.text_size_default)) * density);
	    	verticalSpacing = (int)(options.getInt(Keys.VERTICAL_SPACING, res.getInteger(R.integer.vertical_spacing_default)) * density);

	    	if (options.getBoolean(Keys.TILE, true)) {
	    		columnWidth = (int)(options.getInt(Keys.COLUMN_WIDTH, res.getInteger(R.integer.column_width_default)) * density);
	    		grid.setColumnWidth(columnWidth);
			} else {
	    		grid.setNumColumns(1);
	    	}
	    	if (hideDockInLandscape) {
	    		Dock.setAlwaysHide(false);
	    		Dock.unhide();
	    	}
		} else {
			//Log.v(APP_TAG, "loadFilteredApps : orientation");
			iconSize = (int)(options.getInt(Keys.ICON_SIZE_LANDSCAPE, res.getInteger(R.integer.icon_size_land_default)) * density);
			textSize = (int)(options.getInt(Keys.TEXT_SIZE_LANDSCAPE, res.getInteger(R.integer.text_size_land_default)) * density);
			verticalSpacing = (int)(options.getInt(Keys.VERTICAL_SPACING_LANDSCAPE, res.getInteger(R.integer.vertical_spacing_land_default)) * density);
	    	if (options.getBoolean(Keys.TILE, true)) {
				columnWidth = (int)(options.getInt(Keys.COLUMN_WIDTH_LANDSCAPE, res.getInteger(R.integer.column_width_land_default)) * density);
	    	} else {
	    		grid.setNumColumns(2);
	    		columnWidth = -1;
	    	}
			grid.setColumnWidth(columnWidth);
	    	if (hideDockInLandscape) {
	    		Dock.hide();
	    		Dock.setAlwaysHide(true);
	    	}
		}
		adapter.setIconSize(iconSize);
		adapter.setTextSize(textSize);
		grid.setVerticalSpacing(verticalSpacing);
	}

	public void loadFilteredApps() {
		Category curCategory = categories.getCategory(categories.getCurCategory());
		curCatData = DatabaseHelper.getEntries(this, curCategory.getName());
		adapter.update(curCatData);
		if (!options.getBoolean(Keys.HIDE_MAIN_BAR, false)) {
			((Button)findViewById(R.id.category_button)).setText(curCategory.getRepresentName(this));
		}
		/*if (null != mGetAppsThread)
			mGetAppsThread.quit();*/
	}

	//launches app and adds it to history
	public boolean launch(BaseData item) {
		Intent i = item.getLaunchIntent(this);
		if (i == null) {
			return false;
		}
		try {
			startActivity(i);
		} catch (ActivityNotFoundException e) {
			Toast.makeText(this, "Activity is not found", Toast.LENGTH_LONG).show();
			DatabaseHelper.removeApp(this, item.getComponent());
			loadFilteredApps();
		} finally {
			if (searchIsOpened) {
				closeSearch();
			}
		}
		return true;
	}

	
	public static ProgressDialog progress;
	protected boolean icons;
	protected int appShortcut;
	public static Handler handler;
	public static AppsTaskHandler loadAppsHandler;
	public static GetAppsThread mGetAppsThread;

	public static class AppsTaskHandler extends Handler {
		Apps activity;
		AppsTaskHandler(Apps activity) {
			this.activity = activity;
		}
		@Override
		public void handleMessage(Message msg) {
			try {
				progress.dismiss();
			}
			catch (Exception ignored) {}
			activity.loadFilteredApps();
			mGetAppsThread.quit();
		}
	}

	@SuppressWarnings("deprecation")
	public void loadAppsFromSystem(final boolean iconPackChanged) {
		if (null != progress) {
			try {
				if (null != mGetAppsThread)
					mGetAppsThread.quit();
				progress.dismiss();
			} catch (Exception e) {}
		}
		progress = new ProgressDialog(this);
		progress.setCancelable(false);
		progress.setMessage("Getting applications...");
		progress.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
		progress.setIndeterminate(true);
		progress.show();
		//TODO access shared preferences the other way
		appShortcut = Integer.parseInt(options.getString(Keys.APP_SHORTCUT, "3"));
		icons = appShortcut >= CustomAdapter.ICON;
		// delete icons from cache if they aren't used
		if (!icons) {
			Cache.deleteIcons(this);
		}
		handler = new Handler();
		loadAppsHandler = new AppsTaskHandler(this);
		Runnable appsTask;
		if (Build.VERSION.SDK_INT >= 21) {
			appsTask = new AppsTaskLollipop(this, icons, iconPackChanged);
		} else {
			appsTask = new AppsTask(this, icons, iconPackChanged);
		}
		mGetAppsThread = new GetAppsThread("GetAppsThread");
		mGetAppsThread.start();
		mGetAppsThread.prepareHandler();
		mGetAppsThread.postTask(appsTask);
		
	}
	public static void writeIconToFile(File iconFile, Drawable d, String component) {
		try {
			Bitmap bmp;
			IconPackManager ipm = LauncherApp.getInstance().getIconPackManager();
			// get icon from icon pack
			if ((bmp = ipm.getBitmap(component)) == null) {
				// edit drawable to match icon pack
				bmp = ipm.transformDrawable(d);
			}
			// save icon in cache
			FileOutputStream out = new FileOutputStream(iconFile);
			bmp.compress(CompressFormat.PNG, 100, out);
			out.close();
		} catch (Exception e) {
			iconFile.delete();
		}
	}

	private void openCategoriesList() {
		AlertDialog.Builder builder = new AlertDialog.Builder(this);
		builder.setCancelable(true);
		categories.loadCategoriesList(); // TODO Remove after migration to 0.6.0
		final ArrayList<String> cats = new ArrayList<String>(categories.getCategories());
		cats.remove(CategoryManager.HIDDEN);
		ArrayList<String> toRemove = new ArrayList<String>();
		for (String category: cats) {
			if (categories.isHidden(category))
				toRemove.add(category);
		}
		cats.removeAll(toRemove);
		toRemove = null;
		final ArrayList<String> categoriesNames = new ArrayList<String>(cats.size());
		for (String category: cats) {
			categoriesNames.add(categories.getCategory(category).getRepresentName(this));
		}
		builder.setAdapter(new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, categoriesNames), 
			new DialogInterface.OnClickListener(){
			@Override
			public void onClick(DialogInterface di, int catNum) {
				String newCat = cats.get(catNum);
				if (!newCat.equals(categories.getCurCategory())) {
					categories.setCurCategory(newCat);
					loadFilteredApps();
				}
			}
		});
		if (!isFinishing())
			builder.create().show();
	}
	//shows popup window for editing categories of app or shortcut
	private void itemEdit(final BaseData item) {
		//Log.v(APP_TAG, "Open app edit window");
		
		AlertDialog.Builder builder = new AlertDialog.Builder(this);

		builder.setTitle(item.getName());
		builder.setCancelable(true);

		ArrayList<String> editableCategories =  categories.getEditableCategories();
		final int nCategories = editableCategories.size();
		ArrayList<String> editableCategoriesNames = new ArrayList<String>(nCategories);
		final int position = grid.getFirstVisiblePosition();
		for (String category: editableCategories) {
			editableCategoriesNames.add(categories.getCategory(category).getRepresentName(this));
		}
		if (nCategories > 0) {
			final String[] editableCategoriesArray = new String[nCategories];
			editableCategories.toArray(editableCategoriesArray);
			final String[] editableCategoriesNamesArray = new String[nCategories];
			editableCategoriesNames.toArray(editableCategoriesNamesArray);
			
			final boolean[] checked = new boolean[nCategories];			

			for (int i = 0; i < nCategories ; i++) {
				checked[i] = DatabaseHelper.hasItem(this, item, editableCategoriesArray[i]);
			}

			final boolean[] oldChecked = checked.clone();

			builder.setMultiChoiceItems(editableCategoriesNamesArray, checked, 
				new DialogInterface.OnMultiChoiceClickListener() {							
				@Override
				public void onClick(DialogInterface dialog, int which, boolean isChecked) {
				}
			});
			builder.setPositiveButton("OK", new OnClickListener(){
				@Override
				public void onClick(DialogInterface arg0, int arg1) {
					for (int i = 0 ; i < nCategories ; i++) {
						if (checked[i] && ! oldChecked[i])
							DatabaseHelper.addToCategory(Apps.this, item, editableCategoriesArray[i]);
						else if (!checked[i] && oldChecked[i])
							DatabaseHelper.removeFromCategory(Apps.this, item, editableCategoriesArray[i]);
					}
					loadFilteredApps();
					grid.setSelection(position);
				}});
		}
		if (!isFinishing())
			builder.create().show();
	}
	public void itemContextMenu(final AppData item) {
		//Log.v(APP_TAG, "Open app edit window");
		
		AlertDialog.Builder builder = new AlertDialog.Builder(this);
		Resources res = getResources();
		builder.setTitle(item.getName());
		builder.setCancelable(true);
		
		String[] commands = new String[]{
			res.getString(R.string.aboutTitle),
				res.getString(R.string.findInMarket),
			res.getString(R.string.editAppCategories),
			res.getString(R.string.uninstall),
			(Dock.hasApp(item)) ? 
				res.getString(R.string.remove_from_dock):
				res.getString(R.string.add_to_dock),
			res.getString(R.string.change_icon)
		};
		builder.setAdapter(new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, commands),
		new DialogInterface.OnClickListener(){
			public void onClick(DialogInterface di, int which) {
				Uri uri;
				switch(which) {
					case 0:
						uri = Uri.parse("package:"+ComponentName.unflattenFromString(
							item.getComponent()).getPackageName());
						startActivity(new Intent(android.provider.Settings.ACTION_APPLICATION_DETAILS_SETTINGS, uri));
						break;
					case 1:
						uri = Uri.parse("market://details?id="+ComponentName.unflattenFromString(
							item.getComponent()).getPackageName());
						startActivity(new Intent(Intent.ACTION_VIEW, uri));
						break;
					case 2:
						itemEdit(item);
						break;
					case 3:
						uri = Uri.parse("package:"+ComponentName.unflattenFromString(
							item.getComponent()).getPackageName());
						startActivityForResult(new Intent(Intent.ACTION_DELETE, uri), 0);
						break;
					case 4:
						if (Dock.hasApp(item)) {
							Dock.remove(item);
						} else {
							if (!Dock.isFull()) {
								Dock.add(item);
							} else {
								Toast.makeText(Apps.this, getResources().getString(R.string.dock_is_full), Toast.LENGTH_LONG).show();
							}
						}
						Dock.update();
						break;
					case 5:
						Intent intent = new Intent(Apps.this, ChangeIconActivity.class);
						intent.putExtra(ChangeIconActivity.COMPONENT_NAME, item.getComponent());
						intent.putExtra(ChangeIconActivity.SHORTCUT_NAME, item.getName());
						startActivity(intent);
						break;
				}
			}
		});
		if (!isFinishing())
			builder.create().show();
	}
	
	public void itemContextMenu(final ShortcutData item) {
		//Log.v(APP_TAG, "Open app edit window");
		
		AlertDialog.Builder builder = new AlertDialog.Builder(this);
		Resources res = getResources();
		builder.setTitle(item.getName());
		builder.setCancelable(true);
		
		String[] commands = new String[]{
				res.getString(R.string.editCategories),
				res.getString(R.string.uninstall),
			(Dock.hasApp(item)) ?
					res.getString(R.string.remove_from_dock):
					res.getString(R.string.add_to_dock)
		};
		builder.setAdapter(new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, commands),
		new DialogInterface.OnClickListener(){
			public void onClick(DialogInterface di, int which) {
				switch(which) {
					case 0:
						itemEdit(item);
						break;
					case 1:
						DatabaseHelper.removeShortcut(Apps.this, item.getUri());
						loadFilteredApps();
						break;
					case 2:
						if (Dock.hasApp(item)) {
							Dock.remove(item);
						} else {
							if (!Dock.isFull()) {
								Dock.add(item);
							} else {
								Toast.makeText(Apps.this, getResources().getString(R.string.dock_is_full), Toast.LENGTH_LONG).show();
							}
						}
						Dock.update();
						break;
				}
			}
		});
		if (!isFinishing())
			builder.create().show();
	}

	@Override
	public void onConfigurationChanged(Configuration newConfig) {
		//Log.v(APP_TAG, "Configuration changed");
		super.onConfigurationChanged(newConfig);
		changePrefsOnRotate();
		loadFilteredApps();
	}
	
	private void menu() {
		//Log.v(APP_TAG, "Trying to open menu");
		if (lock) {
			AlertDialog.Builder builder = new AlertDialog.Builder(this);
			//builder.setTitle("");
			builder.setMessage(getResources().getString(R.string.type_password));
			final EditText inputBox = new EditText(this);
			inputBox.setInputType(InputType.TYPE_CLASS_TEXT|InputType.TYPE_TEXT_VARIATION_PASSWORD);
			builder.setView(inputBox);
			builder.setPositiveButton(android.R.string.yes, 
				new DialogInterface.OnClickListener() {
				@Override
				public void onClick(DialogInterface dialog, int which) {
					if (inputBox.getText().toString().equals(options.getString(Keys.PASSWORD, ""))) {
						openOptionsMenu();
					} else {
						Toast.makeText(getApplicationContext(), getResources().getString(R.string.wrong_password), Toast.LENGTH_LONG).show();
					}
			} });
			builder.setCancelable(true);
			if (!isFinishing()) {
				builder.show();
			}
		} else {
			openOptionsMenu();
		}
	}
	private boolean isDefaultLauncher() {
		final Intent intent = new Intent(Intent.ACTION_MAIN);
		intent.addCategory(Intent.CATEGORY_HOME);
		final ResolveInfo resolveInfo =
				getPackageManager().resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY);
        return resolveInfo != null &&
				getPackageName().equals(resolveInfo.activityInfo.packageName);
	}
	
	protected void onMenuButton(View v) {
		menu();
	}
	public void searchInWeb(String text) {
		String site = options.getString(Keys.SEARCH_PROVIDER, "https://duckduckgo.com/?q=");
		String url = site + text;
		Intent intent = new Intent(Intent.ACTION_VIEW);
		intent.setData(Uri.parse(url));
		try {
			startActivity(intent);
		} catch (ActivityNotFoundException e) {
			Toast.makeText(this, "Cannot handle "+ url + " request", Toast.LENGTH_LONG).show();
		}
	}
	public void openSearch() {
		//Log.v(APP_TAG, "Start searching");
		categories.setCurCategory(CategoryManager.ALL);
		loadFilteredApps();
		toggleFullscreen(false);
		if (options.getBoolean(Keys.HIDE_MAIN_BAR, false)) {
			findViewById(R.id.main_bar).setVisibility(View.VISIBLE);
		}
		findViewById(R.id.tabs).setVisibility(View.GONE);
		InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
		final EditText text = (EditText)findViewById(R.id.textField);
		if (imm != null) {
			imm.showSoftInput(grid, InputMethodManager.SHOW_IMPLICIT);
		}
		findViewById(R.id.searchBar).setVisibility(View.VISIBLE);
		text.setVisibility(View.VISIBLE);
		findViewById(R.id.webSearchButton).setVisibility(View.VISIBLE);
		if (Dock.isVisible()) {
			Dock.hide();
		}
		text.addTextChangedListener(new TextWatcher() {
			@Override
			public void afterTextChanged(Editable s) {}
			@Override
			public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
			@Override
			public void onTextChanged(CharSequence s, int start, int count, int after) {
				adapter.filter(s);
			}
		});
		text.setOnKeyListener(new View.OnKeyListener() {
			public boolean onKey(View v, int keyCode, KeyEvent event) {
				if (event.getAction() != KeyEvent.ACTION_DOWN) {
					return false;
				}
				// allow arrow up/down to focus the grid of apps
				if (keyCode == KeyEvent.KEYCODE_DPAD_UP || keyCode == KeyEvent.KEYCODE_DPAD_DOWN) {
					grid.requestFocus();
					return true;
				}
				// On enter, search the internet
				if (keyCode == KeyEvent.KEYCODE_ENTER) {
					searchInWeb(text.getText().toString());
					return true;
				}
				return false;
			}
		});
		View.OnClickListener onClick = new View.OnClickListener() {
			@Override
			public void onClick(View v) {
				searchInWeb(text.getText().toString());
			}
		};
		findViewById(R.id.webSearchButton).setOnClickListener(onClick);
		searchIsOpened = true;
		text.requestFocusFromTouch();
	}
	public void closeSearch() {
		//Log.v(APP_TAG, "Quit search");
		EditText text = (EditText)findViewById(R.id.textField);
		text.setText("");
		findViewById(R.id.searchBar).setVisibility(View.GONE);
		findViewById(R.id.webSearchButton).setVisibility(View.GONE);
		text.setVisibility(View.GONE);
		hideMainBarIfNeeded();
		if (!Dock.isEmpty()) {
			Dock.unhide();
		}
		searchIsOpened=false;
		toggleFullscreen(true);
	}
	private void hideMainBarIfNeeded() {
		if (options.getBoolean(Keys.HIDE_MAIN_BAR, false)) {
			findViewById(R.id.main_bar).setVisibility(View.GONE);
		} else {
			findViewById(R.id.tabs).setVisibility(View.VISIBLE);
		}
	}
	public void onMyClick(View v) {
		switch(v.getId()) {
			case R.id.searchButton:
				openSearch();
				break;
			case R.id.category_button:
				openCategoriesList();
				break;
			case R.id.menuButton:
				menu();
				break;
			case R.id.quit_hidden_apps:
				toggleHiddenCategory();
				break;
		}
	}
	
	@Override
	public boolean dispatchTouchEvent(MotionEvent event) {
		gestureDetector.onTouchEvent(event);
		return super.dispatchTouchEvent(event);
	}
	
	@Override
	public boolean onKeyDown(int keyCode, KeyEvent event) {
		if (event.getAction() != KeyEvent.ACTION_DOWN) {
			return super.onKeyDown(keyCode, event);
		}
		if (keyCode == KeyEvent.KEYCODE_MENU || keyCode == KeyEvent.KEYCODE_BUTTON_SELECT) {
			menu();
			return true;
		} else if (keyCode == KeyEvent.KEYCODE_INFO || keyCode == KeyEvent.KEYCODE_BUTTON_Y) {
			BaseData item = getFocusedLaunchItem();
			if (item instanceof AppData) {
				itemContextMenu((AppData) item);
				return true;
			} else if (item instanceof ShortcutData) {
				itemContextMenu((ShortcutData) item);
				return true;
			}
		} else if (keyCode == KeyEvent.KEYCODE_BACK || keyCode == KeyEvent.KEYCODE_ESCAPE
			|| keyCode == KeyEvent.KEYCODE_BUTTON_B) {
			//Log.v(APP_TAG, "BACK pressed");
			if (searchIsOpened) {
				closeSearch();
			} else if (!isDefaultLauncher()) {
				finish();
				return true;
			} else {
				if (categories.getCurCategory().equals(CategoryManager.HIDDEN)) {
					findViewById(R.id.quit_hidden_apps).setVisibility(View.GONE);
					hideMainBarIfNeeded();
				}
				categories.prevCategory();
			}
			loadFilteredApps();
			return true;
		} else if (event.isAltPressed()) {
			if (keyCode >= KeyEvent.KEYCODE_1 && keyCode <= KeyEvent.KEYCODE_9) {
				Object app = Dock.getApp(keyCode-KeyEvent.KEYCODE_1);
				if (app instanceof BaseData && launch((BaseData) app)) {
					return true;
				}
			} else if (keyCode == KeyEvent.KEYCODE_0 && !searchIsOpened) {
				openSearch();
			} else if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT) {
				categories.setCurCategory(categories.getCategory(CategoryManager.PREVIOUS));
				loadFilteredApps();
			} else if (keyCode == KeyEvent.KEYCODE_DPAD_RIGHT) {
				categories.setCurCategory(categories.getCategory(CategoryManager.NEXT));
				loadFilteredApps();
			} else if (keyCode == KeyEvent.KEYCODE_DPAD_UP) {
				openCategoriesList();
			} else if (keyCode == KeyEvent.KEYCODE_O && !lock) {
				startActivity(new Intent(this, Options.class));
			} else if (keyCode == KeyEvent.KEYCODE_H && !lock) {
				toggleHiddenCategory();
			} else if (keyCode == KeyEvent.KEYCODE_R && !lock) {
				if (null != mGetAppsThread)
					mGetAppsThread.quit();
				loadAppsFromSystem(true);
			}
		} else if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER || keyCode == KeyEvent.KEYCODE_ENTER
				||	keyCode == KeyEvent.KEYCODE_BUTTON_A || keyCode == KeyEvent.KEYCODE_BUTTON_1) {
			BaseData item = getFocusedLaunchItem();
			if (item != null && launch(item)) {
				return true;
			}
		} else if (keyCode == KeyEvent.KEYCODE_VOLUME_UP) {
			if (options.getBoolean(Keys.VOLUME_BUTTONS, false)) {
				categories.setCurCategory(categories.getCategory(CategoryManager.PREVIOUS));
				loadFilteredApps();
				return true;
			}
		} else if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN) {
			if (options.getBoolean(Keys.VOLUME_BUTTONS, false)) {
				categories.setCurCategory(categories.getCategory(CategoryManager.NEXT));
				loadFilteredApps();
				return true;
			}
		} else if (keyCode == KeyEvent.KEYCODE_BUTTON_L1 || keyCode == KeyEvent.KEYCODE_BUTTON_L2
				|| keyCode == KeyEvent.KEYCODE_BUTTON_THUMBL|| keyCode == KeyEvent.KEYCODE_PAGE_DOWN) {
			categories.setCurCategory(categories.getCategory(CategoryManager.PREVIOUS));
			loadFilteredApps();
			return true;
		} else if (keyCode == KeyEvent.KEYCODE_BUTTON_R1 || keyCode == KeyEvent.KEYCODE_BUTTON_R2
				|| keyCode == KeyEvent.KEYCODE_BUTTON_THUMBR || keyCode == KeyEvent.KEYCODE_PAGE_UP) {
			categories.setCurCategory(categories.getCategory(CategoryManager.NEXT));
			loadFilteredApps();
			return true;
		} else if (event.isPrintingKey() && !searchIsOpened) {
			openSearch(); // search as you type
			EditText text = findViewById(R.id.textField);
			return text.onKeyDown(keyCode, event);
		}
		return super.onKeyDown(keyCode, event);
	}
	
	@Override
	public void onBackPressed() {}
	
	@Override
	protected void onStop() {
		super.onStop();
	}
	
	@Override
	protected void onPause() {
		//Log.v(APP_TAG, "onPause");
		super.onPause();
		if (searchIsOpened) {
			closeSearch();
		}
	}
	@Override
	protected void onDestroy() {
		//Log.v(APP_TAG, "onDestroy");
		grid.setOnScrollListener(null);
		grid.setOnTouchListener(null);
		grid.setOnItemClickListener(null);
		super.onDestroy();
	}
	
	@Override
	public void onActivityResult(int requestCode, int resultCode, Intent data) {
		if (resultCode == RESULT_OK) {
			loadFilteredApps();
		}
	}
	
	@Override
	protected void onNewIntent(Intent i) {
		//Log.v(APP_TAG, "onNewIntent");
		String action = i.getAction();
		if (Intent.ACTION_MAIN.equals(action)) {
			handleHomeButtonPress();
		} else if (ACTION_OPEN_MENU.equals(action)) {
			menu();
		}
		
		super.onNewIntent(i);
	}

	private BaseData getFocusedLaunchItem() {
		View view = getCurrentFocus();
		if (view instanceof GridView) {
			view = ((GridView) view).getSelectedView();
		}
		if (view != null) {
			Object viewTag = view.getTag();
			if (viewTag instanceof BaseData) {
				return (AppData)viewTag;
			}
		}
		return null;
	}

	private void handleHomeButtonPress() {
		try {
		homeButtonPressed = true;
		if (categories == null) {
			categories = LauncherApp.getCategoryManager();
		}
		if (categories.getCurCategory().equals(CategoryManager.HIDDEN)) {
			findViewById(R.id.quit_hidden_apps).setVisibility(View.GONE);
			hideMainBarIfNeeded();
			categories.setCurCategory(categories.getHome());
		} else if (categories.getCurCategory().equals(categories.getHome())) {
			String newCategory = options.getString(Keys.HOME_BUTTON, "");
			if (newCategory.length() > 0) {
				categories.setCurCategory(newCategory);
			} else {
				categories.setCurCategory(categories.getHome());
			}
		} else {
			categories.setCurCategory(categories.getHome());
		}
		loadFilteredApps();
		} catch (Exception e) {
			Toast.makeText(this, e.toString(), Toast.LENGTH_LONG).show();
		}
	}

	private void toggleFullscreen(boolean enableFullscreen) {
		if (options.getBoolean(Keys.FULLSCREEN, false)) {
			if (enableFullscreen) {
				getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);
			} else {
				getWindow().clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
			}
		}
	}

	private void toggleHiddenCategory() {
		if (CategoryManager.HIDDEN.equals(categories.getCurCategory())) {
			categories.setCurCategory(categories.getHome());
			findViewById(R.id.quit_hidden_apps).setVisibility(View.GONE);
			hideMainBarIfNeeded();
		} else {
			categories.setCurCategory(CategoryManager.HIDDEN);
			findViewById(R.id.searchBar).setVisibility(View.GONE);
			findViewById(R.id.tabs).setVisibility(View.GONE);
			if (options.getBoolean(Keys.HIDE_MAIN_BAR, false)) {
				findViewById(R.id.main_bar).setVisibility(View.VISIBLE);
			}
			findViewById(R.id.quit_hidden_apps).setVisibility(View.VISIBLE);
		}
		if (searchIsOpened) {
			closeSearch();
		}
		loadFilteredApps();
	}

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		//Log.v(APP_TAG, "onCreate");
		options = PreferenceManager.getDefaultSharedPreferences(this);
		if (options.getBoolean(Keys.FULLSCREEN, false)) {
			requestWindowFeature(Window.FEATURE_NO_TITLE);
		}
		categories = LauncherApp.getCategoryManager();
		super.onCreate(savedInstanceState);
		if (Build.VERSION.SDK_INT >= 11) {
			Themer.applyTheme(this, options);
			if (options.getBoolean(Keys.KEEP_IN_MEMORY, false)) {
				PersistentNotification.setNotification(this);
			}
		}
		if (options.getBoolean(Keys.SHOW_TUTORIAL, true)) {
			startActivity(new Intent(this, TutorialActivity.class));
		}
		if (!DatabaseHelper.hasMenuShortcut(this) && Build.VERSION.SDK_INT < 26) {
			addMenuShortcut();
		}
		setRequestedOrientation(Integer.parseInt(options.getString(Keys.ORIENTATION, "2")));
		if (Build.VERSION.SDK_INT >= 21) {
			Themer.setWindowDecorations(this, options);
		}
		toggleFullscreen(true);
		setContentView(MainLayout.get(this, options));
		grid = (GridView) findViewById(R.id.appsGrid);
		adapter = new CustomAdapter(this);
		grid.setAdapter(adapter);
		options.edit().putBoolean(Keys.MESSAGE_SHOWN, true).commit();
		if (options.getBoolean(Keys.ICON_PACK_CHANGED, false)) {
			loadAppsFromSystem(true);
			options.edit().putBoolean(Keys.ICON_PACK_CHANGED, false).commit();
		}
		Dock.init(this);
		changePrefsOnRotate();
		gestureDetector = new GestureDetector(this, new SwipeListener(this));
		grid.setOnTouchListener(new View.OnTouchListener() {
			public boolean onTouch(View view, MotionEvent event) {
				return gestureDetector.onTouchEvent(event);
			}
		});
		grid.setOnItemClickListener(new AdapterView.OnItemClickListener() {
			@Override
			public void onItemClick(AdapterView<?> adapterView, View view, int i, long l) {
				view.callOnClick();
			}
		});
	}
	@Override
	public boolean onOptionsItemSelected(MenuItem item) {
		//Log.v(APP_TAG, "Menu item is selected");
		switch(item.getItemId()) {
		case R.id.full_scan:
			if (null != mGetAppsThread)
				mGetAppsThread.quit();
			loadAppsFromSystem(true);
			return true;
		case R.id.change_wallpaper:
			startActivity(new Intent(Intent.ACTION_SET_WALLPAPER));
			return true;
		case R.id.options:
			startActivity(new Intent(this, Options.class));
			return true;
		case R.id.access_hidden:
			toggleHiddenCategory();
			return true;
		default:
			return false;
		}
	}

	@Override
	public boolean onCreateOptionsMenu(Menu menu) {
		getMenuInflater().inflate(R.menu.main, menu);
		return true;
	}

	@Override
	protected void onResume() {
		super.onResume();
		//Log.v(APP_TAG, "onResume");
		
	    lock = options.getString(Keys.PASSWORD, "").length() > 0;
	    /*if (!homeButtonPressed) {
	    	try {
	    		//loadList(false);
	    	} catch (Exception e) {
	    		Toast.makeText(Apps.this, e.toString(), Toast.LENGTH_LONG).show();
	    	}
	    } else {
	    	homeButtonPressed = false;
	    }*/
	    if (homeButtonPressed) {
	    	homeButtonPressed = false;
	    }
	    
		if (DatabaseHelper.isDatabaseEmpty(this) && !launcherUpdate) {
			loadAppsFromSystem(true);
		} else {
			loadFilteredApps();
		}
		
		Dock.initApps();
		launcherUpdate = false;
	}
	@Override
	protected void onPostResume() {
		super.onPostResume();
		if (options.getBoolean(Keys.SHOW_KEYBOARD_ON_START, false)) {
			grid.postDelayed(new Runnable() {
				public void run() {
					openSearch();
				}
			}, 100);
		}
	}
}


Question: package ru.henridellal.emerald.ui;
import java.util.ArrayList;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileReader;
import java.io.FileWriter;
import java.lang.ref.SoftReference;
import android.content.Context;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.Toast;
import ru.henridellal.emerald.activity.Apps;
import ru.henridellal.emerald.util.IconPackManager;
import ru.henridellal.emerald.R;
import ru.henridellal.emerald.data.AppData;
import ru.henridellal.emerald.data.BaseData;
import ru.henridellal.emerald.data.DatabaseHelper;
import ru.henridellal.emerald.data.ShortcutData;
import ru.henridellal.emerald.listener.OnAppClickListener;
import ru.henridellal.emerald.listener.OnAppLongClickListener;
import ru.henridellal.emerald.listener.OnAppUnlockLongClickListener;
import ru.henridellal.emerald.preference.Keys;




public class Dock {
	private static int[] buttonIds = {
			R.id.button1, R.id.button2,
			R.id.button3, R.id.button4,
			R.id.button5
		};

	private static ArrayList<BaseData> apps;
	private static ArrayList<ImageView> buttons;
	private static SoftReference<Context> contextRef;
	private static LinearLayout dockBar;
	private static int defaultHeight;
	private static boolean alwaysHide = false;
	private static OnAppClickListener onAppClickListener;
	private static View.OnLongClickListener onAppLongClickListener;
	
	public static void init(Context context) {
		Apps mainActivity = (Apps) context;
		onAppClickListener = new OnAppClickListener(mainActivity);
		if (mainActivity.options.getString(Keys.PASSWORD, "").length() > 0) {
			onAppLongClickListener = new OnAppUnlockLongClickListener(context);
		} else {
			onAppLongClickListener = new OnAppLongClickListener(mainActivity);
		}
		contextRef = new SoftReference<Context>(context);
		dockBar = (LinearLayout) mainActivity.findViewById(R.id.dock_bar);
		defaultHeight = dockBar.getLayoutParams().height;
		apps = new ArrayList<BaseData>();
		addButtons(mainActivity);
	}

	private static void addButtons(Apps mainActivity) {
		buttons = new ArrayList<ImageView>();
		for (int i = 0; i < 5; i++)
			buttons.add((ImageView)mainActivity.findViewById(buttonIds[i]));
	}

	public static Object getApp(int index) {
		return ((apps.size() > index) ? apps.get(index) : null);
	}
	public static boolean hasApp(BaseData app) {
		return apps.contains(app);
	}
	
	public static void add(BaseData app) {
		if (DatabaseHelper.hasItem(contextRef.get(), app, null)) {
			apps.add(app);
			saveApps();
		}
	}

	public static void remove(BaseData app) {
		apps.remove(app);
		saveApps();
	}

	public static void remove(String component) {
		for (BaseData app: apps) {
			if (component.equals(app.getId())) {
				remove(app);
				return;
			}
		}
	}

	// TODO remove after implementation of "unlimited" dock
	public static boolean isFull() {
		return apps.size() == buttons.size();
	}

	public static boolean isEmpty() {
		return apps.size() == 0;
	}

	public static void initApps() {
		BufferedReader reader = null;
		boolean needSave = false;
		apps.clear();
		File f = new File(contextRef.get().getFilesDir(), "dock");
		try {
			if (!f.exists()) {
				f.createNewFile();
				update();
				return;
			}
			reader = new BufferedReader(new FileReader(f));
			String data;
			while (null != (data = reader.readLine())) {
				BaseData a;
Answer: 				if (data.startsWith(AppData.COMPONENT)) {