Skip to content

Commit

Permalink
Redesign the app (un)install receiver
Browse files Browse the repository at this point in the history
- Packages will be refreshed individually whenever neccesary
- Refreshing directly modifies SearchActivity.appInfoList.backingAppInfoList
  if it's available at WeakReference App.backingAppInfoList
- LocaleChangeReceiver triggers a full refresh when changing the system language
- In the preferences' title bar there is now a refresh button, just in case
- IconCache.tryIconCaching() no longer returns true if icon already exists
  instead the icon gets refreshed anyway
- IconCache.getIconCacheFile() has been centralized into AppInfo
  • Loading branch information
SebiderSushi committed Nov 22, 2017
1 parent b8d7742 commit e6466af
Show file tree
Hide file tree
Showing 16 changed files with 470 additions and 316 deletions.
9 changes: 6 additions & 3 deletions android/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -70,15 +70,18 @@

<receiver android:name=".background.AppInstallOrRemoveReceiver">
<intent-filter>
<action android:name="android.intent.action.PACKAGE_INSTALL" />
<action android:name="android.intent.action.PACKAGE_ADDED" />
<action android:name="android.intent.action.PACKAGE_REMOVED" />
<action android:name="android.intent.action.PACKAGE_REPLACED" />
<action android:name="android.intent.action.PACKAGE_CHANGED" />
<action android:name="android.intent.action.PACKAGE_FULLY_REMOVED" />
<data android:scheme="package" />
</intent-filter>
</receiver>

<receiver android:name=".background.LocaleChangeReceiver">
<intent-filter>
<action android:name="android.intent.action.LOCALE_CHANGED" />
</intent-filter>
</receiver>
</application>

</manifest>
2 changes: 2 additions & 0 deletions android/src/main/java/org/ligi/fast/App.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import org.ligi.tracedroid.TraceDroid;

import java.io.File;
import java.lang.ref.WeakReference;

