From bcce4b8ff2f0be89d04a4df0c279f8e139a52e9e Mon Sep 17 00:00:00 2001 From: Sebastian Date: Thu, 7 Sep 2017 23:26:29 +0200 Subject: [PATCH] Redesign the app (un)install receiver This change is aimed at improving the mechanisms for refreshing the app list as before they showed the following, numerous flaws: - Failing on Android Oreo - No automatic data refresh after a system language change - Icons are never refreshed - Always running a full, slow refresh - Manual refreshing for troubleshooting is cumbersome --- android/src/main/AndroidManifest.xml | 11 +- android/src/main/java/org/ligi/fast/App.java | 9 + .../AppInstallOrRemoveReceiver.java | 210 +++++++++++++++++- .../background/BackgroundGatherAsyncTask.java | 14 +- .../background/BaseAppGatherAsyncTask.java | 121 +++++----- .../background/ChangedPackagesAsyncTask.java | 159 +++++++++++++ .../fast/background/LocaleChangeReceiver.java | 19 ++ .../org/ligi/fast/model/AppIconCache.java | 18 +- .../java/org/ligi/fast/model/AppInfo.java | 71 +++--- .../ligi/fast/model/DynamicAppInfoList.java | 49 ++-- .../fast/settings/AndroidFASTSettings.java | 32 +++ .../org/ligi/fast/settings/FASTSettings.java | 10 + .../ligi/fast/ui/FASTSettingsActivity.java | 6 + .../java/org/ligi/fast/ui/LoadingDialog.java | 2 +- .../java/org/ligi/fast/ui/SearchActivity.java | 108 ++++++--- .../ligi/fast/ui/SetLabelDialogBuilder.java | 2 +- .../src/noExtras/res/layout/main_title.xml | 61 +++-- .../src/noExtras/res/layout/prefs_title.xml | 70 +++--- .../src/withExtras/res/layout/main_title.xml | 79 +++---- .../src/withExtras/res/layout/prefs_title.xml | 88 ++++---- 20 files changed, 817 insertions(+), 322 deletions(-) create mode 100644 android/src/main/java/org/ligi/fast/background/ChangedPackagesAsyncTask.java create mode 100644 android/src/main/java/org/ligi/fast/background/LocaleChangeReceiver.java diff --git a/android/src/main/AndroidManifest.xml b/android/src/main/AndroidManifest.xml index a8127b9d..6fdb4087 100644 --- a/android/src/main/AndroidManifest.xml +++ b/android/src/main/AndroidManifest.xml @@ -70,15 +70,18 @@ - - - - + + + + + + + diff --git a/android/src/main/java/org/ligi/fast/App.java b/android/src/main/java/org/ligi/fast/App.java index a7de96b6..a47080c4 100644 --- a/android/src/main/java/org/ligi/fast/App.java +++ b/android/src/main/java/org/ligi/fast/App.java @@ -2,13 +2,19 @@ import android.app.Activity; import android.app.Application; +import android.content.pm.ChangedPackages; +import android.os.Build; +import android.util.Log; +import org.ligi.fast.background.BackgroundGatherAsyncTask; +import org.ligi.fast.background.ChangedPackagesAsyncTask; import org.ligi.fast.model.AppInfoList; import org.ligi.fast.settings.AndroidFASTSettings; import org.ligi.fast.settings.FASTSettings; import org.ligi.tracedroid.TraceDroid; import java.io.File; +import java.lang.ref.WeakReference; public class App extends Application { @@ -22,6 +28,7 @@ public interface PackageChangedListener { } public static PackageChangedListener packageChangedListener; + public static WeakReference backingAppInfoList; @Override public void onCreate() { @@ -29,6 +36,8 @@ public void onCreate() { appInstance = this; TraceDroid.init(this); settings = new AndroidFASTSettings(App.this); + + Log.d(LOG_TAG, "onCreate"); } public static FASTSettings getSettings() { diff --git a/android/src/main/java/org/ligi/fast/background/AppInstallOrRemoveReceiver.java b/android/src/main/java/org/ligi/fast/background/AppInstallOrRemoveReceiver.java index 3ea580b8..8ac34f6f 100644 --- a/android/src/main/java/org/ligi/fast/background/AppInstallOrRemoveReceiver.java +++ b/android/src/main/java/org/ligi/fast/background/AppInstallOrRemoveReceiver.java @@ -3,24 +3,216 @@ import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; +import android.content.pm.ResolveInfo; +import android.net.Uri; +import android.os.AsyncTask; +import android.os.Build; +import android.util.Log; + import org.ligi.fast.App; +import org.ligi.fast.model.AppInfo; import org.ligi.fast.model.AppInfoList; import org.ligi.fast.util.AppInfoListStore; +import java.util.Iterator; +import java.util.List; + +/** + * Whenever an app is installed, uninstalled or components change + * (e.g. the app disabled one of it's activities to hide it from the launcher) + * this receiver takes care of removing or updating corresponding entries + * (namely all activities and aliases) from AppInfoList and deletes their icons + * from cache to clean up when uninstalling or to cause a refresh when updating. + */ public class AppInstallOrRemoveReceiver extends BroadcastReceiver { + @Override public void onReceive(Context context, Intent intent) { - final AppInfoListStore appInfoListStore = new AppInfoListStore(context); + if (Build.VERSION.SDK_INT < 11) { + new AppInstallOrRemoveAsyncTask(context.getApplicationContext(), intent).doInBackground(new Void[1]); + } else { + final PendingResult pendingResult = goAsync(); + new AppInstallOrRemoveAsyncTask(context.getApplicationContext(), intent, pendingResult).execute(); + } + } + + private static class AppInstallOrRemoveAsyncTask extends AsyncTask { + private BroadcastReceiver.PendingResult mPendingResult; + private Context mContext; + private Intent mIntent; + private AppInfoListStore mAppInfoListStore; + + private void save(AppInfoList appInfoList) { + if (App.packageChangedListener == null) { + if (App.backingAppInfoList != null) { + AppInfoList backingList = App.backingAppInfoList.get(); + backingList.clear(); + backingList.addAll(appInfoList); + } else { + mAppInfoListStore.save(appInfoList); + } + } else { + App.packageChangedListener.onPackageChange(appInfoList); + } + } + + AppInstallOrRemoveAsyncTask(Context context, Intent intent) { + this.mContext = context; + this.mIntent = intent; + } + + AppInstallOrRemoveAsyncTask(Context context, Intent intent, BroadcastReceiver.PendingResult pendingResult) { + this(context, intent); + this.mPendingResult = pendingResult; + } + + @Override + protected Void doInBackground(Void... params) { + long start = System.currentTimeMillis(); + Uri data = mIntent.getData(); + if (data == null) return null; // This should never be the case + String packageName = data.getSchemeSpecificPart(); + String action = mIntent.getAction(); + + Log.d(App.LOG_TAG, "BroadcastReceiver: Action - " + action + "; Package - " + packageName); - if (App.packageChangedListener == null) { - App.packageChangedListener = new App.PackageChangedListener() { - @Override - public void onPackageChange(AppInfoList appInfoList) { - appInfoListStore.save(appInfoList); + //TODO: When moving to MinApiLevel 14 or higher, refactor Intent.ACTION_PACKAGE_REMOVED + // to Intent.ACTION_PACKAGE_FULLY_REMOVED and remove the following block + + // If this is the removal Broadcast during an upgrade don't to do anything. + // Data will be updated in the next invocation by the package added Broadcast. + // This prevents running the update twice. + // getBooleanExtra defaultValue is false so that this is not also tripped + // in case of a full uninstall where this extra is missing. + // NOTE: This is only due to compatibility below API level 14 + // When using the Intent.ACTION_PACKAGE_FULLY_REMOVED instead of + // Intent.ACTION_PACKAGE_REMOVED this is not necessary + boolean replacingDefaultFalse = mIntent.getBooleanExtra(Intent.EXTRA_REPLACING, false); + if (Intent.ACTION_PACKAGE_REMOVED.equals(action) && replacingDefaultFalse) return null; + + mAppInfoListStore = new AppInfoListStore(mContext); + AppInfoList appInfoList; + if (App.backingAppInfoList != null) { + appInfoList = new AppInfoList(); + appInfoList.addAll(App.backingAppInfoList.get()); + if (appInfoList.size() == 0) { + appInfoList = mAppInfoListStore.load(); + } + } else { + appInfoList = mAppInfoListStore.load(); + } + + // Check the package is newly installed or not + // getBooleanExtra defaultValue is true so that in case of doubt the + // presence of old information is checked anyway + boolean replacingDefaultTrue = mIntent.getBooleanExtra(Intent.EXTRA_REPLACING, true); + boolean newInstall = Intent.ACTION_PACKAGE_ADDED.equals(action) && !replacingDefaultTrue; + AppInfoList matchedAppInfoList = new AppInfoList(); + // If this is not a new install, i.e. update or uninstall, then collect the existing records + // already held about this app into matchedAppInfoList and remove them from the main list. + // That way if uninstalling an app the main list is already updated after this step. + // If it is an update only matchedAppInfoList will have to be iterated in the next step + // since it contains all records that need an update. + // After this all old info & icons of the affected package should be cleaned up. + if (!newInstall) { + for (Iterator iterator = appInfoList.iterator(); iterator.hasNext(); ) { + AppInfo appInfo = iterator.next(); + if (appInfo.getPackageName().equals(packageName)) { + matchedAppInfoList.add(appInfo); + iterator.remove(); + // Delete the current icon to force a refresh + //File icon = appInfo.getIconCacheFile(); + //icon.delete(); + } + } + // Just to be sure; If this is the case there is no old information + // to update and new information can simply be added in the next step + newInstall = matchedAppInfoList.size() == 0; + } + + // If not uninstalled go on to update/add new information + // Otherwise everything is done and the list just has to be saved + if (!Intent.ACTION_PACKAGE_REMOVED.equals(action)) { + Intent launcherIntent = new Intent(Intent.ACTION_MAIN); + launcherIntent.addCategory(Intent.CATEGORY_LAUNCHER); + launcherIntent.setPackage(packageName); + List resolveInfoList = mContext.getPackageManager().queryIntentActivities(launcherIntent, 0); + + Intent homeIntent = new Intent(Intent.ACTION_MAIN); + homeIntent.addCategory(Intent.CATEGORY_HOME); + homeIntent.setPackage(packageName); + List homeInfoList = mContext.getPackageManager().queryIntentActivities(homeIntent, 0); + + // If there are no activities that should be displayed on the launcher we can quit here + if (resolveInfoList.size() == 0 && homeInfoList.size() == 0) { + save(appInfoList); + long end = System.currentTimeMillis(); + long duration = end - start; + Log.d(App.LOG_TAG, "BroadcastReceiver ran short " + duration + "ms."); + return null; } - }; + + // Deduplicate Resolve Info of activities with both categories - like SearchActivity (see manifest) + for (ResolveInfo info : resolveInfoList) { + Iterator homeIterator = homeInfoList.iterator(); + while (homeIterator.hasNext()) { + ResolveInfo homeInfo = homeIterator.next(); + if (homeInfo.activityInfo.name.equals(info.activityInfo.name)) { + homeIterator.remove(); + break; + } + } + if (!homeIterator.hasNext()) { + break; + } + } + resolveInfoList.addAll(homeInfoList); + + if (newInstall) { // New app, simple adding + for (ResolveInfo info : resolveInfoList) { + appInfoList.add(new AppInfo(mContext, info)); + } + } else { // Update, merge data + for (ResolveInfo info : resolveInfoList) { + AppInfo newAppInfo = new AppInfo(mContext, info); + + Iterator oldInfoIterator = matchedAppInfoList.iterator(); + while (oldInfoIterator.hasNext()) { + AppInfo oldInfo = oldInfoIterator.next(); + if (oldInfo.getActivityName().equals(newAppInfo.getActivityName())) { + if (oldInfo.getLabelMode() == 2) { // AppInfo is alias + oldInfo.setLabel(newAppInfo.getLabel()); + oldInfo.setInstallTime(newAppInfo.getInstallTime()); + appInfoList.add(oldInfo); + } else { + newAppInfo.setCallCount(oldInfo.getCallCount()); + newAppInfo.setPinMode(oldInfo.getPinMode()); + newAppInfo.setLabelMode(oldInfo.getLabelMode()); + newAppInfo.setOverrideLabel(oldInfo.getOverrideLabel()); + } + oldInfoIterator.remove(); + } + } + appInfoList.add(newAppInfo); + } + } + } + + save(appInfoList); + mContext = null; + long end = System.currentTimeMillis(); + long duration = end - start; + Log.d(App.LOG_TAG, "BroadcastReceiver ran " + duration + "ms."); + return null; } - new BackgroundGatherAsyncTask(context, appInfoListStore.load()).execute(); + @Override + protected void onPostExecute(Void aVoid) { + super.onPostExecute(aVoid); + if (Build.VERSION.SDK_INT >= 11) { + // Must call finish() so the BroadcastReceiver can be recycled. + mPendingResult.finish(); + } + } } -} +} \ No newline at end of file diff --git a/android/src/main/java/org/ligi/fast/background/BackgroundGatherAsyncTask.java b/android/src/main/java/org/ligi/fast/background/BackgroundGatherAsyncTask.java index 9be3198b..eb7e8b0a 100644 --- a/android/src/main/java/org/ligi/fast/background/BackgroundGatherAsyncTask.java +++ b/android/src/main/java/org/ligi/fast/background/BackgroundGatherAsyncTask.java @@ -4,15 +4,15 @@ import org.ligi.fast.App; import org.ligi.fast.model.AppInfo; -import org.ligi.fast.model.AppInfoList; import org.ligi.fast.util.AppInfoListStore; -import java.util.List; - public class BackgroundGatherAsyncTask extends BaseAppGatherAsyncTask { - public BackgroundGatherAsyncTask(Context context, AppInfoList oldAppInfoList) { - super(context, oldAppInfoList); + private Context context; + + public BackgroundGatherAsyncTask(Context context) { + super(context); + this.context = context; } @Override @@ -25,7 +25,9 @@ protected void onPostExecute(Void result) { super.onPostExecute(result); if (App.packageChangedListener != null) { App.packageChangedListener.onPackageChange(appInfoList); + } else { + new AppInfoListStore(context).save(appInfoList); } + context = null; } - } diff --git a/android/src/main/java/org/ligi/fast/background/BaseAppGatherAsyncTask.java b/android/src/main/java/org/ligi/fast/background/BaseAppGatherAsyncTask.java index 65a9e34b..dd01ca6b 100644 --- a/android/src/main/java/org/ligi/fast/background/BaseAppGatherAsyncTask.java +++ b/android/src/main/java/org/ligi/fast/background/BaseAppGatherAsyncTask.java @@ -4,81 +4,102 @@ import android.content.Intent; import android.content.pm.ResolveInfo; import android.os.AsyncTask; +import android.util.Log; +import org.ligi.fast.App; import org.ligi.fast.model.AppInfo; import org.ligi.fast.model.AppInfoList; -import org.ligi.tracedroid.logging.Log; +import org.ligi.fast.util.AppInfoListStore; -import java.util.ArrayList; +import java.util.Iterator; import java.util.List; /** * Async-Task to Retrieve / Store Application Info needed by this App */ public class BaseAppGatherAsyncTask extends AsyncTask { - private final Context ctx; + private Context ctx; + private AppInfoList oldAppList; protected int appCount; protected AppInfoList appInfoList; - private final AppInfoList oldAppList; - public BaseAppGatherAsyncTask(Context ctx) { - this(ctx, null); - } - - public BaseAppGatherAsyncTask(Context ctx, AppInfoList oldAppList) { + protected BaseAppGatherAsyncTask(Context ctx) { this.ctx = ctx; - appInfoList = new AppInfoList(); - this.oldAppList = oldAppList; } - private void processCategory(final String category) { - final Intent mainIntent = new Intent(Intent.ACTION_MAIN, null); - mainIntent.addCategory(category); - try { - final List resolveInfoList = ctx.getPackageManager().queryIntentActivities(mainIntent, 0); - appCount += resolveInfoList.size(); - for (ResolveInfo info : resolveInfoList) { - final AppInfo actAppInfo = new AppInfo(ctx, info); + @Override + protected Void doInBackground(Void... params) { + long start = System.currentTimeMillis(); + long start_total = System.currentTimeMillis(); + Intent launcherIntent = new Intent(Intent.ACTION_MAIN); + launcherIntent.addCategory(Intent.CATEGORY_LAUNCHER); + List resolveInfoList = ctx.getPackageManager().queryIntentActivities(launcherIntent, 0); + + Intent homeIntent = new Intent(Intent.ACTION_MAIN); + homeIntent.addCategory(Intent.CATEGORY_HOME); + resolveInfoList.addAll(ctx.getPackageManager().queryIntentActivities(homeIntent, 0)); - if (!ctx.getPackageName().equals(actAppInfo.getPackageName())) { // ignore self + long now = System.currentTimeMillis(); + long duration = now - start; + Log.d(App.LOG_TAG, "AppList query ran " + duration + "ms."); - // Update call count from current index that is being used. - // This is because we may have updated the call count since the last time - // we saved the package list. An alternative would be to save the package list - // each time we leave - if (oldAppList != null) { - for(AppInfo oldInfo : oldAppList) { - if (oldInfo.getActivityName().equals(actAppInfo.getActivityName())) { - if (oldInfo.getLabelMode() == 2) { - appInfoList.add(oldInfo); - } else { - actAppInfo.setCallCount(oldInfo.getCallCount()); - actAppInfo.setPinMode(oldInfo.getPinMode()); - actAppInfo.setLabelMode(oldInfo.getLabelMode()); - actAppInfo.setOverrideLabel(oldInfo.getOverrideLabel()); - } + start = System.currentTimeMillis(); + if (App.backingAppInfoList != null) { + this.oldAppList = new AppInfoList(); + this.oldAppList.addAll(App.backingAppInfoList.get()); + } else { + this.oldAppList = new AppInfoListStore(ctx).load(); + } + appInfoList = new AppInfoList(); + appCount = resolveInfoList.size(); + now = System.currentTimeMillis(); + duration = now - start; + Log.d(App.LOG_TAG, "AppList loading ran " + duration + "ms."); + + start = System.currentTimeMillis(); + for (ResolveInfo info : resolveInfoList) { + // ignore self and Android Settings FallbackHome + if (!ctx.getPackageName().equals(info.activityInfo.packageName) + && !"com.android.settings.FallbackHome".equals(info.activityInfo.name)) { + AppInfo newAppInfo = new AppInfo(ctx, info); + + // Update call count from current index that is being used. + // This is because we may have updated the call count since the last time + // we saved the package list. An alternative would be to save the package list + // each time we leave + if (oldAppList != null) { + Iterator oldInfoIterator = oldAppList.iterator(); + while (oldInfoIterator.hasNext()) { + AppInfo oldInfo = oldInfoIterator.next(); + if (oldInfo.getActivityName().equals(newAppInfo.getActivityName())) { + if (oldInfo.getLabelMode() == 2) { // AppInfo is alias + oldInfo.setLabel(newAppInfo.getLabel()); + oldInfo.setInstallTime(newAppInfo.getInstallTime()); + appInfoList.add(oldInfo); + } else { + newAppInfo.setCallCount(oldInfo.getCallCount()); + newAppInfo.setPinMode(oldInfo.getPinMode()); + newAppInfo.setLabelMode(oldInfo.getLabelMode()); + newAppInfo.setOverrideLabel(oldInfo.getOverrideLabel()); } + oldInfoIterator.remove(); + // Can't break here anymore because of aliases + // So instead this removes entries after processing } } - appInfoList.add(actAppInfo); - publishProgress(actAppInfo); } + appInfoList.add(newAppInfo); + publishProgress(newAppInfo); } - } catch (Exception e) { - Log.d("Exception occurred when getting activities skipping...!"); } + now = System.currentTimeMillis(); + duration = now - start; + Log.d(App.LOG_TAG, "AppList merging ran " + duration + "ms."); - } - - - @Override - protected Void doInBackground(Void... params) { - // TODO the progressbar could be more exact here by first querying both - calculating the - // total app-count and then process them - but as we do not expect that much launchers we - // should be OK here - appCount=0; - processCategory(Intent.CATEGORY_LAUNCHER); - processCategory(Intent.CATEGORY_HOME); + ctx = null; + long end = System.currentTimeMillis(); + duration = end - start_total; + Log.d(App.LOG_TAG, "Full Update ran " + duration + "ms."); return null; } diff --git a/android/src/main/java/org/ligi/fast/background/ChangedPackagesAsyncTask.java b/android/src/main/java/org/ligi/fast/background/ChangedPackagesAsyncTask.java new file mode 100644 index 00000000..5bc24bc6 --- /dev/null +++ b/android/src/main/java/org/ligi/fast/background/ChangedPackagesAsyncTask.java @@ -0,0 +1,159 @@ +package org.ligi.fast.background; + +import android.annotation.TargetApi; +import android.content.Context; +import android.content.Intent; +import android.content.pm.ResolveInfo; +import android.os.AsyncTask; +import android.util.Log; + +import org.ligi.fast.App; +import org.ligi.fast.model.AppInfo; +import org.ligi.fast.model.AppInfoList; +import org.ligi.fast.util.AppInfoListStore; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +@TargetApi(26) +public class ChangedPackagesAsyncTask extends AsyncTask { + private Context mContext; + private List mChangedPackageNames; + private AppInfoListStore mAppInfoListStore; + + private void save(AppInfoList appInfoList) { + if (App.packageChangedListener == null) { + if (App.backingAppInfoList != null) { + AppInfoList backingList = App.backingAppInfoList.get(); + backingList.clear(); + backingList.addAll(appInfoList); + } else { + mAppInfoListStore.save(appInfoList); + } + } else { + App.packageChangedListener.onPackageChange(appInfoList); + } + } + + public ChangedPackagesAsyncTask(Context context, List changedPackages) { + this.mContext = context; + this.mChangedPackageNames = changedPackages; + } + + @Override + protected Void doInBackground(Void... voids) { + if (mChangedPackageNames == null) return null; // Promised to never be the case + if (mChangedPackageNames.size() == 0) return null; + long start = System.currentTimeMillis(); + + mAppInfoListStore = new AppInfoListStore(mContext); + AppInfoList appInfoList = null; + if (App.backingAppInfoList != null) { + appInfoList = new AppInfoList(); + appInfoList.addAll(App.backingAppInfoList.get()); + if (appInfoList.size() == 0) { + appInfoList = mAppInfoListStore.load(); + } + } else { + appInfoList = mAppInfoListStore.load(); + } + + AppInfoList matchedAppInfoList = new AppInfoList(); + List launcherResolveInfoList = new ArrayList<>(); + List homeResolveInfoList = new ArrayList<>(); + // Collect the existing records already held about this app + // into matchedAppInfoList and remove them from the main list. + // That way if uninstalling an app the main list is already updated after this step. + // If it is an update only matchedAppInfoList will have to be iterated in the next step + // since it contains all records that need an update. + // After this all old info & icons of the affected package should be cleaned up. + for (String packageName : mChangedPackageNames) { + Log.d(App.LOG_TAG, "changed: " + packageName); + + // ignore self + if (mContext.getPackageName().equals(packageName)) continue; + + + for (Iterator iterator = appInfoList.iterator(); iterator.hasNext(); ) { + AppInfo appInfo = iterator.next(); + if (appInfo.getPackageName().equals(packageName)) { + matchedAppInfoList.add(appInfo); + iterator.remove(); + } + } + + + Intent launcherIntent = new Intent(Intent.ACTION_MAIN); + launcherIntent.addCategory(Intent.CATEGORY_LAUNCHER); + launcherIntent.setPackage(packageName); + launcherResolveInfoList.addAll(mContext.getPackageManager().queryIntentActivities(launcherIntent, 0)); + + Intent homeIntent = new Intent(Intent.ACTION_MAIN); + homeIntent.addCategory(Intent.CATEGORY_HOME); + homeIntent.setPackage(packageName); + homeResolveInfoList.addAll(mContext.getPackageManager().queryIntentActivities(homeIntent, 0)); + } + + // If there are no activities that should be displayed on the launcher we can quit here + if (launcherResolveInfoList.size() == 0 && homeResolveInfoList.size() == 0) { + save(appInfoList); // Saving here to apply removed entries + long end = System.currentTimeMillis(); + long duration = end - start; + Log.d(App.LOG_TAG, "BroadcastReceiver ran " + duration + "ms."); + return null; + } + + // Deduplicate Resolve Info of activities with both categories - like SearchActivity (see manifest) + for (ResolveInfo info : launcherResolveInfoList) { + Iterator homeIterator = homeResolveInfoList.iterator(); + while (homeIterator.hasNext()) { + ResolveInfo homeInfo = homeIterator.next(); + if (homeInfo.activityInfo.name.equals(info.activityInfo.name)) { + homeIterator.remove(); + break; + } + } + if (!homeIterator.hasNext()) { + break; + } + } + launcherResolveInfoList.addAll(homeResolveInfoList); + + if (matchedAppInfoList.size() == 0) { // New app, simple adding + for (ResolveInfo info : launcherResolveInfoList) { + appInfoList.add(new AppInfo(mContext, info)); + } + } else { // Update, merge data + for (ResolveInfo info : launcherResolveInfoList) { + AppInfo newAppInfo = new AppInfo(mContext, info); + + Iterator oldInfoIterator = matchedAppInfoList.iterator(); + while (oldInfoIterator.hasNext()) { + AppInfo oldInfo = oldInfoIterator.next(); + if (oldInfo.getActivityName().equals(newAppInfo.getActivityName())) { + if (oldInfo.getLabelMode() == 2) { // AppInfo is alias + oldInfo.setLabel(newAppInfo.getLabel()); + oldInfo.setInstallTime(newAppInfo.getInstallTime()); + appInfoList.add(oldInfo); + } else { + newAppInfo.setCallCount(oldInfo.getCallCount()); + newAppInfo.setPinMode(oldInfo.getPinMode()); + newAppInfo.setLabelMode(oldInfo.getLabelMode()); + newAppInfo.setOverrideLabel(oldInfo.getOverrideLabel()); + } + oldInfoIterator.remove(); + } + } + appInfoList.add(newAppInfo); + } + } + + save(appInfoList); + mContext = null; + long end = System.currentTimeMillis(); + long duration = end - start; + Log.d(App.LOG_TAG, "ChangedPackages AsyncTask ran " + duration + "ms."); + return null; + } +} diff --git a/android/src/main/java/org/ligi/fast/background/LocaleChangeReceiver.java b/android/src/main/java/org/ligi/fast/background/LocaleChangeReceiver.java new file mode 100644 index 00000000..ddf8214d --- /dev/null +++ b/android/src/main/java/org/ligi/fast/background/LocaleChangeReceiver.java @@ -0,0 +1,19 @@ +package org.ligi.fast.background; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; + +import org.ligi.fast.App; +import org.ligi.fast.model.AppInfoList; +import org.ligi.fast.util.AppInfoListStore; + +/** + * Refreshes the whole AppInfoList to update labels when the user changes the system language + */ +public class LocaleChangeReceiver extends BroadcastReceiver { + @Override + public void onReceive(Context context, Intent intent) { + new BackgroundGatherAsyncTask(context.getApplicationContext()).execute(); + } +} diff --git a/android/src/main/java/org/ligi/fast/model/AppIconCache.java b/android/src/main/java/org/ligi/fast/model/AppIconCache.java index 90bef536..feae3c8e 100644 --- a/android/src/main/java/org/ligi/fast/model/AppIconCache.java +++ b/android/src/main/java/org/ligi/fast/model/AppIconCache.java @@ -73,26 +73,16 @@ class IconCacheSpec { @SuppressWarnings("ResultOfMethodCallIgnored") // as we do not care if it is new or old private boolean createIconCacheFile() throws IOException { - return getIconCacheFile().createNewFile(); - } - - private File getIconCacheFile() { - final File file = new File(App.getBaseDir() + "/" + appInfo.getHash() + ".png"); - Log.i("returning " + file.exists()); - return file; + return appInfo.getIconCacheFile().createNewFile(); } private boolean tryIconCaching(IconCacheSpec iconCacheSpec, ResolveInfo ri, PackageManager pm) { - if (getIconCacheFile().exists()) { - return true; - } - try { final Drawable icon = ri.loadIcon(pm); if (icon != null) { createIconCacheFile(); - final FileOutputStream fos = new FileOutputStream(getIconCacheFile()); + final FileOutputStream fos = new FileOutputStream(appInfo.getIconCacheFile()); final Bitmap cacheIcon = iconConverter.toScaledBitmap(icon, iconCacheSpec); cacheIcon.compress(Bitmap.CompressFormat.PNG, iconCacheSpec.quality, fos); @@ -114,12 +104,12 @@ public Drawable getIcon() { } try { - final FileInputStream fileInputStream = new FileInputStream(getIconCacheFile()); + final FileInputStream fileInputStream = new FileInputStream(appInfo.getIconCacheFile()); final BitmapDrawable drawable = new BitmapDrawable(ctx.getResources(), fileInputStream); cachedIcon = new SoftReference(drawable); return cachedIcon.get(); } catch (Exception e) { - Log.w("Could not load the cached Icon" + getIconCacheFile().getAbsolutePath() + " reason " + e); + Log.w("Could not load the cached Icon" + appInfo.getIconCacheFile().getAbsolutePath() + " reason " + e); } // if we came here we we could not return the cached Icon - ty to rescue situation diff --git a/android/src/main/java/org/ligi/fast/model/AppInfo.java b/android/src/main/java/org/ligi/fast/model/AppInfo.java index 89e8ed55..d3c3a25d 100755 --- a/android/src/main/java/org/ligi/fast/model/AppInfo.java +++ b/android/src/main/java/org/ligi/fast/model/AppInfo.java @@ -8,18 +8,22 @@ import android.content.pm.ResolveInfo; import android.graphics.drawable.Drawable; import android.os.Build; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; + import org.ligi.axt.helpers.ResolveInfoHelper; +import org.ligi.fast.App; import org.ligi.fast.util.UmlautConverter; import org.ligi.tracedroid.logging.Log; +import java.io.File; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; + /** * Class to Retrieve / Store Application Information needed by this App */ public class AppInfo { private static final String SEPARATOR = ";;"; - + private final AppIconCache iconCache; private String label; private String alternateLabel; private String overrideLabel; @@ -33,8 +37,6 @@ public class AppInfo { private int pinMode = 0; private int labelMode = 0; - private final AppIconCache iconCache; - private AppInfo(Context ctx) { iconCache = new AppIconCache(ctx, this); } @@ -79,19 +81,19 @@ public AppInfo(Context _ctx, ResolveInfo ri) { // init attributes label = new ResolveInfoHelper(ri).getLabelSafely(_ctx); label = label.replace("ά", "α") - .replaceAll("έ", "ε") - .replaceAll("ή", "η") - .replaceAll("ί", "ι") - .replaceAll("ό", "ο") - .replaceAll("ύ", "υ") - .replaceAll("ώ", "ω") - .replaceAll("Ά", "Α") - .replaceAll("Έ", "Ε") - .replaceAll("Ή", "Η") - .replaceAll("Ί", "Ι") - .replaceAll("Ό", "Ο") - .replaceAll("Ύ", "Υ") - .replaceAll("Ώ", "Ω"); + .replaceAll("έ", "ε") + .replaceAll("ή", "η") + .replaceAll("ί", "ι") + .replaceAll("ό", "ο") + .replaceAll("ύ", "υ") + .replaceAll("ώ", "ω") + .replaceAll("Ά", "Α") + .replaceAll("Έ", "Ε") + .replaceAll("Ή", "Η") + .replaceAll("Ί", "Ι") + .replaceAll("Ό", "Ο") + .replaceAll("Ύ", "Υ") + .replaceAll("Ώ", "Ω"); if (ri.activityInfo != null) { packageName = ri.activityInfo.packageName; activityName = ri.activityInfo.name; @@ -105,14 +107,14 @@ public AppInfo(Context _ctx, ResolveInfo ri) { installTime = 0; - try { - final PackageInfo pi = pmManager.getPackageInfo(packageName, 0); - if (Build.VERSION.SDK_INT >= 9) { + if (Build.VERSION.SDK_INT >= 9) { + try { + final PackageInfo pi = pmManager.getPackageInfo(packageName, 0); installTime = pi.lastUpdateTime; + } catch (NameNotFoundException e) { + // TODO Auto-generated catch block + e.printStackTrace(); } - } catch (NameNotFoundException e) { - // TODO Auto-generated catch block - e.printStackTrace(); } @@ -183,6 +185,7 @@ public String getActivityName() { /** * Please keep in mind that this might now return unexpected values + * * @return the user-set label if it is set, default otherwise */ public String getDisplayLabel() { @@ -197,16 +200,24 @@ public String getLabel() { return this.label; } + public void setLabel(String label) { + this.label = label; + } + public int getCallCount() { return callCount; } + public void setCallCount(int count) { + callCount = count; + } + public long getInstallTime() { return installTime; } - public void setCallCount(int count) { - callCount = count; + public void setInstallTime(long installTime) { + this.installTime = installTime; } public void incrementCallCount() { @@ -223,6 +234,7 @@ public String getHash() { /** * Please keep in mind that this might now return unexpected values + * * @return the user-set label if it is set, alternateLabel otherwise */ public String getAlternateDisplayLabel() { @@ -247,8 +259,7 @@ public void mergeSafe(AppInfo appInfo) { setCallCount(Math.max(localCallCount, remoteCallCount)); if (appInfo.getPinMode() != 0) { setPinMode(appInfo.getPinMode()); - } - else { + } else { setPinMode(getPinMode()); } @@ -284,4 +295,8 @@ public String getOverrideLabel() { public void setOverrideLabel(String label) { this.overrideLabel = label; } + + public File getIconCacheFile() { + return new File(App.getBaseDir() + "/" + this.hash + ".png"); + } } diff --git a/android/src/main/java/org/ligi/fast/model/DynamicAppInfoList.java b/android/src/main/java/org/ligi/fast/model/DynamicAppInfoList.java index 40e4381b..8d0245ba 100755 --- a/android/src/main/java/org/ligi/fast/model/DynamicAppInfoList.java +++ b/android/src/main/java/org/ligi/fast/model/DynamicAppInfoList.java @@ -3,8 +3,8 @@ import org.ligi.fast.settings.FASTSettings; import org.ligi.fast.util.StringUtils; -import java.util.ArrayList; import java.util.Comparator; +import java.util.Iterator; import java.util.List; import java.util.Locale; @@ -13,7 +13,7 @@ */ public class DynamicAppInfoList extends AppInfoList { - private List backingAppInfoList; + private AppInfoList backingAppInfoList; private String currentQuery = ""; private final FASTSettings settings; @@ -27,36 +27,37 @@ public enum SortMode { @SuppressWarnings("unchecked") public DynamicAppInfoList(List backingAppInfoList, FASTSettings settings) { this.settings = settings; - this.backingAppInfoList=new ArrayList<>(); + this.backingAppInfoList = new AppInfoList(); update(backingAppInfoList); } @Override public void update(List pkgAppsListAll) { - final List appsToRemove = new ArrayList<>(); - for (AppInfo localApp : backingAppInfoList) { - if (getAppWithHash(localApp.getHash(), pkgAppsListAll) == null) { - appsToRemove.add(localApp); + if (pkgAppsListAll != backingAppInfoList) { + Iterator iterator = backingAppInfoList.iterator(); + while (iterator.hasNext()) { + AppInfo localApp = iterator.next(); + if (getAppWithHash(localApp.getHash(), pkgAppsListAll) == null) { + iterator.remove(); + } } - } - backingAppInfoList.removeAll(appsToRemove); - - for (AppInfo app : pkgAppsListAll) { - final AppInfo appWithHash = getAppWithHash(app.getHash(), backingAppInfoList); - if (appWithHash != null) { - if (app.getLabelMode() == 2) { - final AppInfoList aliasesWithHash = getAliasesWithHash(app.getHash(), backingAppInfoList); - if (!aliasesWithHash.contains(app)) { - backingAppInfoList.add(app); + + for (AppInfo app : pkgAppsListAll) { + final AppInfo appWithHash = getAppWithHash(app.getHash(), backingAppInfoList); + if (appWithHash != null) { + if (app.getLabelMode() == 2) { + AppInfoList aliasesWithHash = getAliasesWithHash(app.getHash(), backingAppInfoList); + if (!aliasesWithHash.contains(app)) { + backingAppInfoList.add(app); + } + } else { + appWithHash.mergeSafe(app); } } else { - appWithHash.mergeSafe(app); + backingAppInfoList.add(app); } - } else { - backingAppInfoList.add(app); } } - setSortMode(currentSortMode); } @@ -87,11 +88,11 @@ public void setQuery(String act_query) { filteredAppInfoList.add(info); } } - + if (sorter != null) { java.util.Collections.sort(filteredAppInfoList, sorter); } - + super.update(filteredAppInfoList); } @@ -176,7 +177,7 @@ private static AppInfoList getAliasesWithHash(String hash, List appInfo return aliasesWithHash; } - public List getBackingAppInfoList() { + public AppInfoList getBackingAppInfoList() { return backingAppInfoList; } } diff --git a/android/src/main/java/org/ligi/fast/settings/AndroidFASTSettings.java b/android/src/main/java/org/ligi/fast/settings/AndroidFASTSettings.java index f6b9ca2a..25921316 100644 --- a/android/src/main/java/org/ligi/fast/settings/AndroidFASTSettings.java +++ b/android/src/main/java/org/ligi/fast/settings/AndroidFASTSettings.java @@ -1,8 +1,12 @@ package org.ligi.fast.settings; +import android.annotation.TargetApi; +import android.content.ContentProvider; +import android.content.ContentResolver; import android.content.Context; import android.content.SharedPreferences; import android.preference.PreferenceManager; +import android.provider.Settings; /** * Class to handle the Preferences @@ -14,9 +18,11 @@ public class AndroidFASTSettings implements FASTSettings { public static final String DEFUAULT_ICONSIZE = "medium"; public static final String DEFUAULT_ICON_RESOLUTION = "96"; private final SharedPreferences mSharedPreferences; + private final ContentResolver mContentResolver; public AndroidFASTSettings(Context ctx) { mSharedPreferences = PreferenceManager.getDefaultSharedPreferences(ctx); + mContentResolver = ctx.getContentResolver(); } public boolean isLaunchSingleActivated() { @@ -82,4 +88,30 @@ public boolean isShowHiddenActivated() { return mSharedPreferences.getBoolean(KEY_SHOW_HIDDEN, false); } + /** + * Checks if the system was rebooted since the last call and if yes, resets the known sequence number before returning it + * + * @return the currently known pm sequence number or default + */ + @TargetApi(26) + public int getSequenceNumber() { + int currentBootCount = + Settings.Global.getInt(mContentResolver, Settings.Global.BOOT_COUNT, 0); + int knownBootCount = mSharedPreferences.getInt(KEY_KNOWN_BOOT_COUNT, 0); + if (currentBootCount > knownBootCount) { + SharedPreferences.Editor mEditor = mSharedPreferences.edit(); + mEditor.putInt(KEY_KNOWN_PM_SEQUENCE_NUMBER, DEFAULT_KNOWN_PM_SEQUENCE_NUMBER); + mEditor.putInt(KEY_KNOWN_BOOT_COUNT, currentBootCount); + mEditor.apply(); + return DEFAULT_KNOWN_PM_SEQUENCE_NUMBER; + } + return mSharedPreferences.getInt(KEY_KNOWN_PM_SEQUENCE_NUMBER, DEFAULT_KNOWN_PM_SEQUENCE_NUMBER); + } + + @TargetApi(26) + public void putSequenceNumber(int sequenceNumber) { + SharedPreferences.Editor mEditor = mSharedPreferences.edit(); + mEditor.putInt(KEY_KNOWN_PM_SEQUENCE_NUMBER, sequenceNumber); + mEditor.apply(); + } } diff --git a/android/src/main/java/org/ligi/fast/settings/FASTSettings.java b/android/src/main/java/org/ligi/fast/settings/FASTSettings.java index b4f312c8..e24defee 100644 --- a/android/src/main/java/org/ligi/fast/settings/FASTSettings.java +++ b/android/src/main/java/org/ligi/fast/settings/FASTSettings.java @@ -1,5 +1,7 @@ package org.ligi.fast.settings; +import android.annotation.TargetApi; + /** * Interface for FAST's preferences */ @@ -20,6 +22,9 @@ public interface FASTSettings { static final String KEY_GAP_SEARCH = "gap_search"; static final String KEY_ICONRES = "icon_res"; static final String KEY_SHOW_HIDDEN = "show_hidden"; + static final String KEY_KNOWN_BOOT_COUNT = "known_boot_count"; + static final String KEY_KNOWN_PM_SEQUENCE_NUMBER = "known_pm_sequence_number"; + static final int DEFAULT_KNOWN_PM_SEQUENCE_NUMBER = -1; boolean isLaunchSingleActivated(); @@ -51,4 +56,9 @@ public interface FASTSettings { boolean isShowHiddenActivated(); + @TargetApi(26) + int getSequenceNumber(); + + @TargetApi(26) + void putSequenceNumber(int sequenceNumber); } diff --git a/android/src/main/java/org/ligi/fast/ui/FASTSettingsActivity.java b/android/src/main/java/org/ligi/fast/ui/FASTSettingsActivity.java index 46a93a5f..323fcf6f 100755 --- a/android/src/main/java/org/ligi/fast/ui/FASTSettingsActivity.java +++ b/android/src/main/java/org/ligi/fast/ui/FASTSettingsActivity.java @@ -15,6 +15,7 @@ import org.ligi.fast.App; import org.ligi.fast.R; import org.ligi.fast.TargetStore; +import org.ligi.fast.background.BackgroundGatherAsyncTask; import org.ligi.fast.settings.FASTSettings; /** @@ -212,6 +213,11 @@ public void helpClicked(View v) { HelpDialog.show(this); } + public void refreshClicked(View v) { + new BackgroundGatherAsyncTask(getApplicationContext()).execute(); + onBackPressed(); + } + private class CacheRemovingOnPreferenceClickListener implements Preference.OnPreferenceClickListener { @Override public boolean onPreferenceClick(Preference preference) { diff --git a/android/src/main/java/org/ligi/fast/ui/LoadingDialog.java b/android/src/main/java/org/ligi/fast/ui/LoadingDialog.java index 266b903c..396f8680 100644 --- a/android/src/main/java/org/ligi/fast/ui/LoadingDialog.java +++ b/android/src/main/java/org/ligi/fast/ui/LoadingDialog.java @@ -34,7 +34,7 @@ protected void onCreate(Bundle savedInstanceState) { progressBar = (ProgressBar) findViewById(R.id.progress); setTitle(getString(R.string.loadingDialogTitle)); - new BaseAppGatherAsyncTask(this) { + new BaseAppGatherAsyncTask(this.getApplicationContext()) { private int actAppIndex = 0; diff --git a/android/src/main/java/org/ligi/fast/ui/SearchActivity.java b/android/src/main/java/org/ligi/fast/ui/SearchActivity.java index ab81717d..226a1b1b 100755 --- a/android/src/main/java/org/ligi/fast/ui/SearchActivity.java +++ b/android/src/main/java/org/ligi/fast/ui/SearchActivity.java @@ -4,6 +4,8 @@ import android.content.ActivityNotFoundException; import android.content.Context; import android.content.Intent; +import android.content.IntentFilter; +import android.content.pm.ChangedPackages; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.os.Build; @@ -29,13 +31,18 @@ import org.ligi.axt.simplifications.SimpleTextWatcher; import org.ligi.fast.App; import org.ligi.fast.R; +import org.ligi.fast.background.AppInstallOrRemoveReceiver; import org.ligi.fast.background.BackgroundGatherAsyncTask; +import org.ligi.fast.background.ChangedPackagesAsyncTask; import org.ligi.fast.model.AppInfo; import org.ligi.fast.model.AppInfoList; import org.ligi.fast.model.DynamicAppInfoList; import org.ligi.fast.util.AppInfoListStore; import org.ligi.tracedroid.sending.TraceDroidEmailSender; +import java.lang.ref.WeakReference; +import java.util.ArrayList; +import java.util.List; import java.util.Locale; /** @@ -43,14 +50,29 @@ */ public class SearchActivity extends Activity implements App.PackageChangedListener { + private AppInstallOrRemoveReceiver mAppReceiver; private DynamicAppInfoList appInfoList; private AppInfoAdapter adapter; private String oldSearch = ""; private EditText searchQueryEditText; private GridView gridView; + private Context mContext; private AppInfoListStore appInfoListStore; + private static int getWidthByIconSize(String iconSize) { + switch (iconSize) { + case "tiny": + return R.dimen.cell_size_tiny; + case "small": + return R.dimen.cell_size_small; + case "large": + return R.dimen.cell_size_large; + default: + return R.dimen.cell_size; + } + } + @Override public void onCreate(Bundle savedInstanceState) { App.applyTheme(this); @@ -65,10 +87,50 @@ public void onCreate(Bundle savedInstanceState) { appInfoListStore = new AppInfoListStore(this); - final AppInfoList loadedAppInfoList = appInfoListStore.load(); + AppInfoList loadedAppInfoList = appInfoListStore.load(); appInfoList = new DynamicAppInfoList(loadedAppInfoList, App.getSettings()); adapter = new AppInfoAdapter(this, appInfoList); + App.backingAppInfoList = new WeakReference<>(appInfoList.getBackingAppInfoList()); + + mContext = this; + // Although this will also be done in onResume(), it is not guaranteed + // that it happens before the ChangedPackagesAsyncTask will try to save + App.packageChangedListener = this; + + // Since on Android 8 Oreo the BroadcastReceiver won't run while FAST itself not running, + // update the App List as needed now at app start + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + int lastKnownSequenceNumber = App.getSettings().getSequenceNumber(); + if (lastKnownSequenceNumber == App.getSettings().DEFAULT_KNOWN_PM_SEQUENCE_NUMBER) { + // First launch of FAST after boot + // Since changes between last app shutdown and device shutdown could be possible and + // won't be returned by the package manager anymore there's a full refresh needed. + App.getSettings().putSequenceNumber(0); + new BackgroundGatherAsyncTask(getApplicationContext()).execute(); + } else { + ChangedPackages changedPackages = + getApplicationContext().getPackageManager() + .getChangedPackages(lastKnownSequenceNumber); + if (changedPackages != null) { + App.getSettings().putSequenceNumber(changedPackages.getSequenceNumber()); + new ChangedPackagesAsyncTask(getApplicationContext(), changedPackages.getPackageNames()).execute(); + } + } + } + + // Since starting at API level 26 Oreo most implicit Broadcasts can't be + // registered in the Manifest anymore, register to them now at app start + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + //Register App Install Receiver + mAppReceiver = new AppInstallOrRemoveReceiver(); + IntentFilter filter = new IntentFilter(); + filter.addAction(Intent.ACTION_PACKAGE_ADDED); + filter.addAction(Intent.ACTION_PACKAGE_CHANGED); + filter.addAction(Intent.ACTION_PACKAGE_REMOVED); + filter.addDataScheme("package"); + mContext.registerReceiver(mAppReceiver, filter); + } configureAdapter(); @@ -111,11 +173,7 @@ public void afterTextChanged(Editable s) { @Override public void onItemClick(AdapterView arg0, View arg1, int pos, long arg3) { - try { - startItemAtPos(pos); - } catch (ActivityNotFoundException e) { - // e.g. uninstalled while app running - TODO should refresh list - } + startItemAtPos(pos); } }); @@ -138,12 +196,6 @@ public boolean onItemLongClick(AdapterView arg0, View arg1, if (appInfoList.size() == 0) { startActivityForResult(new Intent(this, LoadingDialog.class), R.id.activityResultLoadingDialog); - } else { // the second time - we use the old index to be fast but - // regenerate in background to be recent - - // Use the pkgAppsListTemp in order to update data from the saved file with recent - // call count information (seeing as we may not have saved it recently). - new BackgroundGatherAsyncTask(this, appInfoList).execute(); } gridView.setAdapter(adapter); @@ -180,8 +232,10 @@ public void startItemAtPos(int pos) { try { startActivity(intent); } catch (ActivityNotFoundException e) { - e.printStackTrace(); - Toast.makeText(this,"cannot start: " + e,Toast.LENGTH_LONG).show(); + Toast.makeText(this, app.getDisplayLabel() + " is no longer there - Refreshing data...", Toast.LENGTH_LONG).show(); + List packages = new ArrayList<>(); + packages.add(app.getPackageName()); + new ChangedPackagesAsyncTask(getApplicationContext(), packages).execute(); } if (Build.VERSION.SDK_INT > 18) { @@ -190,12 +244,11 @@ public void startItemAtPos(int pos) { } if (App.getSettings().isFinishOnLaunchEnabled()) { - Log.i(App.LOG_TAG, "Finished early."); + Log.i(App.LOG_TAG, "Finished on launch."); finish(); } } - @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); @@ -224,19 +277,6 @@ protected void onResume() { gridView.setColumnWidth((int) getResources().getDimension(getWidthByIconSize(iconSize))); } - private static int getWidthByIconSize(String iconSize) { - switch (iconSize) { - case "tiny": - return R.dimen.cell_size_tiny; - case "small": - return R.dimen.cell_size_small; - case "large": - return R.dimen.cell_size_large; - default: - return R.dimen.cell_size; - } - } - private void dealWithUserPreferencesRegardingSoftKeyboard() { if (App.getSettings().isShowKeyBoardOnStartActivated()) { showKeyboard(); @@ -310,9 +350,7 @@ private String getHomePackageName() { @Override public void onPackageChange(final AppInfoList newAppInfoList) { - // TODO we should also do a cleanup of cached icons here // we might not come from UI Thread - runOnUiThread(new Runnable() { @Override public void run() { @@ -331,6 +369,14 @@ protected void onPause() { super.onPause(); } + @Override + protected void onDestroy() { + super.onDestroy(); + App.backingAppInfoList = null; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) + mContext.unregisterReceiver(mAppReceiver); + } + public void addEntry(AppInfo new_entry) { appInfoList.getBackingAppInfoList().add(new_entry); } diff --git a/android/src/main/java/org/ligi/fast/ui/SetLabelDialogBuilder.java b/android/src/main/java/org/ligi/fast/ui/SetLabelDialogBuilder.java index 19ba4331..ef73e3cb 100644 --- a/android/src/main/java/org/ligi/fast/ui/SetLabelDialogBuilder.java +++ b/android/src/main/java/org/ligi/fast/ui/SetLabelDialogBuilder.java @@ -80,6 +80,6 @@ public void onClick(DialogInterface dialogInterface, int i) { } }); - setNeutralButton(R.string.cancel, null); + setNeutralButton(android.R.string.cancel, null); } } diff --git a/android/src/noExtras/res/layout/main_title.xml b/android/src/noExtras/res/layout/main_title.xml index 10040677..a61ae44a 100644 --- a/android/src/noExtras/res/layout/main_title.xml +++ b/android/src/noExtras/res/layout/main_title.xml @@ -1,40 +1,33 @@ + android:layout_height="wrap_content" + android:orientation="horizontal" + android:paddingBottom="2dp" + android:paddingLeft="@dimen/padding_medium" + android:paddingStart="@dimen/padding_medium"> - + - - - - - - - + + diff --git a/android/src/noExtras/res/layout/prefs_title.xml b/android/src/noExtras/res/layout/prefs_title.xml index de590014..96a76ee2 100644 --- a/android/src/noExtras/res/layout/prefs_title.xml +++ b/android/src/noExtras/res/layout/prefs_title.xml @@ -1,46 +1,48 @@ + android:layout_height="wrap_content" + android:orientation="horizontal" + android:paddingBottom="2dp"> - - + + - - - - - - - - + android:scaleType="fitEnd" + android:src="@drawable/back" /> + + + + + \ No newline at end of file diff --git a/android/src/withExtras/res/layout/main_title.xml b/android/src/withExtras/res/layout/main_title.xml index 29117e84..2b2c80dd 100644 --- a/android/src/withExtras/res/layout/main_title.xml +++ b/android/src/withExtras/res/layout/main_title.xml @@ -1,50 +1,43 @@ + android:layout_height="wrap_content" + android:orientation="horizontal" + android:paddingBottom="2dp" + android:paddingLeft="@dimen/padding_medium" + android:paddingStart="@dimen/padding_medium"> - + - + - - - - - - - + + diff --git a/android/src/withExtras/res/layout/prefs_title.xml b/android/src/withExtras/res/layout/prefs_title.xml index d9d38c8d..d82e9ea5 100644 --- a/android/src/withExtras/res/layout/prefs_title.xml +++ b/android/src/withExtras/res/layout/prefs_title.xml @@ -1,56 +1,58 @@ + android:layout_height="wrap_content" + android:orientation="horizontal" + android:paddingBottom="2dp"> + android:background="@drawable/hightlight" + android:clickable="true" + android:focusable="true" + android:onClick="homePressed" + android:orientation="horizontal"> - - - - - - - - - + android:scaleType="fitEnd" + android:src="@drawable/back" /> + + - + + + + + \ No newline at end of file