package de.robv.android.xposed.installer.util;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.ResolveInfo;
import android.graphics.drawable.Drawable;
import android.os.FileUtils;
import android.util.Log;
import android.widget.Toast;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CopyOnWriteArrayList;
import de.robv.android.xposed.installer.ModulesFragment;
import de.robv.android.xposed.installer.R;
import de.robv.android.xposed.installer.XposedApp;
import de.robv.android.xposed.installer.repo.ModuleVersion;
import de.robv.android.xposed.installer.repo.RepoDb;
public final class ModuleUtil {
// xposedminversion below this
private static final String MODULES_LIST_FILE = XposedApp.BASE_DIR + "conf/modules.list";
private static final String PLAY_STORE_PACKAGE = "com.android.vending";
public static int MIN_MODULE_VERSION = 2; // reject modules with
private static ModuleUtil mInstance = null;
private final XposedApp mApp;
private final PackageManager mPm;
private final String mFrameworkPackageName;
private final List<ModuleListener> mListeners = new CopyOnWriteArrayList<ModuleListener>();
private SharedPreferences mPref;
private InstalledModule mFramework = null;
private Map<String, InstalledModule> mInstalledModules;
private boolean mIsReloading = false;
private Toast mToast;
private ModuleUtil() {
mApp = XposedApp.getInstance();
mPref = mApp.getSharedPreferences("enabled_modules", Context.MODE_PRIVATE);
mPm = mApp.getPackageManager();
mFrameworkPackageName = mApp.getPackageName();
}
public static synchronized ModuleUtil getInstance() {
if (mInstance == null) {
mInstance = new ModuleUtil();
mInstance.reloadInstalledModules();
}
return mInstance;
}
public static int extractIntPart(String str) {
int result = 0, length = str.length();
for (int offset = 0; offset < length; offset++) {
char c = str.charAt(offset);
if ('0' <= c && c <= '9')
result = result * 10 + (c - '0');
else
break;
}
return result;
}
public void reloadInstalledModules() {
synchronized (this) {
if (mIsReloading)
return;
mIsReloading = true;
}
Map<String, InstalledModule> modules = new HashMap<String, InstalledModule>();
RepoDb.beginTransation();
try {
RepoDb.deleteAllInstalledModules();
for (PackageInfo pkg : mPm.getInstalledPackages(PackageManager.GET_META_DATA)) {
ApplicationInfo app = pkg.applicationInfo;
if (!app.enabled)
continue;
InstalledModule installed = null;
if (app.metaData != null && app.metaData.containsKey("xposedmodule")) {
installed = new InstalledModule(pkg, false);
modules.put(pkg.packageName, installed);
} else if (isFramework(pkg.packageName)) {
mFramework = installed = new InstalledModule(pkg, true);
}
if (installed != null)
RepoDb.insertInstalledModule(installed);
}
RepoDb.setTransactionSuccessful();
} finally {
RepoDb.endTransation();
}
mInstalledModules = modules;
synchronized (this) {
mIsReloading = false;
}
for (ModuleListener listener : mListeners) {
listener.onInstalledModulesReloaded(mInstance);
}
}
public InstalledModule reloadSingleModule(String packageName) {
PackageInfo pkg;
try {
pkg = mPm.getPackageInfo(packageName, PackageManager.GET_META_DATA);
} catch (NameNotFoundException e) {
RepoDb.deleteInstalledModule(packageName);
InstalledModule old = mInstalledModules.remove(packageName);
if (old != null) {
for (ModuleListener listener : mListeners) {
listener.onSingleInstalledModuleReloaded(mInstance, packageName, null);
}
}
return null;
}
ApplicationInfo app = pkg.applicationInfo;
if (app.enabled && app.metaData != null && app.metaData.containsKey("xposedmodule")) {
InstalledModule module = new InstalledModule(pkg, false);
RepoDb.insertInstalledModule(module);
mInstalledModules.put(packageName, module);
for (ModuleListener listener : mListeners) {
listener.onSingleInstalledModuleReloaded(mInstance, packageName,
module);
}
return module;
} else {
RepoDb.deleteInstalledModule(packageName);
InstalledModule old = mInstalledModules.remove(packageName);
if (old != null) {
for (ModuleListener listener : mListeners) {
listener.onSingleInstalledModuleReloaded(mInstance, packageName, null);
}
}
return null;
}
}
public InstalledModule getFramework() {
return mFramework;
}
public String getFrameworkPackageName() {
return mFrameworkPackageName;
}
public boolean isFramework(String packageName) {
return mFrameworkPackageName.equals(packageName);
}
public InstalledModule getModule(String packageName) {
return mInstalledModules.get(packageName);
}
public Map<String, InstalledModule> getModules() {
return mInstalledModules;
}
public void setModuleEnabled(String packageName, boolean enabled) {
if (enabled)
mPref.edit().putInt(packageName, 1).apply();
else
mPref.edit().remove(packageName).apply();
}
public boolean isModuleEnabled(String packageName) {
return mPref.contains(packageName);
}
public List<InstalledModule> getEnabledModules() {
LinkedList<InstalledModule> result = new LinkedList<InstalledModule>();
for (String packageName : mPref.getAll().keySet()) {
InstalledModule module = getModule(packageName);
if (module != null)
result.add(module);
else
setModuleEnabled(packageName, false);
}
return result;
}
public synchronized void updateModulesList(boolean showToast) {
try {
Log.i(XposedApp.TAG, "updating modules.list");
int installedXposedVersion = XposedApp.getInstalledXposedVersion();
PrintWriter modulesList = new PrintWriter(MODULES_LIST_FILE);
PrintWriter enabledModulesList = new PrintWriter(XposedApp.ENABLED_MODULES_LIST_FILE);
List<InstalledModule> enabledModules = getEnabledModules();
for (InstalledModule module : enabledModules) {
if (module.minVersion > installedXposedVersion || module.minVersion < MIN_MODULE_VERSION)
continue;
modulesList.println(module.app.sourceDir);
try {
String installer = mPm.getInstallerPackageName(module.app.packageName);
if (!PLAY_STORE_PACKAGE.equals(installer)) {
enabledModulesList.println(module.app.packageName);
}
} catch (IllegalArgumentException ignored) {
// In rare cases, the package might not be installed anymore at this point,
// so the PackageManager can't return its installer package name.
}
}
modulesList.close();
enabledModulesList.close();
FileUtils.setPermissions(MODULES_LIST_FILE, 00664, -1, -1);
FileUtils.setPermissions(XposedApp.ENABLED_MODULES_LIST_FILE, 00664, -1, -1);
if (showToast)
showToast(R.string.xposed_module_list_updated);
} catch (IOException e) {
Log.e(XposedApp.TAG, "cannot write " + MODULES_LIST_FILE, e);
Toast.makeText(mApp, "cannot write " + MODULES_LIST_FILE + e, Toast.LENGTH_SHORT).show();
}
}
private void showToast(int message) {
if (mToast != null) {
mToast.cancel();
mToast = null;
}
mToast = Toast.makeText(mApp, mApp.getString(message), Toast.LENGTH_SHORT);
mToast.show();
}
public void addListener(ModuleListener listener) {
if (!mListeners.contains(listener))
mListeners.add(listener);
}
public void removeListener(ModuleListener listener) {
mListeners.remove(listener);
}
public interface ModuleListener {
/**
* Called whenever one (previously or now) installed module has been
* reloaded
*/
void onSingleInstalledModuleReloaded(ModuleUtil moduleUtil, String packageName, InstalledModule module);
/**
* Called whenever all installed modules have been reloaded
*/
void onInstalledModulesReloaded(ModuleUtil moduleUtil);
}
public class InstalledModule {
private static final int FLAG_FORWARD_LOCK = 1 << 29;
public final String packageName;
public final boolean isFramework;
public final String versionName;
public final int versionCode;
public final int minVersion;
public ApplicationInfo app;
private String appName; // loaded lazyily
private String description; // loaded lazyily
private Drawable.ConstantState iconCache = null;
private InstalledModule(PackageInfo pkg, boolean isFramework) {
this.app = pkg.applicationInfo;
this.packageName = pkg.packageName;
this.isFramework = isFramework;
this.versionName = pkg.versionName;
this.versionCode = pkg.versionCode;
if (isFramework) {
this.minVersion = 0;
this.description = "";
} else {
Object minVersionRaw = app.metaData.get("xposedminversion");
if (minVersionRaw instanceof Integer) {
this.minVersion = (Integer) minVersionRaw;
} else if (minVersionRaw instanceof String) {
this.minVersion = extractIntPart((String) minVersionRaw);
} else {
this.minVersion = 0;
}
}
}
public boolean isInstalledOnExternalStorage() {
return (app.flags & ApplicationInfo.FLAG_EXTERNAL_STORAGE) != 0;
}
/**
* @hide
*/
public boolean isForwardLocked() {
return (app.flags & FLAG_FORWARD_LOCK) != 0;
}
public String getAppName() {
if (appName == null)
appName = app.loadLabel(mPm).toString();
return appName;
}
public String getDescription() {
if (this.description == null) {
Object descriptionRaw = app.metaData.get("xposeddescription");
String descriptionTmp = null;
if (descriptionRaw instanceof String) {
descriptionTmp = ((String) descriptionRaw).trim();
} else if (descriptionRaw instanceof Integer) {
try {
int resId = (Integer) descriptionRaw;
if (resId != 0)
descriptionTmp = mPm.getResourcesForApplication(app).getString(resId).trim();
} catch (Exception ignored) {
}
}
this.description = (descriptionTmp != null) ? descriptionTmp : "";
}
return this.description;
}
public boolean isUpdate(ModuleVersion version) {
return (version != null) && version.code > versionCode;
}
public Drawable getIcon() {
if (iconCache != null)
return iconCache.newDrawable();
Intent mIntent = new Intent(Intent.ACTION_MAIN);
mIntent.addCategory(ModulesFragment.SETTINGS_CATEGORY);
mIntent.setPackage(app.packageName);
List<ResolveInfo> ris = mPm.queryIntentActivities(mIntent, 0);
Drawable result;
if (ris == null || ris.size() <= 0)
result = app.loadIcon(mPm);
else
result = ris.get(0).activityInfo.loadIcon(mPm);
iconCache = result.getConstantState();
return result;
}
@Override
public String toString() {
return getAppName();
}
}
}