/* * Last Launcher * Copyright (C) 2019,2020 Shubham Tyagi * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package io.github.subhamtyagi.lastlauncher; import android.Manifest; import android.annotation.TargetApi; import android.app.Activity; import android.app.Dialog; import android.content.BroadcastReceiver; import android.content.ClipData; import android.content.ClipboardManager; import android.content.ComponentName; import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.database.Cursor; import android.graphics.Color; import android.graphics.Typeface; import android.net.Uri; import android.os.AsyncTask; import android.os.Build; import android.os.Bundle; import android.provider.MediaStore; import android.text.Editable; import android.text.SpannableString; import android.text.TextWatcher; import android.text.style.ForegroundColorSpan; import android.util.ArrayMap; import android.view.ContextThemeWrapper; import android.view.Gravity; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup.LayoutParams; import android.view.Window; import android.view.WindowManager; import android.view.inputmethod.EditorInfo; import android.view.inputmethod.InputMethodManager; import android.widget.EditText; import android.widget.PopupMenu; import android.widget.TextView; import org.apmem.tools.layouts.FlowLayout; import java.io.FileNotFoundException; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Map; import io.github.subhamtyagi.lastlauncher.dialogs.ColorSizeDialog; import io.github.subhamtyagi.lastlauncher.dialogs.FrozenAppsDialogs; import io.github.subhamtyagi.lastlauncher.dialogs.GlobalColorSizeDialog; import io.github.subhamtyagi.lastlauncher.dialogs.GlobalSettingsDialog; import io.github.subhamtyagi.lastlauncher.dialogs.HiddenAppsDialogs; import io.github.subhamtyagi.lastlauncher.dialogs.PaddingDialog; import io.github.subhamtyagi.lastlauncher.dialogs.RenameInputDialogs; import io.github.subhamtyagi.lastlauncher.model.Apps; import io.github.subhamtyagi.lastlauncher.model.Shortcut; import io.github.subhamtyagi.lastlauncher.utils.CrashUtils; import io.github.subhamtyagi.lastlauncher.utils.DbUtils; import io.github.subhamtyagi.lastlauncher.utils.Gestures; import io.github.subhamtyagi.lastlauncher.utils.ShortcutUtils; import io.github.subhamtyagi.lastlauncher.utils.Utils; import io.github.subhamtyagi.lastlauncher.views.textview.AppTextView; import static android.content.Intent.ACTION_PACKAGE_ADDED; import static android.content.Intent.ACTION_PACKAGE_REMOVED; import static android.content.Intent.ACTION_PACKAGE_REPLACED; /** * -------------------------------------------------------------------------- * People can criticise me all the time they want, * by this is what I am and I won't change the way I live for them. * I live the way I want to flow. * ------------------------------------------------------------------------- * - * If we don’t transform the world, who will? If not now, when? * If you have something to give, give it now * - * Do your little bit of good where you are; * it’s those little bits of good put together that overwhelm the world." * - * Don’t just think, do it. Now it is you turn, do it now, go fast and open pull request * - * ---------------------------------------------------------------------------- * This Activity extends the api 14 Activity Class not latest AppCompatActivity * Reason: Small apk size */ public class LauncherActivity extends Activity implements View.OnClickListener, View.OnLongClickListener, Gestures.OnSwipeListener { public static final int COLOR_SNIFFER_REQUEST = 154; public final static String DEFAULT_COLOR_FOR_APPS = "default_color_for_apps"; //various sorting constant //why constant? Why not enums for this ? // may be lack from DB side public static final int SORT_BY_NAME = 1; public static final int SORT_BY_SIZE = 2; public static final int SORT_BY_COLOR = 3; public static final int SORT_BY_OPENING_COUNTS = 4; public static final int SORT_BY_CUSTOM = 5; public static final int SORT_BY_UPDATE_TIME = 6; public static final int SORT_BY_RECENT_OPEN = 7; private static final int RESTORE_REQUEST = 125; private static final int FONTS_REQUEST = 126; private static final int PERMISSION_REQUEST = 127; private static final int DEFAUTL_TEXT_SIZE_NORMAL_APPS = 20; private static final int DEFAUTL_TEXT_SIZE_OFTEN_APPS = 36; public static ArrayList<Apps> mAppsList; private static FlowLayout mHomeLayout; // when search bar is appear this will be true and show search result private static boolean searching = false; private static int recentlyUsedCounter = 0; private final String TAG = "LauncherActivity"; private BroadcastReceiver broadcastReceiverAppInstall; private BroadcastReceiver broadcastReceiverShortcutInstall; private Typeface mTypeface; private Dialog dialogs; //search box private EditText mSearchBox; private InputMethodManager imm; // gesture detector private Gestures detector; private static void showSearchResult(ArrayList<Apps> filteredApps) { if (!searching) return; mHomeLayout.removeAllViews(); //mHomeLayout. //Log.d(TAG, "showSearchResult: yes search result showed "); mHomeLayout.setPadding(0, 150, 0, 0); /*//sort the apps alphabetically Collections.sort(filteredApps, (a, b) -> String.CASE_INSENSITIVE_ORDER.compare( a.getAppName(), b.getAppName() ));*/ for (Apps apps : filteredApps) { mHomeLayout.addView(apps.getTextView(), new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT)); } } @Override public boolean dispatchTouchEvent(MotionEvent ev) { //pass touch event to detector detector.onTouchEvent(ev); return super.dispatchTouchEvent(ev); } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // initialize the shared prefs may be done in application class DbUtils.init(this); if (BuildConfig.DEBUG) { new CrashUtils(getApplicationContext(), ""); } int theme = DbUtils.getTheme(); //theme must be set before setContentView setTheme(theme); setContentView(R.layout.activity_launcher); // set the status bar color as per theme if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { setNavigationAndStatusBarColor(theme); } // set the fonts setFont(); mHomeLayout = findViewById(R.id.home_layout); mHomeLayout.setOnLongClickListener(this); imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); //our search box mSearchBox = findViewById(R.id.search_box); //setup listeners on mSearchBox setSearchBoxListeners(); //set alignment default is center|center_vertical mHomeLayout.setGravity(DbUtils.getFlowLayoutAlignment()); //set padding .. mHomeLayout.setPadding(DbUtils.getPaddingLeft(), DbUtils.getPaddingTop(), DbUtils.getPaddingRight(), DbUtils.getPaddingBottom()); detector = new Gestures(this, this); // initGestures(); // loads the apps loadApps(); // register the receiver for installed, uninstall, update apps and shortcut pwa add registerForReceivers(); } private void setSearchBoxListeners() { mSearchBox.addTextChangedListener(new TextWatcher() { @Override public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) { } @Override public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) { //do here search //Log.d("LAL", "onTextChanged: " + charSequence + " charsequence lenght" + charSequence.toString().length()); new SearchTask().execute(charSequence); } @Override public void afterTextChanged(Editable editable) { // do everything } }); mSearchBox.setOnEditorActionListener((v, actionId, event) -> { if (actionId == EditorInfo.IME_ACTION_DONE) { mSearchBox.setVisibility(View.GONE); imm.hideSoftInputFromWindow(mSearchBox.getWindowToken(), 0); //do something on clicking enter return true; } return false; }); } /** * set the color of status bar and navigation bar as per theme * if theme color is light then pass this to system so status icon color will turn into black * * @param theme current theme applied to launcher */ @TargetApi(Build.VERSION_CODES.KITKAT) private void setNavigationAndStatusBarColor(int theme) { getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { getWindow().addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS); getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION); // getWindow().setNavigationBarColor(getResources().getColor(android.R.color.transparent)); } if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { switch (theme) { case R.style.White: case R.style.WhiteOnGrey: case R.style.BlackOnGrey: { getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR); } } } } public void setFont() { // get and set fonts String fontsPath = DbUtils.getFonts(); if (fontsPath == null) { mTypeface = Typeface.createFromAsset(getAssets(), "fonts/raleway_bold.ttf"); } else { try { mTypeface = Typeface.createFromFile(fontsPath); } catch (Exception i) { mTypeface = Typeface.createFromAsset(getAssets(), "fonts/raleway_bold.ttf"); } } } public void loadApps() { // get the apps installed on devices; Intent startupIntent = new Intent(Intent.ACTION_MAIN, null); startupIntent.addCategory(Intent.CATEGORY_LAUNCHER); PackageManager pm = getPackageManager(); List<ResolveInfo> activities = pm.queryIntentActivities(startupIntent, 0); // check whether our app list is already initialized if yes then clear this(when new app or shortcut installed) if (mAppsList != null) { mAppsList.clear(); } // shortcut or pwa counts int installedShortcut = ShortcutUtils.getShortcutCounts(); // Log.d(TAG, "loadApps: install shortcut sizes::" + installedShortcut); int appsCount = activities.size(); mAppsList = new ArrayList<>(appsCount + installedShortcut); // get the most used apps // a list of app that are popular on fdroid and some of my apps List<String> oftenApps = Utils.getOftenAppsList(); List<String> coloredAppsList = Utils.getColoredAppsList(); String packageName, appName; int color, textSize; boolean hide; // iterate over each app and initialize app list for (ResolveInfo resolveInfo : activities) { packageName = resolveInfo.activityInfo.packageName; // activity name as com.example/com.example.MainActivity String activity = packageName + "/" + resolveInfo.activityInfo.name; /// save the app original name so that we can use this later e.g if user change /// the app name then we have the name in DB DbUtils.putAppOriginalName(activity, resolveInfo.loadLabel(pm).toString()); // check whether user set the custom app name for eg. long name to small name appName = DbUtils.getAppName(activity, resolveInfo.loadLabel(pm).toString()); // is app is hidden by user hide = DbUtils.isAppHidden(activity); // get the app text size textSize = DbUtils.getAppSize(activity); // check if text size is null then set the size to default size // size is null(-1) when user installed this app if (textSize == DbUtils.NULL_TEXT_SIZE) { if (oftenApps.contains(packageName)) { textSize = DEFAUTL_TEXT_SIZE_OFTEN_APPS; } else { textSize = DEFAUTL_TEXT_SIZE_NORMAL_APPS; } /// DbUtils.putAppSize(activity, textSize); } // get app color color = DbUtils.getAppColor(activity); // check for default color : set default colors if random color is not set if (!DbUtils.isRandomColor() && color == DbUtils.NULL_TEXT_COLOR) { color = DbUtils.getAppsColorDefault(); if (coloredAppsList.contains(packageName)) { color = Utils.getColor(); } } // whether app size is freezed boolean freeze = DbUtils.isAppFrozen(activity); // this is a separate implementation of ColorSniffer app // if User set the color from external app like ColorSniffer // then use that colors if (BuildConfig.enableColorSniffer) { if (DbUtils.isExternalSourceColor() && color == DbUtils.NULL_TEXT_COLOR) { color = DbUtils.getAppColorExternalSource(activity); } } else if (DbUtils.isRandomColor() && color == DbUtils.NULL_TEXT_COLOR) { color = Utils.generateColorFromString(appName); } int openingCounts = DbUtils.getOpeningCounts(activity); int updateTime = 0; try { //ApplicationInfo appInfo = pm.getApplicationInfo(packageName, 0); long time = pm.getPackageInfo(packageName, 0).lastUpdateTime; time = time / 10000; updateTime = (int) time; } catch (PackageManager.NameNotFoundException e) { e.printStackTrace(); updateTime = 0; } // save all and add this is to app list mAppsList.add(new Apps(false, activity, appName, getCustomView(), color, textSize, hide, freeze, openingCounts, updateTime)); } // now adds Shortcut // shortcut are stored in DB android system doesn't store them ArrayList<Shortcut> shortcuts = ShortcutUtils.getAllShortcuts(); if (shortcuts != null) { for (Shortcut s : shortcuts) { // shortcut only have URI String uri = s.getUri(); // shortcut name String sName = s.getName(); /* if (uri.isEmpty()) { ++installedShortcut; continue; }*/ // Log.d(TAG, "loadApps: shortcut name==" + sName); // Log.d(TAG, "loadApps: shortcut uri==" + uri); // this is the unique code for each uri // let store them in activity field app APP POJO // As we have to store some uniquely identified info in Db // this be used as key as i have done for Each apps(see above) // Usually URI sting is too long and so it will take more memory and storage String sActivity = String.valueOf(Utils.hash(uri)); // get color and size for this shortcut int sColor = DbUtils.getAppColor(sActivity); int sSize = DbUtils.getAppSize(sActivity); if (sSize == DbUtils.NULL_TEXT_SIZE) { sSize = DEFAUTL_TEXT_SIZE_NORMAL_APPS; } if (sColor == DbUtils.NULL_TEXT_COLOR) { if (DbUtils.isRandomColor()) { sColor = Utils.generateColorFromString(sName); } else { sColor = DbUtils.getAppsColorDefault(); } } boolean sFreeze = DbUtils.isAppFrozen(sActivity); int sOpeningCount = DbUtils.getOpeningCounts(sActivity); // add this shortcut to list // currently shortcut hide is disabled mAppsList.add(new Apps(true, uri, sName, getCustomView(), sColor, sSize, false, sFreeze, sOpeningCount, 0)); } } // now sort the app list // and display this sortApps(DbUtils.getSortsTypes()); } /** * @param type sorting type */ public void sortApps(final int type) { new SortTask().execute(type); } // the text view and set the various parameters //TODO: new animated field for this(test randomly) private AppTextView getCustomView() { // AnimatedTextView textView=new AnimatedTextView(this); // textView.setColorSpace(15); AppTextView textView = new AppTextView(this); textView.setOnClickListener(this); textView.setOnLongClickListener(this); textView.setPadding(10, 0, 4, -2); textView.setTypeface(mTypeface); return textView; } // app text is clicked // so launch the app @Override public void onClick(View view) { if (view instanceof AppTextView) { // get the activity String activity = (String) view.getTag(); AppTextView appTextView = (AppTextView) view; if (searching) { InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); imm.hideSoftInputFromWindow(mSearchBox.getWindowToken(), 0); mSearchBox.setVisibility(View.GONE); } if (appTextView.isShortcut()) { try { Intent intent = Intent.parseUri(appTextView.getUri(), 0); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); startActivity(intent); overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out); appOpened(activity); } catch (Exception e) { e.printStackTrace(); } } else { //Notes to me:if view store package and component name then this could reduce this splits String[] strings = activity.split("/"); try { final Intent intent = new Intent(Intent.ACTION_MAIN, null); intent.setClassName(strings[0], strings[1]); intent.setComponent(new ComponentName(strings[0], strings[1])); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); startActivity(intent); overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out); // tell the our db that app is opened appOpened(activity); } catch (Exception ignore) { // Log.e(TAG, "onClick: exception:::" + ignore); } } } } @Override protected void onResume() { super.onResume(); if (searching) { mSearchBox.setVisibility(View.GONE); searching = false; imm.hideSoftInputFromWindow(mSearchBox.getWindowToken(), 0); mHomeLayout.setPadding(DbUtils.getPaddingLeft(), DbUtils.getPaddingTop(), DbUtils.getPaddingRight(), DbUtils.getPaddingBottom()); sortApps(DbUtils.getSortsTypes()); } } //show the option on long click @Override public boolean onLongClick(View view) { if (view instanceof AppTextView) { // show app setting showPopup((String) view.getTag(), (AppTextView) view); } else if (view instanceof FlowLayout) { // show launcher setting dialogs = new GlobalSettingsDialog(this, this); dialogs.show(); } return true; } private void showPopup(String activityName, AppTextView view) { Context context; // set theme // if theme wallpaper ie transparent then we have to show other theme if (DbUtils.getTheme() == R.style.Wallpaper) context = new ContextThemeWrapper(this, R.style.AppTheme); else context = new ContextThemeWrapper(this, DbUtils.getTheme()); PopupMenu popupMenu = new PopupMenu(context, view); popupMenu.getMenuInflater().inflate(R.menu.menu, popupMenu.getMenu()); int color = Color.parseColor("#E53935"); SpannableString s = new SpannableString(getString(R.string.hide)); s.setSpan(new ForegroundColorSpan(color), 0, s.length(), 0); popupMenu.getMenu().findItem(R.id.menu_hide).setTitle(s); s = new SpannableString(getString(R.string.uninstall)); s.setSpan(new ForegroundColorSpan(color), 0, s.length(), 0); popupMenu.getMenu().findItem(R.id.menu_uninstall).setTitle(s); s = new SpannableString(getString(R.string.reset_to_default)); s.setSpan(new ForegroundColorSpan(color), 0, s.length(), 0); popupMenu.getMenu().findItem(R.id.menu_reset_to_default).setTitle(s); // set proper item based on Db value if (DbUtils.isAppFrozen(activityName)) { popupMenu.getMenu().findItem(R.id.menu_freeze_size).setTitle(R.string.unfreeze_size); } //disable some item for shortcut // and change the uninstall to remove if (view.isShortcut()) { SpannableString s1 = new SpannableString(getString(R.string.remove)); s1.setSpan(new ForegroundColorSpan(Color.parseColor("#E53935")), 0, s1.length(), 0); popupMenu.getMenu().findItem(R.id.menu_uninstall).setTitle(s1); popupMenu.getMenu().findItem(R.id.menu_hide).setVisible(false); //renaming is also disabled; // consider it later popupMenu.getMenu().findItem(R.id.menu_rename).setVisible(false); popupMenu.getMenu().findItem(R.id.menu_app_info).setVisible(false); } popupMenu.setOnMenuItemClickListener(menuItem -> { switch (menuItem.getItemId()) { case R.id.menu_color: changeColorSize(activityName, view); break; case R.id.menu_rename: renameApp(activityName, view.getText().toString()); break; case R.id.menu_freeze_size: freezeAppSize(activityName); break; case R.id.menu_hide: hideApp(activityName); break; case R.id.menu_uninstall: if (view.isShortcut()) { removeShortcut(view); } else { uninstallApp(activityName); } break; case R.id.menu_app_info: showAppInfo(activityName); break; case R.id.menu_reset_to_default: resetApp(activityName); break; case R.id.menu_reset_color: resetAppColor(activityName); break; default: return true; } return true; }); // not forget to show popup popupMenu.show(); } //reset the app color to default color; private void resetAppColor(String activityName) { DbUtils.removeColor(activityName); boolean sortNeeded = (DbUtils.getSortsTypes() == SORT_BY_COLOR); addAppAfterReset(activityName, sortNeeded); } // add a new app: generally called after reset private void addAppAfterReset(String activityName, boolean sortNeeded) { for (Apps apps : mAppsList) { if (apps.getActivityName().equalsIgnoreCase(activityName)) { mAppsList.remove(apps); //now add new App int color; if (DbUtils.isRandomColor()) { color = Utils.generateColorFromString(activityName); } else { color = DbUtils.getAppsColorDefault(); } String appOriginalName = DbUtils.getAppOriginalName(activityName, ""); String appName = DbUtils.getAppName(activityName, appOriginalName); int openingCounts = DbUtils.getOpeningCounts(activityName); boolean hide = apps.isHidden(); boolean freezeSize = apps.isSizeFrozen(); int appUpdateTime = apps.getUpdateTime(); Apps newApp = new Apps(apps.isShortcut(), activityName, appName, getCustomView(), color, DEFAUTL_TEXT_SIZE_NORMAL_APPS, hide, freezeSize, openingCounts, appUpdateTime); mAppsList.add(newApp); if (sortNeeded) sortApps(DbUtils.getSortsTypes()); break; } } } // as method name suggest private void freezeAppSize(String activityName) { boolean b = DbUtils.isAppFrozen(activityName); for (Apps apps : mAppsList) { if (activityName.equalsIgnoreCase(apps.getActivityName())) { apps.setFreeze(!b); } } } // as method name suggest private void hideApp(String activityName) { for (Apps apps : mAppsList) { if (activityName.equalsIgnoreCase(apps.getActivityName())) { apps.setAppHidden(true); } } } // show the app rename Dialog private void renameApp(String activityName, String appName) { dialogs = new RenameInputDialogs(this, activityName, appName, this); Window window = dialogs.getWindow(); if (window != null) { window.setGravity(Gravity.BOTTOM); window.setBackgroundDrawableResource(android.R.color.transparent); window.setLayout(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT); } dialogs.show(); } // this is called by RenameInput.class Dialog when user set the name and sort the apps public void onAppRenamed(String activityName, String appNewName) { for (Apps app : mAppsList) { if (app.getActivityName().equalsIgnoreCase(activityName)) { app.setAppName(appNewName.trim()); if (SORT_BY_NAME == DbUtils.getSortsTypes()) { sortApps(SORT_BY_NAME); } break; } } } // reset the app private void resetApp(String activityName) { DbUtils.removeAppName(activityName); DbUtils.removeColor(activityName); DbUtils.removeSize(activityName); addAppAfterReset(activityName, true); } private void showAppInfo(String activityName) { Intent intent = new Intent(android.provider.Settings.ACTION_APPLICATION_DETAILS_SETTINGS); intent.setData(Uri.parse("package:" + activityName.split("/")[0])); startActivity(intent); } private void uninstallApp(String activityName) { Intent intent = new Intent(Intent.ACTION_UNINSTALL_PACKAGE); intent.setData(Uri.parse("package:" + activityName.split("/")[0])); intent.putExtra(Intent.EXTRA_RETURN_RESULT, true); startActivityForResult(intent, 97); } //show dialog(i.e a color seek bar) for change color private void changeColorSize(String activityName, TextView view) { int color = DbUtils.getAppColor(activityName); if (color == DbUtils.NULL_TEXT_COLOR) { color = view.getCurrentTextColor(); } int size = DbUtils.getAppSize(activityName); if (size == DbUtils.NULL_TEXT_SIZE) { for (Apps apps : mAppsList) { if (apps.getActivityName().equals(activityName)) { size = apps.getSize(); break; } } } dialogs = new ColorSizeDialog(this, activityName, color, view, size); Window window = dialogs.getWindow(); if (window != null) { window.setGravity(Gravity.BOTTOM); window.setBackgroundDrawableResource(android.R.color.transparent); window.setLayout(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT); } dialogs.show(); } //TO1DO: multi thread check for memory leaks if any, or check any bad behaviour; private void appOpened(String activity) { /* new Thread() { @Override public void run() { super.run();*/ for (Apps apps : mAppsList) { if (apps.getActivityName().equalsIgnoreCase(activity)) { apps.increaseOpeningCounts();// save to Db that app is opened by user recentlyUsedCounter++; apps.setRecentUsedWeight(recentlyUsedCounter); if (DbUtils.getSortsTypes() == SORT_BY_OPENING_COUNTS) { int counter = apps.getOpeningCounts(); if (counter % 5 == 0) { sortApps(SORT_BY_OPENING_COUNTS); } } else if (DbUtils.getSortsTypes() == SORT_BY_RECENT_OPEN) { sortApps(SORT_BY_RECENT_OPEN); } // increase the app view size if not frozen if (!DbUtils.isSizeFrozen() && !DbUtils.isAppFrozen(activity)) { int size = DbUtils.getAppSize(activity); size += 2; apps.setSize(size); if (DbUtils.getSortsTypes() == SORT_BY_SIZE) { sortApps(SORT_BY_SIZE); } } break; } } /* } }.start();*/ } @Override public void onBackPressed() { mSearchBox.setVisibility(View.GONE); if (searching) { searching = false; mHomeLayout.setPadding(DbUtils.getPaddingLeft(), DbUtils.getPaddingTop(), DbUtils.getPaddingRight(), DbUtils.getPaddingBottom()); sortApps(DbUtils.getSortsTypes()); } } // register the receiver // when new app installed, app updated and app uninstalled launcher have to reflect it private void registerForReceivers() { /*if (broadcastReceiverShortcutInstall!=null){ unregisterReceiver(broadcastReceiverShortcutInstall); } if (broadcastReceiverAppInstall!=null){ unregisterReceiver(broadcastReceiverAppInstall); }*/ //app install and uninstall receiver // Log.d("WTF", "registerForReceivers: called "); IntentFilter intentFilter = new IntentFilter(); intentFilter.addAction(ACTION_PACKAGE_ADDED); intentFilter.addAction(ACTION_PACKAGE_REMOVED); intentFilter.addAction(ACTION_PACKAGE_REPLACED); intentFilter.addDataScheme("package"); if (broadcastReceiverAppInstall == null) { broadcastReceiverAppInstall = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { loadApps(); } }; registerReceiver(broadcastReceiverAppInstall, intentFilter); } //shortcut install receiver IntentFilter filter = new IntentFilter(); filter.addAction("com.android.launcher.action.INSTALL_SHORTCUT"); filter.addAction("com.android.launcher.action.CREATE_SHORTCUT"); if (broadcastReceiverShortcutInstall == null) { broadcastReceiverShortcutInstall = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { Intent shortcutIntent = intent.getParcelableExtra(Intent.EXTRA_SHORTCUT_INTENT); String uri = shortcutIntent.toUri(0); if (shortcutIntent.getAction() == null) { shortcutIntent.setAction(Intent.ACTION_VIEW); } String name = intent.getStringExtra(Intent.EXTRA_SHORTCUT_NAME); //Log.d(TAG, "onReceive: name::" + name); if (ShortcutUtils.isShortcutToApp(uri)) { return; } if (!ShortcutUtils.isShortcutAlreadyAvailable(uri)) { addShortcut(uri, name); } } }; registerReceiver(broadcastReceiverShortcutInstall, filter); } } // unregister the receivers on destroy @Override protected void onDestroy() { super.onDestroy(); if (dialogs != null) { dialogs.dismiss(); dialogs = null; } unregisterReceiver(broadcastReceiverAppInstall); unregisterReceiver(broadcastReceiverShortcutInstall); broadcastReceiverAppInstall = null; broadcastReceiverShortcutInstall = null; } // request storage permission public void requestPermission() { if (Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) { requestPermissions( new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.READ_EXTERNAL_STORAGE}, PERMISSION_REQUEST ); } } @Override public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) { super.onRequestPermissionsResult(requestCode, permissions, grantResults); if (requestCode == 148) { if (grantResults[0] == PackageManager.PERMISSION_GRANTED) { DbUtils.permissionRequired(false); } } } public boolean isPermissionRequired() { if (Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) { return checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED; } return true; } // browse the backup file public void browseFile() { Intent chooseFile; // if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.KITKAT) { // chooseFile = new Intent(Intent.ACTION_OPEN_DOCUMENT); //}else { chooseFile = new Intent(Intent.ACTION_GET_CONTENT); // } chooseFile.addCategory(Intent.CATEGORY_OPENABLE); //chooseFile.setType("application/x-font-ttf"); chooseFile.setType("file/plain"); Intent intent = Intent.createChooser(chooseFile, this.getString(R.string.choose_old_backup_files)); startActivityForResult(intent, RESTORE_REQUEST); } // browse the fonts public void browseFonts() { if (isPermissionRequired()) { requestPermission(); } Intent chooseFile; // if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.KITKAT) { // chooseFile = new Intent(Intent.ACTION_OPEN_DOCUMENT); // }else { chooseFile = new Intent(Intent.ACTION_GET_CONTENT); //} chooseFile.addCategory(Intent.CATEGORY_OPENABLE); chooseFile.setType("application/x-font-ttf"); //chooseFile.setType("file/plain"); Intent intent = Intent.createChooser(chooseFile, "Choose Fonts"); startActivityForResult(intent, FONTS_REQUEST); } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); if (resultCode != Activity.RESULT_OK) return; // restore request if (requestCode == RESTORE_REQUEST) { Uri uri = data.getData(); ContentResolver cr = getContentResolver(); try { boolean b = DbUtils.loadDbFromFile(cr.openInputStream(uri)); if (b) { recreate(); } } catch (FileNotFoundException e) { e.printStackTrace(); } // font request } else if (requestCode == FONTS_REQUEST) { try { String[] proj = {MediaStore.Images.Media.DATA}; Cursor cursor = getContentResolver().query(data.getData(), proj, null, null, null); int column_index = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA); cursor.moveToFirst(); String path = cursor.getString(column_index); cursor.close(); //Log.i(TAG, "onActivityResult: " + path); mTypeface = Typeface.createFromFile(path); DbUtils.setFonts(path); loadApps(); } catch (Exception i) { //i.printStackTrace(); mTypeface = Typeface.createFromAsset(getAssets(), "fonts/raleway_bold.ttf"); } // this handle the request of ColorSniffer app } else if (requestCode == COLOR_SNIFFER_REQUEST) { // //GET DATA FROM COLOR SNIFFER APPS: //K,V ??? no // bundle yes // is it complex: may be //get the data colorSnifferCall(data.getBundleExtra("color_bundle")); } } //may be override of abstract class method to be called from color sniffer #3 types public void colorSnifferCall(Bundle bundle) { boolean defaultColorSet = false;// for change set int DEFAULT_COLOR = bundle.getInt(DEFAULT_COLOR_FOR_APPS);//keys // not set by ColorSniffer if (DEFAULT_COLOR != DbUtils.NULL_TEXT_COLOR) { //NULL_TEXT_COLOR=-1 defaultColorSet = true;// to save cpu cycle } // get each value as proposed by Color Sniffer App developer for (Apps apps : mAppsList) { TextView textView = apps.getTextView(); String appPackage = apps.getActivityName(); int color = bundle.getInt(appPackage); if (color != DbUtils.NULL_TEXT_COLOR) { textView.setTextColor(color); DbUtils.putAppColorExternalSource(appPackage, color); // DbUtils.putAppColor(appPackage, color); } else if (defaultColorSet) { //set default color DbUtils.putAppColor(appPackage, DEFAULT_COLOR); textView.setTextColor(DEFAULT_COLOR); }//else do nothing theme default color will apply } } //Clipboard manager public Map<String, Integer> clipboardData() { Map<String, Integer> result = null; // Log.d(TAG, "clipboardData: "); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { try { ClipboardManager clipboardManager = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE); ClipData clipData = clipboardManager.getPrimaryClip(); if (clipData.getItemCount() > 0) { ClipData.Item item = clipData.getItemAt(0); String tabSepratedData = item.getText().toString(); //Log.d(TAG, "clipboardData: " + tabSepratedData); //validate tabSepratedData and get its data //unique id bae73ae068dacc6cb659d1fb231e7b11 i.e LastLauncher-ColorSniffer MD5-128 String[] line = tabSepratedData.split("\n");//get each line Map<String, Integer> colorsAndId = new ArrayMap<>(); // map to put all values in key and values format // iterate over every line for (String entry : line) { String[] activityIdAndColor = entry.split("\t");// split line into id and color int color = Color.parseColor(activityIdAndColor[1]); colorsAndId.put(activityIdAndColor[0], color);// put id and color to map // Log.d(TAG, "clipboardData: app:" + activityIdAndColor[0] + " color==" + color); } setAppsColorFromClipboard(colorsAndId); result = colorsAndId;// return map } } catch (Exception e) { e.printStackTrace(); } } // return empty null/ return result; } private void setAppsColorFromClipboard(Map<String, Integer> colorsAndId) { if (colorsAndId == null) return; DbUtils.externalSourceColor(true); for (Apps apps : mAppsList) { try { TextView textView = apps.getTextView(); String s = apps.getActivityName().toString(); Integer newColor = colorsAndId.get(s); if (newColor == null) continue; textView.setTextColor(newColor); DbUtils.putAppColorExternalSource(s, newColor); } catch (NullPointerException ignore) { } } } // show the hidden app dialog public void showHiddenApps() { dialogs = new HiddenAppsDialogs(this, mAppsList); dialogs.show(); } // show the frozen app dialog public void showFrozenApps() { dialogs = new FrozenAppsDialogs(this, mAppsList); dialogs.show(); } //set the flow layout alignment it is called from global settings public void setFlowLayoutAlignment(int gravity) { mHomeLayout.setGravity(gravity); DbUtils.setFlowLayoutAlignment(gravity); } public void setPadding() { dialogs = new PaddingDialog(this, mHomeLayout); // Window window = dialogs.getWindow(); // window.setGravity(Gravity.BOTTOM); // window.setBackgroundDrawableResource(android.R.color.transparent); // window.setLayout(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT); dialogs.show(); } public void setColorsAndSize() { dialogs = new GlobalColorSizeDialog(this, mAppsList); Window window = dialogs.getWindow(); if (window != null) { window.setGravity(Gravity.BOTTOM); window.setBackgroundDrawableResource(android.R.color.transparent); window.setLayout(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT); } dialogs.show(); } private void addShortcut(String uri, String appName) { if (mAppsList == null) return; mAppsList.add(new Apps(true, uri, appName, getCustomView(), DbUtils.NULL_TEXT_COLOR, DEFAUTL_TEXT_SIZE_NORMAL_APPS, false, false, 0, (int) System.currentTimeMillis() / 1000)); ShortcutUtils.addShortcut(new Shortcut(appName, uri)); // Log.d(TAG, "addShortcut: shortcut name==" + appName); sortApps(DbUtils.getSortsTypes()); } /** * remove the shortcut * * @param view shortcut view to be removed... */ private void removeShortcut(AppTextView view) { // view.setVisibility(View.GONE); boolean b = ShortcutUtils.removeShortcut(new Shortcut(view.getText().toString(), view.getUri())); // Log.d(TAG, "removeShortcut: boolene" + b); if (b) loadApps(); } @Override public void onSwipe(Gestures.Direction direction) { if (direction == Gestures.Direction.SWIPE_UP) { searching = true; mSearchBox.setText(""); mSearchBox.setVisibility(View.VISIBLE); mSearchBox.requestFocus(); imm.showSoftInput(mSearchBox, InputMethodManager.SHOW_IMPLICIT); } else if (direction == Gestures.Direction.SWIPE_DOWN) { if (searching) { mSearchBox.setVisibility(View.GONE); imm.hideSoftInputFromWindow(mSearchBox.getWindowToken(), 0); onResume(); } } } @Override public void onDoubleTap() { } static class SearchTask extends AsyncTask<CharSequence, Void, ArrayList<Apps>> { @Override protected void onPostExecute(ArrayList<Apps> filteredApps) { super.onPostExecute(filteredApps); showSearchResult(filteredApps); } @Override protected ArrayList<Apps> doInBackground(CharSequence... charSequences) { ArrayList<Apps> filteredApps = new ArrayList<>(); for (Apps app : mAppsList) { if (charSequences[0].length() == 0) { filteredApps.add(app); } else if (Utils.simpleFuzzySearch(charSequences[0], app.getAppName())) { filteredApps.add(app); } } return filteredApps; } } static class SortTask extends AsyncTask<Integer, Void, Void> { @Override protected void onPostExecute(Void aVoid) { super.onPostExecute(aVoid); mHomeLayout.removeAllViews(); // now add the app textView to home // FlowLayoutManager.LayoutParams params = new FlowLayoutManager.LayoutParams(LayoutParams.WRAP_CONTENT,LayoutParams.WRAP_CONTENT); // params.setNewLine(true); for (Apps apps : mAppsList) { mHomeLayout.addView(apps.getTextView(), new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT)); } } @Override protected Void doInBackground(Integer... integers) { int type = integers[0]; DbUtils.setAppsSortsType(type); //sort the apps alphabetically Collections.sort(mAppsList, (a, b) -> String.CASE_INSENSITIVE_ORDER.compare( a.getAppName(), b.getAppName() )); switch (type) { case SORT_BY_SIZE://descending Collections.sort(mAppsList, (apps, t1) -> t1.getSize() - apps.getSize()); break; case SORT_BY_OPENING_COUNTS://descending Collections.sort(mAppsList, (apps, t1) -> t1.getOpeningCounts() - apps.getOpeningCounts()); break; case SORT_BY_COLOR: Collections.sort(mAppsList, (apps, t1) -> { float[] hsv = new float[3]; Color.colorToHSV(apps.getColor(), hsv); float[] another = new float[3]; Color.colorToHSV(t1.getColor(), another); for (int i = 0; i < 3; i++) { if (hsv[i] != another[i]) { return (hsv[i] < another[i]) ? -1 : 1; } } return 0; }); break; case SORT_BY_UPDATE_TIME://descending Collections.sort(mAppsList, (apps, t1) -> (int) (t1.getUpdateTime() - apps.getUpdateTime())); break; case SORT_BY_RECENT_OPEN://descending Collections.sort(mAppsList, (apps, t1) -> (t1.getRecentUsedWeight() - apps.getRecentUsedWeight())); break; } return null; } } }