public class App extends Application {

Expand All @@ -22,6 +23,7 @@ public interface PackageChangedListener {
}

public static PackageChangedListener packageChangedListener;
public static WeakReference<AppInfoList> backingAppInfoList;

@Override
public void onCreate() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,24 +3,153 @@
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ResolveInfo;
import android.net.Uri;

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.io.File;
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 {
//public final static String LOG_TAG = "FAST.AppInstallOrRemoveReceiver";

@Override
public void onReceive(Context context, Intent intent) {
final AppInfoListStore appInfoListStore = new AppInfoListStore(context);
Uri data = intent.getData();
if (data == null) return; // This should never be the case but...just to be sure...
String packageName = data.getSchemeSpecificPart();
String action = intent.getAction();
AppInfoListStore appInfoListStore = new AppInfoListStore(context);
AppInfoList appInfoList = null;
if (App.backingAppInfoList != null) {
appInfoList = App.backingAppInfoList.get();
}
if (appInfoList == null) {
appInfoList = appInfoListStore.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 newInstall = Intent.ACTION_PACKAGE_ADDED.equals(action) && !intent.getBooleanExtra(Intent.EXTRA_REPLACING, true);
AppInfoList matchedAppInfoList = new AppInfoList();
if (!newInstall) {
// Collect all information concerning the changed package
Iterator<AppInfo> appInfoIterator = appInfoList.iterator();
while (appInfoIterator.hasNext()) {
AppInfo appInfo = appInfoIterator.next();
if (appInfo.getPackageName().equals(packageName)) {
matchedAppInfoList.add(appInfo);
appInfoIterator.remove();
File icon = appInfo.getIconCacheFile();
icon.delete();
/*
if (!icon.delete()) {
Log.d(App.LOG_TAG, "AppInstallOrRemoveReceiver: Icon deletion failed for hash: " + appInfo.getHash());
Log.d(AppInstallOrRemoveReceiver.LOG_TAG, "Icon deletion failed for hash: " + appInfo.getHash());
}
*/
}
}
// Just to be sure; If this is the case there is no old information
// to update and we can continue to simply add the new information
newInstall = matchedAppInfoList.size() == 0;
}

if (App.packageChangedListener == null) {
App.packageChangedListener = new App.PackageChangedListener() {
@Override
public void onPackageChange(AppInfoList appInfoList) {
appInfoListStore.save(appInfoList);
if (!Intent.ACTION_PACKAGE_FULLY_REMOVED.equals(action)) {
Intent launcherIntent = new Intent(Intent.ACTION_MAIN);
launcherIntent.addCategory(Intent.CATEGORY_LAUNCHER);
launcherIntent.setPackage(packageName);
List<ResolveInfo> resolveInfoList = context.getPackageManager().queryIntentActivities(launcherIntent, 0);

Intent homeIntent = new Intent(Intent.ACTION_MAIN);
homeIntent.addCategory(Intent.CATEGORY_HOME);
homeIntent.setPackage(packageName);
List<ResolveInfo> homeInfoList = context.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) {
//Log.d(App.LOG_TAG, "AppInstallOrRemoveReceiver: No launcher Activities"
// + "\n\tPackage: " + packageName);
return;
}

// Deduplicate Resolve Info of activities with both categories - like SearchActivity (see manifest)
for (ResolveInfo info : resolveInfoList) {
Iterator<ResolveInfo> 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);

/*
String log =
"AppInstallOrRemoveReceiver: Updating info:"
+ "\n\tAction: " + action
+ "\n\tPackage: " + packageName
+ "\n\tLabel: " + resolveInfoList.get(0).activityInfo.loadLabel(context.getPackageManager())
+ "\n\tActivities: " + String.valueOf(resolveInfoList.size());
Log.d(App.LOG_TAG, log);
Log.d(AppInstallOrRemoveReceiver.LOG_TAG, log);
for (ResolveInfo i : resolveInfoList) {
Log.d(App.LOG_TAG, "\t " + i.activityInfo.name);
Log.d(AppInstallOrRemoveReceiver.LOG_TAG, "\t " + i.activityInfo.name);
}
*/

if (newInstall) { // New app, simple adding
for (ResolveInfo info : resolveInfoList) {
appInfoList.add(new AppInfo(context, info));
}
} else { // Update, merge data
for (ResolveInfo info : resolveInfoList) {
AppInfo actAppInfo = new AppInfo(context, info);

Iterator<AppInfo> oldInfoIterator = matchedAppInfoList.iterator();
while (oldInfoIterator.hasNext()) {
AppInfo oldInfo = oldInfoIterator.next();
if (oldInfo.getActivityName().equals(actAppInfo.getActivityName())) {
if (oldInfo.getLabelMode() == 2) { // AppInfo is alias
oldInfo.setLabel(actAppInfo.getLabel());
oldInfo.setInstallTime(actAppInfo.getInstallTime());
appInfoList.add(oldInfo);
} else {
actAppInfo.setCallCount(oldInfo.getCallCount());
actAppInfo.setPinMode(oldInfo.getPinMode());
actAppInfo.setLabelMode(oldInfo.getLabelMode());
actAppInfo.setOverrideLabel(oldInfo.getOverrideLabel());
}
oldInfoIterator.remove();
}
}
appInfoList.add(actAppInfo);
}
};
}
}

new BackgroundGatherAsyncTask(context, appInfoListStore.load()).execute();
if (App.packageChangedListener == null) {
appInfoListStore.save(appInfoList);
} else {
App.packageChangedListener.onPackageChange(appInfoList);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,12 @@
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);
}

@Override
Expand All @@ -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;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -5,80 +5,81 @@
import android.content.pm.ResolveInfo;
import android.os.AsyncTask;

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<Void, AppInfo, Void> {
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) {
this.ctx = ctx;
appInfoList = new AppInfoList();
this.oldAppList = oldAppList;
if (App.backingAppInfoList != null) {
this.oldAppList = App.backingAppInfoList.get();
}
if (this.appInfoList == null) {
this.appInfoList = new AppInfoListStore(ctx).load();
}
}

private void processCategory(final String category) {
final Intent mainIntent = new Intent(Intent.ACTION_MAIN, null);
mainIntent.addCategory(category);
try {
final List<ResolveInfo> 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) {
Intent launcherIntent = new Intent(Intent.ACTION_MAIN);
launcherIntent.addCategory(Intent.CATEGORY_LAUNCHER);
List<ResolveInfo> 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
appCount = resolveInfoList.size();

// 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());
}
for (ResolveInfo info : resolveInfoList) {
if (!ctx.getPackageName().equals(info.activityInfo.packageName)) { // ignore self
AppInfo actAppInfo = 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<AppInfo> oldInfoIterator = oldAppList.iterator();
while (oldInfoIterator.hasNext()) {
AppInfo oldInfo = oldInfoIterator.next();
if (oldInfo.getActivityName().equals(actAppInfo.getActivityName())) {
if (oldInfo.getLabelMode() == 2) { // AppInfo is alias
oldInfo.setLabel(actAppInfo.getLabel());
oldInfo.setInstallTime(actAppInfo.getInstallTime());
appInfoList.add(oldInfo);
} else {
actAppInfo.setCallCount(oldInfo.getCallCount());
actAppInfo.setPinMode(oldInfo.getPinMode());
actAppInfo.setLabelMode(oldInfo.getLabelMode());
actAppInfo.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(actAppInfo);
publishProgress(actAppInfo);
}
} catch (Exception e) {
Log.d("Exception occurred when getting activities skipping...!");
}

}


@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;
return null;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -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).execute();
}
}
Loading

0 comments on commit e6466af

Please sign in to comment.