Remove module repo completely

This commit is contained in:
NekoInverter 2021-01-26 16:15:50 +08:00
parent 9fa92741ab
commit ffc7980ac6
No known key found for this signature in database
GPG Key ID: 280D6CCCF95715F9
46 changed files with 30 additions and 3913 deletions

View File

@ -34,12 +34,6 @@
android:name=".ui.activity.EdDownloadActivity"
android:label="@string/Install" />
<activity android:name=".ui.activity.BlackListActivity" />
<activity
android:name=".ui.activity.DownloadDetailsActivity"
android:label="@string/nav_item_download" />
<activity
android:name=".ui.activity.DownloadActivity"
android:label="@string/Downloads" />
<activity
android:name=".ui.activity.ModuleScopeActivity"
android:label="@string/menu_scope" />

View File

@ -120,8 +120,6 @@ public class App extends Application implements Application.ActivityLifecycleCal
} catch (PackageManager.NameNotFoundException ignored) {
}
}
//RepoLoader.getInstance().triggerFirstLoadIfNecessary();
}
private void registerReceivers() {
@ -142,19 +140,6 @@ public class App extends Application implements Application.ActivityLifecycleCal
mkdir("log");
}
/*
public void updateProgressIndicator(final SwipeRefreshLayout refreshLayout) {
final boolean isLoading = RepoLoader.getInstance().isLoading() || ModuleUtil.getInstance().isLoading();
runOnUiThread(() -> {
synchronized (App.this) {
if (currentActivity != null) {
if (refreshLayout != null)
refreshLayout.setRefreshing(isLoading);
}
}
});
}
*/
@Override
public synchronized void onActivityCreated(@NonNull Activity activity, Bundle savedInstanceState) {
if (isUiLoaded) {
@ -182,13 +167,12 @@ public class App extends Application implements Application.ActivityLifecycleCal
@Override
public synchronized void onActivityResumed(@NonNull Activity activity) {
//currentActivity = (AppCompatActivity) activity;
//updateProgressIndicator(null);
}
@Override
public synchronized void onActivityPaused(@NonNull Activity activity) {
//currentActivity = null;
}
@Override

View File

@ -6,6 +6,7 @@ import android.content.SharedPreferences;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.res.Resources;
import android.graphics.drawable.Drawable;
import android.os.AsyncTask;
import android.text.TextUtils;
@ -32,7 +33,6 @@ import com.bumptech.glide.request.transition.Transition;
import org.meowcat.edxposed.manager.App;
import org.meowcat.edxposed.manager.R;
import org.meowcat.edxposed.manager.util.GlideApp;
import org.meowcat.edxposed.manager.util.InstallApkUtil;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
@ -277,7 +277,7 @@ public class AppAdapter extends RecyclerView.Adapter<AppAdapter.ViewHolder> impl
@Override
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
ApplicationInfo info = showList.get(position);
holder.appName.setText(InstallApkUtil.getAppLabel(info, pm));
holder.appName.setText(getAppLabel(info, pm));
try {
PackageInfo packageInfo = pm.getPackageInfo(info.packageName, 0);
GlideApp.with(holder.appIcon)
@ -387,7 +387,7 @@ public class AppAdapter extends RecyclerView.Adapter<AppAdapter.ViewHolder> impl
ArrayList<ApplicationInfo> filtered = new ArrayList<>();
String filter = constraint.toString().toLowerCase();
for (ApplicationInfo info : fullList) {
if (lowercaseContains(InstallApkUtil.getAppLabel(info, pm), filter)
if (lowercaseContains(getAppLabel(info, pm), filter)
|| lowercaseContains(info.packageName, filter)) {
filtered.add(info);
}
@ -402,4 +402,15 @@ public class AppAdapter extends RecyclerView.Adapter<AppAdapter.ViewHolder> impl
notifyDataSetChanged();
}
}
public static String getAppLabel(ApplicationInfo info, PackageManager pm) {
try {
if (info.labelRes > 0) {
Resources res = pm.getResourcesForApplication(info);
return res.getString(info.labelRes);
}
} catch (Exception ignored) {
}
return info.loadLabel(pm).toString();
}
}

View File

@ -1,123 +0,0 @@
package org.meowcat.edxposed.manager.adapters;
import android.database.Cursor;
import android.database.DataSetObserver;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
public abstract class CursorRecyclerViewAdapter<VH extends RecyclerView.ViewHolder> extends RecyclerView.Adapter<VH> {
private Cursor cursor;
private boolean dataValid;
private int rowIdColumn;
private final DataSetObserver dataSetObserver;
public CursorRecyclerViewAdapter(Cursor cursor) {
this.cursor = cursor;
dataValid = cursor != null;
rowIdColumn = dataValid ? cursor.getColumnIndex("_id") : -1;
dataSetObserver = new NotifyingDataSetObserver();
if (this.cursor != null) {
this.cursor.registerDataSetObserver(dataSetObserver);
}
}
protected Cursor getCursor() {
return cursor;
}
@Override
public int getItemCount() {
if (dataValid && cursor != null) {
return cursor.getCount();
}
return 0;
}
@Override
public long getItemId(int position) {
if (dataValid && cursor != null && cursor.moveToPosition(position)) {
return cursor.getLong(rowIdColumn);
}
return 0;
}
@Override
public void setHasStableIds(boolean hasStableIds) {
super.setHasStableIds(true);
}
public abstract void onBindViewHolder(VH viewHolder, Cursor cursor);
@Override
public void onBindViewHolder(@NonNull VH viewHolder, int position) {
if (!dataValid) {
throw new IllegalStateException("this should only be called when the cursor is valid");
}
if (!cursor.moveToPosition(position)) {
throw new IllegalStateException("couldn't move cursor to position " + position);
}
onBindViewHolder(viewHolder, cursor);
}
/**
* Change the underlying cursor to a new cursor. If there is an existing cursor it will be
* closed.
*/
public void changeCursor(Cursor cursor) {
Cursor old = swapCursor(cursor);
if (old != null) {
old.close();
}
}
/**
* Swap in a new Cursor, returning the old Cursor. Unlike
* {@link #changeCursor(Cursor)}, the returned old Cursor is <em>not</em>
* closed.
*/
private Cursor swapCursor(Cursor newCursor) {
if (newCursor == cursor) {
return null;
}
final Cursor oldCursor = cursor;
if (oldCursor != null && dataSetObserver != null) {
oldCursor.unregisterDataSetObserver(dataSetObserver);
}
cursor = newCursor;
if (cursor != null) {
if (dataSetObserver != null) {
cursor.registerDataSetObserver(dataSetObserver);
}
rowIdColumn = newCursor.getColumnIndexOrThrow("_id");
dataValid = true;
} else {
rowIdColumn = -1;
dataValid = false;
//There is no notifyDataSetInvalidated() method in RecyclerView.Adapter
}
notifyDataSetChanged();
return oldCursor;
}
private class NotifyingDataSetObserver extends DataSetObserver {
@Override
public void onChanged() {
super.onChanged();
dataValid = true;
notifyDataSetChanged();
}
@Override
public void onInvalidated() {
super.onInvalidated();
dataValid = false;
notifyDataSetChanged();
//There is no notifyDataSetInvalidated() method in RecyclerView.Adapter
}
}
}

View File

@ -1,44 +0,0 @@
package org.meowcat.edxposed.manager.receivers;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import org.json.JSONObject;
import org.meowcat.edxposed.manager.BuildConfig;
import org.meowcat.edxposed.manager.util.NotificationUtil;
import org.meowcat.edxposed.manager.util.TaskRunner;
import org.meowcat.edxposed.manager.util.json.JSONUtils;
import java.util.concurrent.Callable;
public class BootReceiver extends BroadcastReceiver {
@Override
public void onReceive(final Context context, Intent intent) {
//new TaskRunner().executeAsync(new LongRunningTask());
}
private static class LongRunningTask implements Callable<Void> {
@Override
public Void call() {
try {
Thread.sleep(60 * 60 * 1000);
String jsonString = JSONUtils.getFileContent(JSONUtils.JSON_LINK).replace("%XPOSED_ZIP%", "");
String newApkVersion = new JSONObject(jsonString).getJSONObject("apk").getString("version");
Integer a = BuildConfig.VERSION_CODE;
Integer b = Integer.valueOf(newApkVersion);
if (a.compareTo(b) < 0) {
NotificationUtil.showInstallerUpdateNotification();
}
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
}

View File

@ -1,28 +0,0 @@
package org.meowcat.edxposed.manager.repo;
import android.util.Pair;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
public class Module {
@SuppressWarnings("WeakerAccess")
public final Repository repository;
public final List<Pair<String, String>> moreInfo = new LinkedList<>();
public final List<ModuleVersion> versions = new ArrayList<>();
final List<String> screenshots = new ArrayList<>();
public String packageName;
public String name;
public String summary;
public String description;
public boolean descriptionIsHtml = false;
public String author;
public String support;
long created = -1;
long updated = -1;
Module(Repository repository) {
this.repository = repository;
}
}

View File

@ -1,17 +0,0 @@
package org.meowcat.edxposed.manager.repo;
public class ModuleVersion {
public final Module module;
public String name;
public int code;
public String downloadLink;
public String md5sum;
public String changelog;
public boolean changelogIsHtml = false;
public ReleaseType relType = ReleaseType.STABLE;
public long uploaded = -1;
/* package */ ModuleVersion(Module module) {
this.module = module;
}
}

View File

@ -1,40 +0,0 @@
package org.meowcat.edxposed.manager.repo;
import org.meowcat.edxposed.manager.R;
public enum ReleaseType {
STABLE(R.string.reltype_stable, R.string.reltype_stable_summary), BETA(R.string.reltype_beta, R.string.reltype_beta_summary), EXPERIMENTAL(R.string.reltype_experimental, R.string.reltype_experimental_summary);
private static final ReleaseType[] sValuesCache = values();
private final int mTitleId;
private final int mSummaryId;
ReleaseType(int titleId, int summaryId) {
mTitleId = titleId;
mSummaryId = summaryId;
}
public static ReleaseType fromString(String value) {
if (value == null || value.equals("stable"))
return STABLE;
else if (value.equals("beta"))
return BETA;
else if (value.equals("experimental"))
return EXPERIMENTAL;
else
return STABLE;
}
public static ReleaseType fromOrdinal(int ordinal) {
return sValuesCache[ordinal];
}
public int getTitleId() {
return mTitleId;
}
public int getSummaryId() {
return mSummaryId;
}
}

View File

@ -1,492 +0,0 @@
package org.meowcat.edxposed.manager.repo;
import android.annotation.SuppressLint;
import android.content.ContentValues;
import android.content.Context;
import android.content.SharedPreferences;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.text.TextUtils;
import android.util.Pair;
import org.meowcat.edxposed.manager.App;
import org.meowcat.edxposed.manager.BuildConfig;
import org.meowcat.edxposed.manager.repo.RepoDbDefinitions.InstalledModulesColumns;
import org.meowcat.edxposed.manager.repo.RepoDbDefinitions.InstalledModulesUpdatesColumns;
import org.meowcat.edxposed.manager.repo.RepoDbDefinitions.ModuleVersionsColumns;
import org.meowcat.edxposed.manager.repo.RepoDbDefinitions.ModulesColumns;
import org.meowcat.edxposed.manager.repo.RepoDbDefinitions.MoreInfoColumns;
import org.meowcat.edxposed.manager.repo.RepoDbDefinitions.OverviewColumns;
import org.meowcat.edxposed.manager.repo.RepoDbDefinitions.OverviewColumnsIndexes;
import org.meowcat.edxposed.manager.repo.RepoDbDefinitions.RepositoriesColumns;
import org.meowcat.edxposed.manager.util.ModuleUtil;
import org.meowcat.edxposed.manager.util.ModuleUtil.InstalledModule;
import org.meowcat.edxposed.manager.util.RepoLoader;
import java.io.File;
import java.util.LinkedHashMap;
import java.util.Map;
import static android.content.Context.MODE_PRIVATE;
public final class RepoDb extends SQLiteOpenHelper {
public static final int SORT_STATUS = 0;
public static final int SORT_UPDATED = 1;
private static final int SORT_CREATED = 2;
@SuppressLint("StaticFieldLeak")
private static Context context;
private static final SQLiteDatabase db;
static {
RepoDb instance = new RepoDb(App.getInstance());
db = instance.getWritableDatabase();
db.execSQL("PRAGMA foreign_keys=ON");
instance.createTempTables(db);
}
private RepoDb(Context context) {
super(context, getDbPath(context), null, RepoDbDefinitions.DATABASE_VERSION);
RepoDb.context = context;
}
private static String getDbPath(Context context) {
return new File(context.getNoBackupFilesDir(), RepoDbDefinitions.DATABASE_NAME).getPath();
}
public static void beginTransation() {
db.beginTransaction();
}
public static void setTransactionSuccessful() {
db.setTransactionSuccessful();
}
public static void endTransation() {
db.endTransaction();
}
private static String getString(@SuppressWarnings("SameParameterValue") String table, @SuppressWarnings("SameParameterValue") String searchColumn, String searchValue, @SuppressWarnings("SameParameterValue") String resultColumn) {
String[] projection = new String[]{resultColumn};
String where = searchColumn + " = ?";
String[] whereArgs = new String[]{searchValue};
Cursor c = db.query(table, projection, where, whereArgs, null, null, null, "1");
if (c.moveToFirst()) {
String result = c.getString(c.getColumnIndexOrThrow(resultColumn));
c.close();
return result;
} else {
c.close();
throw new RowNotFoundException("Could not find " + table + "." + searchColumn + " with value '" + searchValue + "'");
}
}
@SuppressWarnings("UnusedReturnValue")
public static long insertRepository(String url) {
ContentValues values = new ContentValues();
values.put(RepositoriesColumns.URL, url);
return db.insertOrThrow(RepositoriesColumns.TABLE_NAME, null, values);
}
public static void deleteRepositories() {
if (db != null)
db.delete(RepositoriesColumns.TABLE_NAME, null, null);
}
public static Map<Long, Repository> getRepositories() {
Map<Long, Repository> result = new LinkedHashMap<>(1);
String[] projection = new String[]{
RepositoriesColumns._ID,
RepositoriesColumns.URL,
RepositoriesColumns.TITLE,
RepositoriesColumns.PARTIAL_URL,
RepositoriesColumns.VERSION,
};
Cursor c = db.query(RepositoriesColumns.TABLE_NAME, projection, null, null, null, null, RepositoriesColumns._ID);
while (c.moveToNext()) {
Repository repo = new Repository();
long id = c.getLong(c.getColumnIndexOrThrow(RepositoriesColumns._ID));
repo.url = c.getString(c.getColumnIndexOrThrow(RepositoriesColumns.URL));
repo.name = c.getString(c.getColumnIndexOrThrow(RepositoriesColumns.TITLE));
repo.partialUrl = c.getString(c.getColumnIndexOrThrow(RepositoriesColumns.PARTIAL_URL));
repo.version = c.getString(c.getColumnIndexOrThrow(RepositoriesColumns.VERSION));
result.put(id, repo);
}
c.close();
return result;
}
public static void updateRepository(long repoId, Repository repository) {
ContentValues values = new ContentValues();
values.put(RepositoriesColumns.TITLE, repository.name);
values.put(RepositoriesColumns.PARTIAL_URL, repository.partialUrl);
values.put(RepositoriesColumns.VERSION, repository.version);
db.update(RepositoriesColumns.TABLE_NAME, values, RepositoriesColumns._ID + " = ?", new String[]{Long.toString(repoId)});
}
public static void updateRepositoryVersion(long repoId, String version) {
ContentValues values = new ContentValues();
values.put(RepositoriesColumns.VERSION, version);
db.update(RepositoriesColumns.TABLE_NAME, values, RepositoriesColumns._ID + " = ?", new String[]{Long.toString(repoId)});
}
@SuppressWarnings("UnusedReturnValue")
public static long insertModule(long repoId, Module mod) {
ContentValues values = new ContentValues();
values.put(ModulesColumns.REPO_ID, repoId);
values.put(ModulesColumns.PKGNAME, mod.packageName);
values.put(ModulesColumns.TITLE, mod.name);
values.put(ModulesColumns.SUMMARY, mod.summary);
values.put(ModulesColumns.DESCRIPTION, mod.description);
values.put(ModulesColumns.DESCRIPTION_IS_HTML, mod.descriptionIsHtml);
values.put(ModulesColumns.AUTHOR, mod.author);
values.put(ModulesColumns.SUPPORT, mod.support);
values.put(ModulesColumns.CREATED, mod.created);
values.put(ModulesColumns.UPDATED, mod.updated);
ModuleVersion latestVersion = RepoLoader.getInstance().getLatestVersion(mod);
db.beginTransaction();
try {
long moduleId = db.insertOrThrow(ModulesColumns.TABLE_NAME, null, values);
long latestVersionId = -1;
for (ModuleVersion version : mod.versions) {
long versionId = insertModuleVersion(moduleId, version);
if (latestVersion == version)
latestVersionId = versionId;
}
if (latestVersionId > -1) {
values = new ContentValues();
values.put(ModulesColumns.LATEST_VERSION, latestVersionId);
db.update(ModulesColumns.TABLE_NAME, values, ModulesColumns._ID + " = ?", new String[]{Long.toString(moduleId)});
}
for (Pair<String, String> moreInfoEntry : mod.moreInfo) {
insertMoreInfo(moduleId, moreInfoEntry.first, moreInfoEntry.second);
}
// TODO Add mod.screenshots
db.setTransactionSuccessful();
return moduleId;
} finally {
db.endTransaction();
}
}
private static long insertModuleVersion(long moduleId, ModuleVersion version) {
ContentValues values = new ContentValues();
values.put(ModuleVersionsColumns.MODULE_ID, moduleId);
values.put(ModuleVersionsColumns.NAME, version.name);
values.put(ModuleVersionsColumns.CODE, version.code);
values.put(ModuleVersionsColumns.DOWNLOAD_LINK, version.downloadLink);
values.put(ModuleVersionsColumns.MD5SUM, version.md5sum);
values.put(ModuleVersionsColumns.CHANGELOG, version.changelog);
values.put(ModuleVersionsColumns.CHANGELOG_IS_HTML, version.changelogIsHtml);
values.put(ModuleVersionsColumns.RELTYPE, version.relType.ordinal());
values.put(ModuleVersionsColumns.UPLOADED, version.uploaded);
return db.insertOrThrow(ModuleVersionsColumns.TABLE_NAME, null,
values);
}
@SuppressWarnings("UnusedReturnValue")
private static long insertMoreInfo(long moduleId, String title, String value) {
ContentValues values = new ContentValues();
values.put(MoreInfoColumns.MODULE_ID, moduleId);
values.put(MoreInfoColumns.LABEL, title);
values.put(MoreInfoColumns.VALUE, value);
return db.insertOrThrow(MoreInfoColumns.TABLE_NAME, null, values);
}
public static void deleteAllModules(long repoId) {
db.delete(ModulesColumns.TABLE_NAME, ModulesColumns.REPO_ID + " = ?", new String[]{Long.toString(repoId)});
}
public static void deleteModule(long repoId, String packageName) {
db.delete(ModulesColumns.TABLE_NAME, ModulesColumns.REPO_ID + " = ? AND " + ModulesColumns.PKGNAME + " = ?", new String[]{Long.toString(repoId), packageName});
}
public static Module getModuleByPackageName(String packageName) {
// The module itself
String[] projection = new String[]{
ModulesColumns._ID,
ModulesColumns.REPO_ID,
ModulesColumns.PKGNAME,
ModulesColumns.TITLE,
ModulesColumns.SUMMARY,
ModulesColumns.DESCRIPTION,
ModulesColumns.DESCRIPTION_IS_HTML,
ModulesColumns.AUTHOR,
ModulesColumns.SUPPORT,
ModulesColumns.CREATED,
ModulesColumns.UPDATED,
};
String where = ModulesColumns.PREFERRED + " = 1 AND " + ModulesColumns.PKGNAME + " = ?";
String[] whereArgs = new String[]{packageName};
Cursor c = db.query(ModulesColumns.TABLE_NAME, projection, where, whereArgs, null, null, null, "1");
if (!c.moveToFirst()) {
c.close();
return null;
}
long moduleId = c.getLong(c.getColumnIndexOrThrow(ModulesColumns._ID));
long repoId = c.getLong(c.getColumnIndexOrThrow(ModulesColumns.REPO_ID));
Module mod = new Module(RepoLoader.getInstance().getRepository(repoId));
mod.packageName = c.getString(c.getColumnIndexOrThrow(ModulesColumns.PKGNAME));
mod.name = c.getString(c.getColumnIndexOrThrow(ModulesColumns.TITLE));
mod.summary = c.getString(c.getColumnIndexOrThrow(ModulesColumns.SUMMARY));
mod.description = c.getString(c.getColumnIndexOrThrow(ModulesColumns.DESCRIPTION));
mod.descriptionIsHtml = c.getInt(c.getColumnIndexOrThrow(ModulesColumns.DESCRIPTION_IS_HTML)) > 0;
mod.author = c.getString(c.getColumnIndexOrThrow(ModulesColumns.AUTHOR));
mod.support = c.getString(c.getColumnIndexOrThrow(ModulesColumns.SUPPORT));
mod.created = c.getLong(c.getColumnIndexOrThrow(ModulesColumns.CREATED));
mod.updated = c.getLong(c.getColumnIndexOrThrow(ModulesColumns.UPDATED));
c.close();
// Versions
projection = new String[]{
ModuleVersionsColumns.NAME,
ModuleVersionsColumns.CODE, ModuleVersionsColumns.DOWNLOAD_LINK,
ModuleVersionsColumns.MD5SUM, ModuleVersionsColumns.CHANGELOG,
ModuleVersionsColumns.CHANGELOG_IS_HTML,
ModuleVersionsColumns.RELTYPE,
ModuleVersionsColumns.UPLOADED,
};
where = ModuleVersionsColumns.MODULE_ID + " = ?";
whereArgs = new String[]{Long.toString(moduleId)};
c = db.query(ModuleVersionsColumns.TABLE_NAME, projection, where, whereArgs, null, null, null);
while (c.moveToNext()) {
ModuleVersion version = new ModuleVersion(mod);
version.name = c.getString(c.getColumnIndexOrThrow(ModuleVersionsColumns.NAME));
version.code = c.getInt(c.getColumnIndexOrThrow(ModuleVersionsColumns.CODE));
version.downloadLink = c.getString(c.getColumnIndexOrThrow(ModuleVersionsColumns.DOWNLOAD_LINK));
version.md5sum = c.getString(c.getColumnIndexOrThrow(ModuleVersionsColumns.MD5SUM));
version.changelog = c.getString(c.getColumnIndexOrThrow(ModuleVersionsColumns.CHANGELOG));
version.changelogIsHtml = c.getInt(c.getColumnIndexOrThrow(ModuleVersionsColumns.CHANGELOG_IS_HTML)) > 0;
version.relType = ReleaseType.fromOrdinal(c.getInt(c.getColumnIndexOrThrow(ModuleVersionsColumns.RELTYPE)));
version.uploaded = c.getLong(c.getColumnIndexOrThrow(ModuleVersionsColumns.UPLOADED));
mod.versions.add(version);
}
c.close();
// MoreInfo
projection = new String[]{
MoreInfoColumns.LABEL,
MoreInfoColumns.VALUE,
};
where = MoreInfoColumns.MODULE_ID + " = ?";
whereArgs = new String[]{Long.toString(moduleId)};
c = db.query(MoreInfoColumns.TABLE_NAME, projection, where, whereArgs, null, null, MoreInfoColumns._ID);
while (c.moveToNext()) {
String label = c.getString(c.getColumnIndexOrThrow(MoreInfoColumns.LABEL));
String value = c.getString(c.getColumnIndexOrThrow(MoreInfoColumns.VALUE));
mod.moreInfo.add(new Pair<>(label, value));
}
c.close();
return mod;
}
public static String getModuleSupport(String packageName) {
return getString(ModulesColumns.TABLE_NAME, ModulesColumns.PKGNAME, packageName, ModulesColumns.SUPPORT);
}
public static void updateModuleLatestVersion(String packageName) {
int maxShownReleaseType = RepoLoader.getInstance().getMaxShownReleaseType(packageName).ordinal();
db.execSQL("UPDATE " + ModulesColumns.TABLE_NAME
+ " SET " + ModulesColumns.LATEST_VERSION
+ " = (SELECT " + ModuleVersionsColumns._ID + " FROM " + ModuleVersionsColumns.TABLE_NAME + " AS v"
+ " WHERE v." + ModuleVersionsColumns.MODULE_ID
+ " = " + ModulesColumns.TABLE_NAME + "." + ModulesColumns._ID
+ " AND reltype <= ? LIMIT 1)"
+ " WHERE " + ModulesColumns.PKGNAME + " = ?",
new Object[]{maxShownReleaseType, packageName});
}
public static void updateAllModulesLatestVersion() {
db.beginTransaction();
try {
String[] projection = new String[]{ModulesColumns.PKGNAME};
Cursor c = db.query(true, ModulesColumns.TABLE_NAME, projection, null, null, null, null, null, null);
while (c.moveToNext()) {
updateModuleLatestVersion(c.getString(0));
}
c.close();
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}
}
@SuppressWarnings("UnusedReturnValue")
public static long insertInstalledModule(InstalledModule installed) {
ContentValues values = new ContentValues();
values.put(InstalledModulesColumns.PKGNAME, installed.packageName);
values.put(InstalledModulesColumns.VERSION_CODE, installed.versionCode);
values.put(InstalledModulesColumns.VERSION_NAME, installed.versionName);
return db.insertOrThrow(InstalledModulesColumns.TABLE_NAME, null, values);
}
public static void deleteInstalledModule(String packageName) {
db.delete(InstalledModulesColumns.TABLE_NAME, InstalledModulesColumns.PKGNAME + " = ?", new String[]{packageName});
}
public static void deleteAllInstalledModules() {
db.delete(InstalledModulesColumns.TABLE_NAME, null, null);
}
public static Cursor queryModuleOverview(int sortingOrder,
CharSequence filterText) {
// Columns
String[] projection = new String[]{
"m." + ModulesColumns._ID,
"m." + ModulesColumns.PKGNAME,
"m." + ModulesColumns.TITLE,
"m." + ModulesColumns.SUMMARY,
"m." + ModulesColumns.CREATED,
"m." + ModulesColumns.UPDATED,
"v." + ModuleVersionsColumns.NAME + " AS " + OverviewColumns.LATEST_VERSION,
"i." + InstalledModulesColumns.VERSION_NAME + " AS " + OverviewColumns.INSTALLED_VERSION,
"(CASE WHEN m." + ModulesColumns.PKGNAME + " = '" + ModuleUtil.getInstance().getFrameworkPackageName()
+ "' THEN 1 ELSE 0 END) AS " + OverviewColumns.IS_FRAMEWORK,
"(CASE WHEN i." + InstalledModulesColumns.VERSION_NAME + " IS NOT NULL"
+ " THEN 1 ELSE 0 END) AS " + OverviewColumns.IS_INSTALLED,
"(CASE WHEN v." + ModuleVersionsColumns.CODE + " > " + InstalledModulesColumns.VERSION_CODE
+ " THEN 1 ELSE 0 END) AS " + OverviewColumns.HAS_UPDATE,
};
// Conditions
StringBuilder where = new StringBuilder(ModulesColumns.PREFERRED + " = 1");
String[] whereArgs = null;
if (!TextUtils.isEmpty(filterText)) {
where.append(" AND (m." + ModulesColumns.TITLE + " LIKE ?" + " OR m." + ModulesColumns.SUMMARY + " LIKE ?" + " OR m." + ModulesColumns.DESCRIPTION + " LIKE ?" + " OR m." + ModulesColumns.AUTHOR + " LIKE ?)");
String filterTextArg = "%" + filterText + "%";
whereArgs = new String[]{filterTextArg, filterTextArg, filterTextArg, filterTextArg};
} else {
SharedPreferences prefs = context.getSharedPreferences(BuildConfig.APPLICATION_ID + "_preferences", MODE_PRIVATE);
if (prefs.getBoolean("ignore_chinese", false)) {
for (char ch : "的一是不了人我在有他这为中设微模块淘".toCharArray()) {
where.append(" AND NOT (m." + ModulesColumns.TITLE + " LIKE '%").append(ch).append("%'").append(" OR m.").append(ModulesColumns.SUMMARY).append(" LIKE '%").append(ch).append("%'").append(" OR m.").append(ModulesColumns.DESCRIPTION).append(" LIKE '%").append(ch).append("%')");
}
}
}
// Sorting order
StringBuilder sbOrder = new StringBuilder();
if (sortingOrder == SORT_CREATED) {
sbOrder.append(OverviewColumns.CREATED);
sbOrder.append(" DESC,");
} else if (sortingOrder == SORT_UPDATED) {
sbOrder.append(OverviewColumns.UPDATED);
sbOrder.append(" DESC,");
}
sbOrder.append(OverviewColumns.IS_FRAMEWORK);
sbOrder.append(" DESC, ");
sbOrder.append(OverviewColumns.HAS_UPDATE);
sbOrder.append(" DESC, ");
sbOrder.append(OverviewColumns.IS_INSTALLED);
sbOrder.append(" DESC, ");
sbOrder.append("m.");
sbOrder.append(OverviewColumns.TITLE);
sbOrder.append(" COLLATE NOCASE, ");
sbOrder.append("m.");
sbOrder.append(OverviewColumns.PKGNAME);
// Query
Cursor c = db.query(
ModulesColumns.TABLE_NAME + " AS m"
+ " LEFT JOIN " + ModuleVersionsColumns.TABLE_NAME + " AS v"
+ " ON v." + ModuleVersionsColumns._ID + " = m." + ModulesColumns.LATEST_VERSION
+ " LEFT JOIN " + InstalledModulesColumns.TABLE_NAME + " AS i"
+ " ON i." + InstalledModulesColumns.PKGNAME + " = m." + ModulesColumns.PKGNAME,
projection, where.toString(), whereArgs, null, null, sbOrder.toString());
// Cache column indexes
OverviewColumnsIndexes.fillFromCursor(c);
return c;
}
public static String getFrameworkUpdateVersion() {
return getFirstUpdate(true);
}
public static boolean hasModuleUpdates() {
return getFirstUpdate(false) != null;
}
private static String getFirstUpdate(boolean framework) {
String[] projection = new String[]{InstalledModulesUpdatesColumns.LATEST_NAME};
String where = ModulesColumns.PKGNAME + (framework ? " = ?" : " != ?");
String[] whereArgs = new String[]{ModuleUtil.getInstance().getFrameworkPackageName()};
Cursor c = db.query(InstalledModulesUpdatesColumns.VIEW_NAME, projection, where, whereArgs, null, null, null, "1");
String latestVersion = null;
if (c.moveToFirst())
latestVersion = c.getString(c.getColumnIndexOrThrow(InstalledModulesUpdatesColumns.LATEST_NAME));
c.close();
return latestVersion;
}
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL(RepoDbDefinitions.SQL_CREATE_TABLE_REPOSITORIES);
db.execSQL(RepoDbDefinitions.SQL_CREATE_TABLE_MODULES);
db.execSQL(RepoDbDefinitions.SQL_CREATE_TABLE_MODULE_VERSIONS);
db.execSQL(RepoDbDefinitions.SQL_CREATE_INDEX_MODULE_VERSIONS_MODULE_ID);
db.execSQL(RepoDbDefinitions.SQL_CREATE_TABLE_MORE_INFO);
RepoLoader.getInstance().clear(false);
}
private void createTempTables(SQLiteDatabase db) {
db.execSQL(RepoDbDefinitions.SQL_CREATE_TEMP_TABLE_INSTALLED_MODULES);
db.execSQL(RepoDbDefinitions.SQL_CREATE_TEMP_VIEW_INSTALLED_MODULES_UPDATES);
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
// This is only a cache, so simply drop & recreate the tables
db.execSQL("DROP TABLE IF EXISTS " + RepositoriesColumns.TABLE_NAME);
db.execSQL("DROP TABLE IF EXISTS " + ModulesColumns.TABLE_NAME);
db.execSQL("DROP TABLE IF EXISTS " + ModuleVersionsColumns.TABLE_NAME);
db.execSQL("DROP TABLE IF EXISTS " + MoreInfoColumns.TABLE_NAME);
db.execSQL("DROP TABLE IF EXISTS " + InstalledModulesColumns.TABLE_NAME);
db.execSQL("DROP VIEW IF EXISTS " + InstalledModulesUpdatesColumns.VIEW_NAME);
onCreate(db);
}
@Override
public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {
onUpgrade(db, oldVersion, newVersion);
}
public static class RowNotFoundException extends RuntimeException {
private static final long serialVersionUID = -396324186622439535L;
RowNotFoundException(String reason) {
super(reason);
}
}
}

View File

@ -1,216 +0,0 @@
package org.meowcat.edxposed.manager.repo;
import android.database.Cursor;
import android.provider.BaseColumns;
public class RepoDbDefinitions {
static final int DATABASE_VERSION = 4;
static final String DATABASE_NAME = "repo_cache.db";
static final String SQL_CREATE_TABLE_REPOSITORIES = "CREATE TABLE "
+ RepositoriesColumns.TABLE_NAME + " (" + RepositoriesColumns._ID
+ " INTEGER PRIMARY KEY AUTOINCREMENT," + RepositoriesColumns.URL
+ " TEXT NOT NULL, " + RepositoriesColumns.TITLE + " TEXT, "
+ RepositoriesColumns.PARTIAL_URL + " TEXT, "
+ RepositoriesColumns.VERSION + " TEXT, " + "UNIQUE ("
+ RepositoriesColumns.URL + ") ON CONFLICT REPLACE)";
static final String SQL_CREATE_TABLE_MODULES = "CREATE TABLE "
+ ModulesColumns.TABLE_NAME + " (" + ModulesColumns._ID
+ " INTEGER PRIMARY KEY AUTOINCREMENT," +
ModulesColumns.REPO_ID + " INTEGER NOT NULL REFERENCES "
+ RepositoriesColumns.TABLE_NAME + " ON DELETE CASCADE, "
+ ModulesColumns.PKGNAME + " TEXT NOT NULL, " + ModulesColumns.TITLE
+ " TEXT NOT NULL, " + ModulesColumns.SUMMARY + " TEXT, "
+ ModulesColumns.DESCRIPTION + " TEXT, "
+ ModulesColumns.DESCRIPTION_IS_HTML + " INTEGER DEFAULT 0, "
+ ModulesColumns.AUTHOR + " TEXT, " + ModulesColumns.SUPPORT
+ " TEXT, " + ModulesColumns.CREATED + " INTEGER DEFAULT -1, "
+ ModulesColumns.UPDATED + " INTEGER DEFAULT -1, "
+ ModulesColumns.PREFERRED + " INTEGER DEFAULT 1, "
+ ModulesColumns.LATEST_VERSION + " INTEGER REFERENCES "
+ ModuleVersionsColumns.TABLE_NAME + ", " + "UNIQUE ("
+ ModulesColumns.PKGNAME + ", " + ModulesColumns.REPO_ID
+ ") ON CONFLICT REPLACE)";
static final String SQL_CREATE_TABLE_MODULE_VERSIONS = "CREATE TABLE "
+ ModuleVersionsColumns.TABLE_NAME + " ("
+ ModuleVersionsColumns._ID + " INTEGER PRIMARY KEY AUTOINCREMENT,"
+ ModuleVersionsColumns.MODULE_ID + " INTEGER NOT NULL REFERENCES "
+ ModulesColumns.TABLE_NAME + " ON DELETE CASCADE, "
+ ModuleVersionsColumns.NAME + " TEXT NOT NULL, "
+ ModuleVersionsColumns.CODE + " INTEGER NOT NULL, "
+ ModuleVersionsColumns.DOWNLOAD_LINK + " TEXT, "
+ ModuleVersionsColumns.MD5SUM + " TEXT, "
+ ModuleVersionsColumns.CHANGELOG + " TEXT, "
+ ModuleVersionsColumns.CHANGELOG_IS_HTML + " INTEGER DEFAULT 0, "
+ ModuleVersionsColumns.RELTYPE + " INTEGER DEFAULT 0, "
+ ModuleVersionsColumns.UPLOADED + " INTEGER DEFAULT -1)";
static final String SQL_CREATE_INDEX_MODULE_VERSIONS_MODULE_ID = "CREATE INDEX "
+ ModuleVersionsColumns.IDX_MODULE_ID + " ON "
+ ModuleVersionsColumns.TABLE_NAME + " ("
+ ModuleVersionsColumns.MODULE_ID + ")";
static final String SQL_CREATE_TABLE_MORE_INFO = "CREATE TABLE "
+ MoreInfoColumns.TABLE_NAME + " (" + MoreInfoColumns._ID
+ " INTEGER PRIMARY KEY AUTOINCREMENT," + MoreInfoColumns.MODULE_ID
+ " INTEGER NOT NULL REFERENCES " + ModulesColumns.TABLE_NAME
+ " ON DELETE CASCADE, " + MoreInfoColumns.LABEL
+ " TEXT NOT NULL, " + MoreInfoColumns.VALUE + " TEXT)";
static final String SQL_CREATE_TEMP_TABLE_INSTALLED_MODULES = "CREATE TEMP TABLE "
+ InstalledModulesColumns.TABLE_NAME + " ("
+ InstalledModulesColumns.PKGNAME
+ " TEXT PRIMARY KEY ON CONFLICT REPLACE, "
+ InstalledModulesColumns.VERSION_CODE + " INTEGER NOT NULL, "
+ InstalledModulesColumns.VERSION_NAME + " TEXT)";
static final String SQL_CREATE_TEMP_VIEW_INSTALLED_MODULES_UPDATES = "CREATE TEMP VIEW "
+ InstalledModulesUpdatesColumns.VIEW_NAME + " AS SELECT " + "m."
+ ModulesColumns._ID + " AS "
+ InstalledModulesUpdatesColumns.MODULE_ID + ", " + "i."
+ InstalledModulesColumns.PKGNAME + " AS "
+ InstalledModulesUpdatesColumns.PKGNAME + ", " + "i."
+ InstalledModulesColumns.VERSION_CODE + " AS "
+ InstalledModulesUpdatesColumns.INSTALLED_CODE + ", " + "i."
+ InstalledModulesColumns.VERSION_NAME + " AS "
+ InstalledModulesUpdatesColumns.INSTALLED_NAME + ", " + "v."
+ ModuleVersionsColumns._ID + " AS "
+ InstalledModulesUpdatesColumns.LATEST_ID + ", " + "v."
+ ModuleVersionsColumns.CODE + " AS "
+ InstalledModulesUpdatesColumns.LATEST_CODE + ", " + "v."
+ ModuleVersionsColumns.NAME + " AS "
+ InstalledModulesUpdatesColumns.LATEST_NAME + " FROM "
+ InstalledModulesColumns.TABLE_NAME + " AS i" + " INNER JOIN "
+ ModulesColumns.TABLE_NAME + " AS m" + " ON m."
+ ModulesColumns.PKGNAME + " = i." + InstalledModulesColumns.PKGNAME
+ " INNER JOIN " + ModuleVersionsColumns.TABLE_NAME + " AS v"
+ " ON v." + ModuleVersionsColumns._ID + " = m."
+ ModulesColumns.LATEST_VERSION + " WHERE "
+ InstalledModulesUpdatesColumns.LATEST_CODE + " > "
+ InstalledModulesUpdatesColumns.INSTALLED_CODE + " AND "
+ ModulesColumns.PREFERRED + " = 1";
//////////////////////////////////////////////////////////////////////////
public interface RepositoriesColumns extends BaseColumns {
String TABLE_NAME = "repositories";
String URL = "url";
String TITLE = "title";
String PARTIAL_URL = "partial_url";
String VERSION = "version";
}
//////////////////////////////////////////////////////////////////////////
public interface ModulesColumns extends BaseColumns {
String TABLE_NAME = "modules";
String REPO_ID = "repo_id";
String PKGNAME = "pkgname";
String TITLE = "title";
String SUMMARY = "summary";
String DESCRIPTION = "description";
String DESCRIPTION_IS_HTML = "description_is_html";
String AUTHOR = "author";
String SUPPORT = "support";
String CREATED = "created";
String UPDATED = "updated";
String PREFERRED = "preferred";
String LATEST_VERSION = "latest_version_id";
}
//////////////////////////////////////////////////////////////////////////
public interface ModuleVersionsColumns extends BaseColumns {
String TABLE_NAME = "module_versions";
String IDX_MODULE_ID = "module_versions_module_id_idx";
String MODULE_ID = "module_id";
String NAME = "name";
String CODE = "code";
String DOWNLOAD_LINK = "download_link";
String MD5SUM = "md5sum";
String CHANGELOG = "changelog";
String CHANGELOG_IS_HTML = "changelog_is_html";
String RELTYPE = "reltype";
String UPLOADED = "uploaded";
}
//////////////////////////////////////////////////////////////////////////
public interface MoreInfoColumns extends BaseColumns {
String TABLE_NAME = "more_info";
String MODULE_ID = "module_id";
String LABEL = "label";
String VALUE = "value";
}
//////////////////////////////////////////////////////////////////////////
public interface InstalledModulesColumns {
String TABLE_NAME = "installed_modules";
String PKGNAME = "pkgname";
String VERSION_CODE = "version_code";
String VERSION_NAME = "version_name";
}
//////////////////////////////////////////////////////////////////////////
public interface InstalledModulesUpdatesColumns {
String VIEW_NAME = InstalledModulesColumns.TABLE_NAME + "_updates";
String MODULE_ID = "module_id";
String PKGNAME = "pkgname";
String INSTALLED_CODE = "installed_code";
String INSTALLED_NAME = "installed_name";
String LATEST_ID = "latest_id";
String LATEST_CODE = "latest_code";
String LATEST_NAME = "latest_name";
}
//////////////////////////////////////////////////////////////////////////
public interface OverviewColumns extends BaseColumns {
String PKGNAME = ModulesColumns.PKGNAME;
String TITLE = ModulesColumns.TITLE;
String SUMMARY = ModulesColumns.SUMMARY;
String CREATED = ModulesColumns.CREATED;
String UPDATED = ModulesColumns.UPDATED;
String INSTALLED_VERSION = "installed_version";
String LATEST_VERSION = "latest_version";
String IS_FRAMEWORK = "is_framework";
String IS_INSTALLED = "is_installed";
String HAS_UPDATE = "has_update";
}
public static class OverviewColumnsIndexes {
public static int PKGNAME = -1;
public static int TITLE = -1;
public static int SUMMARY = -1;
public static int CREATED = -1;
public static int UPDATED = -1;
public static int INSTALLED_VERSION = -1;
public static int LATEST_VERSION = -1;
public static int IS_FRAMEWORK = -1;
public static int IS_INSTALLED = -1;
public static int HAS_UPDATE = -1;
private static boolean isFilled = false;
private OverviewColumnsIndexes() {
}
static void fillFromCursor(Cursor cursor) {
if (isFilled || cursor == null)
return;
PKGNAME = cursor.getColumnIndexOrThrow(OverviewColumns.PKGNAME);
TITLE = cursor.getColumnIndexOrThrow(OverviewColumns.TITLE);
SUMMARY = cursor.getColumnIndexOrThrow(OverviewColumns.SUMMARY);
CREATED = cursor.getColumnIndexOrThrow(OverviewColumns.CREATED);
UPDATED = cursor.getColumnIndexOrThrow(OverviewColumns.UPDATED);
INSTALLED_VERSION = cursor.getColumnIndexOrThrow(OverviewColumns.INSTALLED_VERSION);
LATEST_VERSION = cursor.getColumnIndexOrThrow(OverviewColumns.LATEST_VERSION);
INSTALLED_VERSION = cursor.getColumnIndexOrThrow(OverviewColumns.INSTALLED_VERSION);
IS_FRAMEWORK = cursor.getColumnIndexOrThrow(OverviewColumns.IS_FRAMEWORK);
IS_INSTALLED = cursor.getColumnIndexOrThrow(OverviewColumns.IS_INSTALLED);
HAS_UPDATE = cursor.getColumnIndexOrThrow(OverviewColumns.HAS_UPDATE);
isFilled = true;
}
}
}

View File

@ -1,320 +0,0 @@
package org.meowcat.edxposed.manager.repo;
import android.app.Activity;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Point;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.LevelListDrawable;
import android.text.SpannableStringBuilder;
import android.text.Spanned;
import android.util.Log;
import android.util.Pair;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.text.HtmlCompat;
import com.bumptech.glide.Glide;
import com.bumptech.glide.request.target.CustomTarget;
import com.bumptech.glide.request.transition.Transition;
import org.meowcat.edxposed.manager.App;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlPullParserFactory;
import java.io.IOException;
import java.io.InputStream;
public class RepoParser {
public final static String TAG = App.TAG;
private final static String NS = null;
private final XmlPullParser parser;
private final RepoParserCallback callback;
private boolean mRepoEventTriggered = false;
private RepoParser(InputStream is, RepoParserCallback callback) throws XmlPullParserException, IOException {
XmlPullParserFactory factory = XmlPullParserFactory.newInstance();
parser = factory.newPullParser();
parser.setInput(is, null);
parser.nextTag();
this.callback = callback;
}
public static void parse(InputStream is, RepoParserCallback callback) throws XmlPullParserException, IOException {
new RepoParser(is, callback).readRepo();
}
public static Spanned parseSimpleHtml(final Context context, String source, final TextView textView) {
source = source.replaceAll("<li>", "\t\u0095 ");
source = source.replaceAll("</li>", "<br>");
Spanned html = HtmlCompat.fromHtml(source, HtmlCompat.FROM_HTML_MODE_LEGACY, source1 -> {
LevelListDrawable levelListDrawable = new LevelListDrawable();
final Drawable[] drawable = new Drawable[1];
Glide.with(context).asBitmap().load(source1).into(new CustomTarget<Bitmap>() {
@Override
public void onResourceReady(@NonNull Bitmap bitmap, @Nullable Transition<? super Bitmap> transition) {
try {
drawable[0] = new BitmapDrawable(context.getResources(), bitmap);
Point size = new Point();
((Activity) context).getWindowManager().getDefaultDisplay().getSize(size);
int multiplier = size.x / bitmap.getWidth();
if (multiplier <= 0) multiplier = 1;
levelListDrawable.addLevel(1, 1, drawable[0]);
levelListDrawable.setBounds(0, 0, bitmap.getWidth() * multiplier, bitmap.getHeight() * multiplier);
levelListDrawable.setLevel(1);
textView.setText(textView.getText());
} catch (Exception ignored) { /* Like a null bitmap, etc. */
}
}
@Override
public void onLoadCleared(@Nullable Drawable placeholder) {
}
});
return drawable[0];
}, null);
// trim trailing newlines
int len = html.length();
int end = len;
for (int i = len - 1; i >= 0; i--) {
if (html.charAt(i) != '\n')
break;
end = i;
}
if (end == len)
return html;
else
return new SpannableStringBuilder(html, 0, end);
}
private void readRepo() throws XmlPullParserException, IOException {
parser.require(XmlPullParser.START_TAG, NS, "repository");
Repository repository = new Repository();
repository.isPartial = "true".equals(parser.getAttributeValue(NS, "partial"));
repository.partialUrl = parser.getAttributeValue(NS, "partial-url");
repository.version = parser.getAttributeValue(NS, "version");
while (parser.nextTag() == XmlPullParser.START_TAG) {
String tagName = parser.getName();
switch (tagName) {
case "name":
repository.name = parser.nextText();
break;
case "module":
triggerRepoEvent(repository);
Module module = readModule(repository);
if (module != null)
callback.onNewModule(module);
break;
case "remove-module":
triggerRepoEvent(repository);
String packageName = readRemoveModule();
if (packageName != null)
callback.onRemoveModule(packageName);
break;
default:
//skip(true);
skip(false);
break;
}
}
callback.onCompleted(repository);
}
private void triggerRepoEvent(Repository repository) {
if (mRepoEventTriggered)
return;
callback.onRepositoryMetadata(repository);
mRepoEventTriggered = true;
}
private Module readModule(Repository repository) throws XmlPullParserException, IOException {
parser.require(XmlPullParser.START_TAG, NS, "module");
final int startDepth = parser.getDepth();
Module module = new Module(repository);
module.packageName = parser.getAttributeValue(NS, "package");
if (module.packageName == null) {
logError("no package name defined");
leave(startDepth);
return null;
}
module.created = parseTimestamp("created");
module.updated = parseTimestamp("updated");
while (parser.nextTag() == XmlPullParser.START_TAG) {
String tagName = parser.getName();
switch (tagName) {
case "name":
module.name = parser.nextText();
break;
case "author":
module.author = parser.nextText();
break;
case "summary":
module.summary = parser.nextText();
break;
case "description":
String isHtml = parser.getAttributeValue(NS, "html");
if (isHtml != null && isHtml.equals("true"))
module.descriptionIsHtml = true;
module.description = parser.nextText();
break;
case "screenshot":
module.screenshots.add(parser.nextText());
break;
case "moreinfo":
String label = parser.getAttributeValue(NS, "label");
String role = parser.getAttributeValue(NS, "role");
String value = parser.nextText();
module.moreInfo.add(new Pair<>(label, value));
if (role != null && role.contains("support"))
module.support = value;
break;
case "version":
ModuleVersion version = readModuleVersion(module);
if (version != null)
module.versions.add(version);
break;
default:
//skip(true);
skip(false);
break;
}
}
if (module.name == null) {
logError("packages need at least a name");
return null;
}
return module;
}
private long parseTimestamp(String attName) {
String value = parser.getAttributeValue(NS, attName);
if (value == null)
return -1;
try {
return Long.parseLong(value) * 1000L;
} catch (NumberFormatException ex) {
return -1;
}
}
private ModuleVersion readModuleVersion(Module module) throws XmlPullParserException, IOException {
parser.require(XmlPullParser.START_TAG, NS, "version");
final int startDepth = parser.getDepth();
ModuleVersion version = new ModuleVersion(module);
version.uploaded = parseTimestamp("uploaded");
while (parser.nextTag() == XmlPullParser.START_TAG) {
String tagName = parser.getName();
switch (tagName) {
case "name":
version.name = parser.nextText();
break;
case "code":
try {
version.code = Integer.parseInt(parser.nextText());
} catch (NumberFormatException nfe) {
logError(nfe.getMessage());
leave(startDepth);
return null;
}
break;
case "reltype":
version.relType = ReleaseType.fromString(parser.nextText());
break;
case "download":
version.downloadLink = parser.nextText();
break;
case "md5sum":
version.md5sum = parser.nextText();
break;
case "changelog":
String isHtml = parser.getAttributeValue(NS, "html");
if (isHtml != null && isHtml.equals("true"))
version.changelogIsHtml = true;
version.changelog = parser.nextText();
break;
case "branch":
// obsolete
// skip(false);
// break;
default:
skip(false);
//skip(true);
break;
}
}
return version;
}
private String readRemoveModule() throws XmlPullParserException, IOException {
parser.require(XmlPullParser.START_TAG, NS, "remove-module");
final int startDepth = parser.getDepth();
String packageName = parser.getAttributeValue(NS, "package");
if (packageName == null) {
logError("no package name defined");
leave(startDepth);
return null;
}
return packageName;
}
private void skip(@SuppressWarnings("SameParameterValue") boolean showWarning) throws XmlPullParserException, IOException {
parser.require(XmlPullParser.START_TAG, null, null);
if (showWarning)
Log.w(TAG, "skipping unknown/erronous tag: " + parser.getPositionDescription());
int level = 1;
while (level > 0) {
int eventType = parser.next();
if (eventType == XmlPullParser.END_TAG) {
level--;
} else if (eventType == XmlPullParser.START_TAG) {
level++;
}
}
}
private void leave(int targetDepth) throws XmlPullParserException, IOException {
Log.w(TAG, "leaving up to level " + targetDepth + ": " + parser.getPositionDescription());
while (parser.getDepth() > targetDepth) {
//noinspection StatementWithEmptyBody
while (parser.next() != XmlPullParser.END_TAG) {
// do nothing
}
}
}
private void logError(String error) {
Log.e(TAG, parser.getPositionDescription() + ": " + error);
}
public interface RepoParserCallback {
void onRepositoryMetadata(Repository repository);
void onNewModule(Module module);
void onRemoveModule(String packageName);
void onCompleted(Repository repository);
}
}

View File

@ -1,12 +0,0 @@
package org.meowcat.edxposed.manager.repo;
public class Repository {
public String name;
public String url;
public boolean isPartial = false;
public String partialUrl;
public String version;
Repository() {
}
}

View File

@ -1,398 +0,0 @@
package org.meowcat.edxposed.manager.ui.activity;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.content.res.Resources;
import android.database.Cursor;
import android.net.ConnectivityManager;
import android.net.Uri;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.widget.SearchView;
import androidx.core.content.ContextCompat;
import androidx.recyclerview.widget.DividerItemDecoration;
import androidx.recyclerview.widget.RecyclerView;
import androidx.transition.TransitionManager;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.timehop.stickyheadersrecyclerview.StickyRecyclerHeadersAdapter;
import com.timehop.stickyheadersrecyclerview.StickyRecyclerHeadersDecoration;
import org.meowcat.edxposed.manager.App;
import org.meowcat.edxposed.manager.R;
import org.meowcat.edxposed.manager.adapters.CursorRecyclerViewAdapter;
import org.meowcat.edxposed.manager.databinding.ActivityDownloadBinding;
import org.meowcat.edxposed.manager.databinding.ItemDownloadBinding;
import org.meowcat.edxposed.manager.repo.RepoDb;
import org.meowcat.edxposed.manager.repo.RepoDbDefinitions;
import org.meowcat.edxposed.manager.util.LinearLayoutManagerFix;
import org.meowcat.edxposed.manager.util.ModuleUtil;
import org.meowcat.edxposed.manager.util.RepoLoader;
import java.text.DateFormat;
import java.util.Date;
import me.zhanghai.android.fastscroll.FastScrollerBuilder;
public class DownloadActivity extends BaseActivity implements RepoLoader.RepoListener, ModuleUtil.ModuleListener, SharedPreferences.OnSharedPreferenceChangeListener {
private DownloadsAdapter adapter;
private String filterText;
private RepoLoader repoLoader;
private ModuleUtil moduleUtil;
private int sortingOrder;
private SearchView searchView;
private SharedPreferences ignoredUpdatesPref;
private boolean changed = false;
private final BroadcastReceiver connectionListener = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if (repoLoader != null) {
repoLoader.triggerReload(true);
}
}
};
private ActivityDownloadBinding binding;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = ActivityDownloadBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
setSupportActionBar(binding.toolbar);
binding.toolbar.setNavigationOnClickListener(view -> finish());
ActionBar bar = getSupportActionBar();
if (bar != null) {
bar.setDisplayHomeAsUpEnabled(true);
}
setupWindowInsets(binding.snackbar, binding.recyclerView);
repoLoader = RepoLoader.getInstance();
moduleUtil = ModuleUtil.getInstance();
adapter = new DownloadsAdapter(this, RepoDb.queryModuleOverview(sortingOrder, filterText));
/*adapter.setFilterQueryProvider(new FilterQueryProvider() {
@Override
public Cursor runQuery(CharSequence constraint) {
return RepoDb.queryModuleOverview(sortingOrder, constraint);
}
});*/
sortingOrder = App.getPreferences().getInt("download_sorting_order", RepoDb.SORT_STATUS);
ignoredUpdatesPref = getSharedPreferences("update_ignored", MODE_PRIVATE);
binding.recyclerView.setImportantForAutofill(View.IMPORTANT_FOR_AUTOFILL_NO_EXCLUDE_DESCENDANTS);
binding.swipeRefreshLayout.setOnRefreshListener(() -> {
repoLoader.setSwipeRefreshLayout(binding.swipeRefreshLayout);
repoLoader.triggerReload(true);
});
repoLoader.addListener(this, true);
moduleUtil.addListener(this);
binding.recyclerView.setAdapter(adapter);
binding.recyclerView.setLayoutManager(new LinearLayoutManagerFix(this));
StickyRecyclerHeadersDecoration headersDecor = new StickyRecyclerHeadersDecoration(adapter);
binding.recyclerView.addItemDecoration(headersDecor);
adapter.registerAdapterDataObserver(new RecyclerView.AdapterDataObserver() {
@Override
public void onChanged() {
headersDecor.invalidateHeaders();
}
});
FastScrollerBuilder fastScrollerBuilder = new FastScrollerBuilder(binding.recyclerView);
if (!App.getPreferences().getBoolean("md2", false)) {
DividerItemDecoration dividerItemDecoration = new DividerItemDecoration(this,
DividerItemDecoration.VERTICAL);
binding.recyclerView.addItemDecoration(dividerItemDecoration);
} else {
fastScrollerBuilder.useMd2Style();
}
fastScrollerBuilder.build();
}
@Override
public void onResume() {
super.onResume();
ignoredUpdatesPref.registerOnSharedPreferenceChangeListener(this);
if (changed) {
reloadItems();
changed = !changed;
}
registerReceiver(connectionListener, new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION));
}
@Override
public void onPause() {
super.onPause();
unregisterReceiver(connectionListener);
}
@Override
public void onDestroy() {
super.onDestroy();
repoLoader.removeListener(this);
moduleUtil.removeListener(this);
ignoredUpdatesPref.unregisterOnSharedPreferenceChangeListener(this);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.menu_download, menu);
// Setup search button
searchView = (SearchView) menu.findItem(R.id.menu_search).getActionView();
searchView.setIconifiedByDefault(true);
searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
@Override
public boolean onQueryTextSubmit(String query) {
setFilter(query);
searchView.clearFocus();
return true;
}
@Override
public boolean onQueryTextChange(String newText) {
setFilter(newText);
return true;
}
});
return super.onCreateOptionsMenu(menu);
}
private void setFilter(String filterText) {
this.filterText = filterText;
reloadItems();
}
private void reloadItems() {
runOnUiThread(() -> {
adapter.changeCursor(RepoDb.queryModuleOverview(sortingOrder, filterText));
TransitionManager.beginDelayedTransition(binding.recyclerView);
adapter.notifyDataSetChanged();
});
}
@Override
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
if (item.getItemId() == R.id.menu_sort) {
new MaterialAlertDialogBuilder(this)
.setTitle(R.string.download_sorting_title)
.setSingleChoiceItems(R.array.download_sort_order, sortingOrder, (dialog, which) -> {
sortingOrder = which;
App.getPreferences().edit().putInt("download_sorting_order", sortingOrder).apply();
reloadItems();
dialog.dismiss();
})
.show();
return true;
}
return super.onOptionsItemSelected(item);
}
@Override
public void onRepoReloaded(final RepoLoader loader) {
reloadItems();
}
@Override
public void onSingleInstalledModuleReloaded(ModuleUtil moduleUtil, String packageName, ModuleUtil.InstalledModule module) {
reloadItems();
}
@Override
public void onInstalledModulesReloaded(ModuleUtil moduleUtil) {
reloadItems();
}
@Override
public void onModuleEnableChange(ModuleUtil moduleUtil) {
}
@Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
changed = true;
}
@Override
public void onBackPressed() {
if (searchView.isIconified()) {
super.onBackPressed();
} else {
searchView.setIconified(true);
}
}
private class DownloadsAdapter extends CursorRecyclerViewAdapter<DownloadsAdapter.ViewHolder> implements StickyRecyclerHeadersAdapter<DownloadsAdapter.HeaderViewHolder> {
private final Context context;
private final DateFormat dateFormatter = DateFormat.getDateInstance(DateFormat.SHORT);
private final SharedPreferences prefs;
private final String[] sectionHeaders;
DownloadsAdapter(Context context, Cursor cursor) {
super(cursor);
this.context = context;
prefs = context.getSharedPreferences("update_ignored", MODE_PRIVATE);
Resources res = context.getResources();
sectionHeaders = new String[]{
res.getString(R.string.download_section_framework),
res.getString(R.string.download_section_update_available),
res.getString(R.string.download_section_installed),
res.getString(R.string.download_section_not_installed),
res.getString(R.string.download_section_24h),
res.getString(R.string.download_section_7d),
res.getString(R.string.download_section_30d),
res.getString(R.string.download_section_older)};
}
@Override
public long getHeaderId(int position) {
Cursor cursor = getCursor();
cursor.moveToPosition(position);
long created = cursor.getLong(RepoDbDefinitions.OverviewColumnsIndexes.CREATED);
long updated = cursor.getLong(RepoDbDefinitions.OverviewColumnsIndexes.UPDATED);
boolean isFramework = cursor.getInt(RepoDbDefinitions.OverviewColumnsIndexes.IS_FRAMEWORK) > 0;
boolean isInstalled = cursor.getInt(RepoDbDefinitions.OverviewColumnsIndexes.IS_INSTALLED) > 0;
boolean updateIgnored = prefs.getBoolean(cursor.getString(RepoDbDefinitions.OverviewColumnsIndexes.PKGNAME), false);
boolean updateIgnorePreference = App.getPreferences().getBoolean("ignore_updates", false);
boolean hasUpdate = cursor.getInt(RepoDbDefinitions.OverviewColumnsIndexes.HAS_UPDATE) > 0;
if (hasUpdate && updateIgnored && updateIgnorePreference) {
hasUpdate = false;
}
if (sortingOrder != RepoDb.SORT_STATUS) {
long timestamp = (sortingOrder == RepoDb.SORT_UPDATED) ? updated : created;
long age = System.currentTimeMillis() - timestamp;
final long mSecsPerDay = 24 * 60 * 60 * 1000L;
if (age < mSecsPerDay)
return 4;
if (age < 7 * mSecsPerDay)
return 5;
if (age < 30 * mSecsPerDay)
return 6;
return 7;
} else {
if (isFramework)
return 0;
if (hasUpdate)
return 1;
else if (isInstalled)
return 2;
else
return 3;
}
}
@Override
public HeaderViewHolder onCreateHeaderViewHolder(ViewGroup parent) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.sticky_header_download, parent, false);
return new HeaderViewHolder(view);
}
@Override
public void onBindHeaderViewHolder(HeaderViewHolder viewHolder, int position) {
long section = getHeaderId(position);
viewHolder.title.setText(sectionHeaders[(int) section]);
}
@NonNull
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
ItemDownloadBinding binding = ItemDownloadBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false);
return new ViewHolder(binding);
}
@Override
public void onBindViewHolder(ViewHolder holder, Cursor cursor) {
String title = cursor.getString(RepoDbDefinitions.OverviewColumnsIndexes.TITLE);
String summary = cursor.getString(RepoDbDefinitions.OverviewColumnsIndexes.SUMMARY);
String installedVersion = cursor.getString(RepoDbDefinitions.OverviewColumnsIndexes.INSTALLED_VERSION);
String latestVersion = cursor.getString(RepoDbDefinitions.OverviewColumnsIndexes.LATEST_VERSION);
long created = cursor.getLong(RepoDbDefinitions.OverviewColumnsIndexes.CREATED);
long updated = cursor.getLong(RepoDbDefinitions.OverviewColumnsIndexes.UPDATED);
boolean isInstalled = cursor.getInt(RepoDbDefinitions.OverviewColumnsIndexes.IS_INSTALLED) > 0;
boolean updateIgnored = prefs.getBoolean(cursor.getString(RepoDbDefinitions.OverviewColumnsIndexes.PKGNAME), false);
boolean updateIgnorePreference = App.getPreferences().getBoolean("ignore_updates", false);
boolean hasUpdate = cursor.getInt(RepoDbDefinitions.OverviewColumnsIndexes.HAS_UPDATE) > 0;
if (hasUpdate && updateIgnored && updateIgnorePreference) {
hasUpdate = false;
}
TextView txtTitle = holder.appName;
txtTitle.setText(title);
TextView txtSummary = holder.appDescription;
txtSummary.setText(summary);
TextView txtStatus = holder.downloadStatus;
if (hasUpdate) {
txtStatus.setText(context.getString(
R.string.download_status_update_available,
installedVersion, latestVersion));
txtStatus.setTextColor(ContextCompat.getColor(DownloadActivity.this, R.color.download_status_update_available));
txtStatus.setVisibility(View.VISIBLE);
} else if (isInstalled) {
txtStatus.setText(context.getString(
R.string.download_status_installed, installedVersion));
txtStatus.setTextColor(ContextCompat.getColor(DownloadActivity.this, R.color.warning));
txtStatus.setTextColor(getThemedColor(android.R.attr.textColorHighlight));
txtStatus.setVisibility(View.VISIBLE);
} else {
txtStatus.setVisibility(View.GONE);
}
String creationDate = dateFormatter.format(new Date(created));
String updateDate = dateFormatter.format(new Date(updated));
holder.timestamps.setText(getString(R.string.download_timestamps, creationDate, updateDate));
String packageName = cursor.getString(RepoDbDefinitions.OverviewColumnsIndexes.PKGNAME);
holder.itemView.setOnClickListener(v -> {
Intent detailsIntent = new Intent(DownloadActivity.this, DownloadDetailsActivity.class);
detailsIntent.setData(Uri.fromParts("package", packageName, null));
startActivity(detailsIntent);
});
}
class ViewHolder extends RecyclerView.ViewHolder {
TextView appName;
TextView appDescription;
TextView downloadStatus;
TextView timestamps;
ViewHolder(ItemDownloadBinding binding) {
super(binding.getRoot());
appName = binding.title;
appDescription = binding.description;
downloadStatus = binding.downloadStatus;
timestamps = binding.timestamps;
}
}
class HeaderViewHolder extends RecyclerView.ViewHolder {
TextView title;
HeaderViewHolder(View view) {
super(view);
title = view.findViewById(android.R.id.title);
}
}
}
}

View File

@ -1,279 +0,0 @@
package org.meowcat.edxposed.manager.ui.activity;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Bundle;
import android.text.TextUtils;
import android.util.Log;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import androidx.annotation.NonNull;
import androidx.appcompat.app.ActionBar;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentPagerAdapter;
import org.meowcat.edxposed.manager.App;
import org.meowcat.edxposed.manager.R;
import org.meowcat.edxposed.manager.databinding.ActivityDownloadDetailsBinding;
import org.meowcat.edxposed.manager.databinding.ActivityDownloadDetailsNotFoundBinding;
import org.meowcat.edxposed.manager.repo.Module;
import org.meowcat.edxposed.manager.ui.fragment.DownloadDetailsFragment;
import org.meowcat.edxposed.manager.ui.fragment.DownloadDetailsSettingsFragment;
import org.meowcat.edxposed.manager.ui.fragment.DownloadDetailsVersionsFragment;
import org.meowcat.edxposed.manager.util.ModuleUtil;
import org.meowcat.edxposed.manager.util.RepoLoader;
import java.util.List;
import java.util.Objects;
public class DownloadDetailsActivity extends BaseActivity implements RepoLoader.RepoListener, ModuleUtil.ModuleListener {
public static final int DOWNLOAD_DESCRIPTION = 0;
public static final int DOWNLOAD_VERSIONS = 1;
public static final int DOWNLOAD_SETTINGS = 2;
static final String XPOSED_REPO_LINK = "http://repo.xposed.info/module/%s";
static final String PLAY_STORE_PACKAGE = "com.android.vending";
static final String PLAY_STORE_LINK = "https://play.google.com/store/apps/details?id=%s";
private static final String TAG = "DownloadDetailsActivity";
private static final RepoLoader repoLoader = RepoLoader.getInstance();
private static final ModuleUtil moduleUtil = ModuleUtil.getInstance();
private String packageName;
private Module module;
private ModuleUtil.InstalledModule installedModule;
private ActivityDownloadDetailsBinding binding;
@Override
public void onCreate(Bundle savedInstanceState) {
packageName = getModulePackageName();
try {
module = repoLoader.getModule(packageName);
} catch (Exception e) {
Log.i(App.TAG, "DownloadDetailsActivity -> " + e.getMessage());
module = null;
}
installedModule = ModuleUtil.getInstance().getModule(packageName);
super.onCreate(savedInstanceState);
repoLoader.addListener(this, false);
moduleUtil.addListener(this);
if (module != null) {
binding = ActivityDownloadDetailsBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
setSupportActionBar(binding.toolbar);
binding.toolbar.setNavigationOnClickListener(view -> finish());
ActionBar ab = getSupportActionBar();
if (ab != null) {
ab.setDisplayHomeAsUpEnabled(true);
}
setupTabs();
boolean directDownload = getIntent().getBooleanExtra("direct_download", false);
// Updates available => start on the versions page
if (installedModule != null && installedModule.isUpdate(repoLoader.getLatestVersion(module)) || directDownload)
binding.downloadPager.setCurrentItem(DOWNLOAD_VERSIONS);
} else {
ActivityDownloadDetailsNotFoundBinding binding = ActivityDownloadDetailsNotFoundBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
binding.message.setText(getResources().getString(R.string.download_details_not_found, packageName));
binding.reload.setOnClickListener(v -> {
v.setEnabled(false);
repoLoader.triggerReload(true);
});
}
setupWindowInsets(binding.snackbar, null);
}
private void setupTabs() {
binding.downloadPager.setAdapter(new SwipeFragmentPagerAdapter(getSupportFragmentManager()));
binding.slidingTabs.setupWithViewPager(binding.downloadPager);
}
private String getModulePackageName() {
Uri uri = getIntent().getData();
if (uri == null)
return null;
String scheme = uri.getScheme();
if (TextUtils.isEmpty(scheme)) {
return null;
} else switch (Objects.requireNonNull(scheme)) {
case "xposed":
case "package":
return uri.getSchemeSpecificPart().replace("//", "");
case "http":
List<String> segments = uri.getPathSegments();
if (segments.size() > 1)
return segments.get(1);
break;
}
return null;
}
@Override
protected void onDestroy() {
super.onDestroy();
repoLoader.removeListener(this);
moduleUtil.removeListener(this);
}
public Module getModule() {
return module;
}
public ModuleUtil.InstalledModule getInstalledModule() {
return installedModule;
}
public void gotoPage(int page) {
binding.downloadPager.setCurrentItem(page);
}
private void reload() {
runOnUiThread(this::recreate);
}
@Override
public void onRepoReloaded(RepoLoader loader) {
reload();
}
@Override
public void onInstalledModulesReloaded(ModuleUtil moduleUtil) {
reload();
}
@Override
public void onModuleEnableChange(ModuleUtil moduleUtil) {
}
@Override
public void onSingleInstalledModuleReloaded(ModuleUtil moduleUtil, String packageName, ModuleUtil.InstalledModule module) {
if (this.packageName.equals(packageName))
reload();
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.menu_download_details, menu);
boolean updateIgnorePreference = App.getPreferences().getBoolean("ignore_updates", false);
if (updateIgnorePreference) {
SharedPreferences prefs = getSharedPreferences("update_ignored", MODE_PRIVATE);
boolean ignored = prefs.getBoolean(module.packageName, false);
menu.findItem(R.id.ignoreUpdate).setChecked(ignored);
} else {
menu.removeItem(R.id.ignoreUpdate);
}
return true;
}
@Override
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
int itemId = item.getItemId();
if (itemId == R.id.menu_refresh) {
RepoLoader.getInstance().triggerReload(true);
return true;
} else if (itemId == R.id.menu_share) {
String text = module.name + " - ";
if (isPackageInstalled(packageName, this)) {
String s = getPackageManager().getInstallerPackageName(packageName);
boolean playStore;
try {
playStore = PLAY_STORE_PACKAGE.equals(s);
} catch (NullPointerException e) {
playStore = false;
}
if (playStore) {
text += String.format(PLAY_STORE_LINK, packageName);
} else {
text += String.format(XPOSED_REPO_LINK, packageName);
}
} else {
text += String.format(XPOSED_REPO_LINK,
packageName);
}
Intent sharingIntent = new Intent(Intent.ACTION_SEND);
sharingIntent.setType("text/plain");
sharingIntent.putExtra(Intent.EXTRA_TEXT, text);
startActivity(Intent.createChooser(sharingIntent, getString(R.string.share)));
return true;
} else if (itemId == R.id.ignoreUpdate) {
SharedPreferences prefs = getSharedPreferences("update_ignored", MODE_PRIVATE);
boolean ignored = prefs.getBoolean(module.packageName, false);
prefs.edit().putBoolean(module.packageName, !ignored).apply();
item.setChecked(!ignored);
}
return super.onOptionsItemSelected(item);
}
private boolean isPackageInstalled(String packagename, Context context) {
PackageManager pm = context.getPackageManager();
try {
pm.getPackageInfo(packagename, PackageManager.GET_ACTIVITIES);
return true;
} catch (PackageManager.NameNotFoundException e) {
return false;
}
}
class SwipeFragmentPagerAdapter extends FragmentPagerAdapter {
final int PAGE_COUNT = 3;
private final String[] tabTitles = new String[]{getString(R.string.download_details_page_description), getString(R.string.download_details_page_versions), getString(R.string.download_details_page_settings),};
SwipeFragmentPagerAdapter(FragmentManager fm) {
super(fm, FragmentPagerAdapter.BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT);
}
@Override
public int getCount() {
return PAGE_COUNT;
}
@NonNull
@Override
public Fragment getItem(int position) {
switch (position) {
case DOWNLOAD_DESCRIPTION:
return new DownloadDetailsFragment();
case DOWNLOAD_VERSIONS:
return new DownloadDetailsVersionsFragment();
case DOWNLOAD_SETTINGS:
return new DownloadDetailsSettingsFragment();
default:
//noinspection ConstantConditions
return null;
}
}
@Override
public CharSequence getPageTitle(int position) {
// Generate title based on item position
return tabTitles[position];
}
}
}

View File

@ -19,9 +19,8 @@ import org.meowcat.edxposed.manager.util.GlideHelper;
import org.meowcat.edxposed.manager.util.ModuleUtil;
import org.meowcat.edxposed.manager.util.light.Light;
public class MainActivity extends BaseActivity implements /*RepoLoader.RepoListener, */ModuleUtil.ModuleListener {
public class MainActivity extends BaseActivity implements ModuleUtil.ModuleListener {
ActivityMainBinding binding;
//private RepoLoader repoLoader;
@SuppressLint("PrivateResource")
@Override
@ -33,23 +32,15 @@ public class MainActivity extends BaseActivity implements /*RepoLoader.RepoListe
if (Light.setLightSourceAlpha(getWindow().getDecorView(), 0.01f, 0.029f)) {
binding.status.setElevation(24);
binding.modules.setElevation(12);
//binding.downloads.setElevation(12);
}
});
setupWindowInsets(binding.snackbar, null);
//repoLoader = RepoLoader.getInstance();
ModuleUtil.getInstance().addListener(this);
//repoLoader.addListener(this, false);
binding.modules.setOnClickListener(v -> {
Intent intent = new Intent();
intent.setClass(getApplicationContext(), ModulesActivity.class);
startActivity(intent);
});
/*binding.downloads.setOnClickListener(v -> {
Intent intent = new Intent();
intent.setClass(getApplicationContext(), DownloadActivity.class);
startActivity(intent);
});*/
binding.apps.setOnClickListener(v -> {
Intent intent = new Intent();
intent.setClass(getApplicationContext(), BlackListActivity.class);
@ -114,28 +105,9 @@ public class MainActivity extends BaseActivity implements /*RepoLoader.RepoListe
new Thread(() -> new BlackListAdapter(getApplicationContext(), AppHelper.isWhiteListMode()).generateCheckedList());
}
/*
private void notifyDataSetChanged() {
runOnUiThread(() -> {
String frameworkUpdateVersion = repoLoader.getFrameworkUpdateVersion();
boolean moduleUpdateAvailable = repoLoader.hasModuleUpdates();
ModuleUtil.getInstance().getEnabledModules().size();
binding.modulesSummary.setText(String.format(getString(R.string.ModulesDetail), ModuleUtil.getInstance().getEnabledModules().size()));
if (frameworkUpdateVersion != null) {
binding.statusSummary.setText(String.format(getString(R.string.welcome_framework_update_available), frameworkUpdateVersion));
}
if (moduleUpdateAvailable) {
binding.downloadSummary.setText(R.string.modules_updates_available);
} else {
binding.downloadSummary.setText(R.string.ModuleUptodate);
}
});
}
*/
@Override
public void onInstalledModulesReloaded(ModuleUtil moduleUtil) {
//notifyDataSetChanged();
}
@Override
@ -145,19 +117,13 @@ public class MainActivity extends BaseActivity implements /*RepoLoader.RepoListe
@Override
public void onSingleInstalledModuleReloaded(ModuleUtil moduleUtil, String packageName, ModuleUtil.InstalledModule module) {
//notifyDataSetChanged();
}
/*@Override
public void onRepoReloaded(RepoLoader loader) {
notifyDataSetChanged();
}*/
}
@Override
protected void onDestroy() {
super.onDestroy();
ModuleUtil.getInstance().removeListener(this);
//repoLoader.removeListener(this);
}
}

View File

@ -31,9 +31,9 @@ import org.meowcat.edxposed.manager.App;
import org.meowcat.edxposed.manager.BuildConfig;
import org.meowcat.edxposed.manager.Constants;
import org.meowcat.edxposed.manager.R;
import org.meowcat.edxposed.manager.adapters.AppAdapter;
import org.meowcat.edxposed.manager.databinding.ActivityModulesBinding;
import org.meowcat.edxposed.manager.util.GlideApp;
import org.meowcat.edxposed.manager.util.InstallApkUtil;
import org.meowcat.edxposed.manager.util.LinearLayoutManagerFix;
import org.meowcat.edxposed.manager.util.ModuleUtil;
@ -82,7 +82,7 @@ public class ModulesActivity extends BaseActivity implements ModuleUtil.ModuleLi
showList = new ArrayList<>();
String filter = queryStr.toLowerCase();
for (ModuleUtil.InstalledModule info : fullList) {
if (lowercaseContains(InstallApkUtil.getAppLabel(info.app, pm), filter)
if (lowercaseContains(AppAdapter.getAppLabel(info.app, pm), filter)
|| lowercaseContains(info.packageName, filter)) {
showList.add(info);
}
@ -351,52 +351,6 @@ public class ModulesActivity extends BaseActivity implements ModuleUtil.ModuleLi
return super.onOptionsItemSelected(item);
}
/*
private void importModules(Uri uri) {
RepoLoader repoLoader = RepoLoader.getInstance();
List<Module> list = new ArrayList<>();
try {
InputStream inputStream = getContentResolver().openInputStream(uri);
InputStreamReader isr = new InputStreamReader(inputStream);
BufferedReader br = new BufferedReader(isr);
String line;
while ((line = br.readLine()) != null) {
Module m = repoLoader.getModule(line);
if (m == null) {
Snackbar.make(binding.snackbar, getString(R.string.download_details_not_found, line), Snackbar.LENGTH_SHORT).show();
} else {
list.add(m);
}
}
br.close();
} catch (Exception e) {
e.printStackTrace();
}
for (final Module m : list) {
if (moduleUtil.getModule(m.packageName) != null) {
continue;
}
ModuleVersion mv = null;
for (int i = 0; i < m.versions.size(); i++) {
ModuleVersion mvTemp = m.versions.get(i);
if (mvTemp.relType == ReleaseType.STABLE) {
mv = mvTemp;
break;
}
}
if (mv != null) {
NavUtil.startURL(this, mv.downloadLink);
}
}
ModuleUtil.getInstance().reloadInstalledModules();
}
*/
@Override
public void onDestroy() {
super.onDestroy();

View File

@ -111,15 +111,7 @@ public class SettingsActivity extends BaseActivity {
@Override
public void onCreatePreferencesFix(Bundle savedInstanceState, String rootKey) {
addPreferencesFromResource(R.xml.prefs);
/*
Preference releaseType = findPreference("release_type_global");
if (releaseType != null) {
releaseType.setOnPreferenceChangeListener((preference, newValue) -> {
RepoLoader.getInstance().setReleaseTypeGlobal((String) newValue);
return true;
});
}
*/
SwitchPreferenceCompat prefWhiteListMode = findPreference("white_list_switch");
if (prefWhiteListMode != null) {
prefWhiteListMode.setChecked(Files.exists(whiteListModeFlag));

View File

@ -1,77 +0,0 @@
package org.meowcat.edxposed.manager.ui.fragment;
import android.annotation.SuppressLint;
import android.net.Uri;
import android.os.Bundle;
import android.text.method.LinkMovementMethod;
import android.util.Pair;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
import org.meowcat.edxposed.manager.R;
import org.meowcat.edxposed.manager.databinding.DownloadDetailsBinding;
import org.meowcat.edxposed.manager.databinding.DownloadMoreinfoBinding;
import org.meowcat.edxposed.manager.repo.Module;
import org.meowcat.edxposed.manager.repo.RepoParser;
import org.meowcat.edxposed.manager.ui.activity.BaseActivity;
import org.meowcat.edxposed.manager.ui.activity.DownloadDetailsActivity;
import org.meowcat.edxposed.manager.util.NavUtil;
import org.meowcat.edxposed.manager.util.chrome.LinkTransformationMethod;
public class DownloadDetailsFragment extends Fragment {
@SuppressLint("SetTextI18n")
@Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
DownloadDetailsActivity mActivity = (DownloadDetailsActivity) getActivity();
if (mActivity == null) {
return null;
}
final Module module = mActivity.getModule();
if (module == null) {
return null;
}
DownloadDetailsBinding binding = DownloadDetailsBinding.inflate(inflater, container, false);
binding.downloadTitle.setText(module.name);
binding.downloadTitle.setTextIsSelectable(true);
if (module.author != null && !module.author.isEmpty())
binding.downloadAuthor.setText(getString(R.string.download_author, module.author));
else
binding.downloadAuthor.setText(R.string.download_unknown_author);
if (module.description != null) {
if (module.descriptionIsHtml) {
binding.downloadDescription.setText(RepoParser.parseSimpleHtml(getActivity(), module.description, binding.downloadDescription));
binding.downloadDescription.setTransformationMethod(new LinkTransformationMethod((BaseActivity) getActivity()));
binding.downloadDescription.setMovementMethod(LinkMovementMethod.getInstance());
} else {
binding.downloadDescription.setText(module.description);
}
binding.downloadDescription.setTextIsSelectable(true);
} else {
binding.downloadDescription.setVisibility(View.GONE);
}
for (Pair<String, String> moreInfoEntry : module.moreInfo) {
DownloadMoreinfoBinding moreinfoBinding = DownloadMoreinfoBinding.inflate(inflater, binding.downloadMoreinfoContainer, false);
moreinfoBinding.title.setText(moreInfoEntry.first + ":");
moreinfoBinding.message.setText(moreInfoEntry.second);
final Uri link = NavUtil.parseURL(moreInfoEntry.second);
if (link != null) {
moreinfoBinding.message.setTextColor(moreinfoBinding.message.getLinkTextColors());
moreinfoBinding.getRoot().setOnClickListener(v -> NavUtil.startURL((BaseActivity) getActivity(), link));
}
binding.downloadMoreinfoContainer.addView(moreinfoBinding.getRoot());
}
return binding.getRoot();
}
}

View File

@ -1,62 +0,0 @@
package org.meowcat.edxposed.manager.ui.fragment;
import android.content.Context;
import android.content.SharedPreferences;
import android.os.Bundle;
import androidx.preference.Preference;
import androidx.preference.PreferenceManager;
import com.takisoft.preferencex.PreferenceFragmentCompat;
import org.meowcat.edxposed.manager.R;
import org.meowcat.edxposed.manager.repo.Module;
import org.meowcat.edxposed.manager.ui.activity.DownloadDetailsActivity;
import org.meowcat.edxposed.manager.util.PrefixedSharedPreferences;
import org.meowcat.edxposed.manager.util.RepoLoader;
import java.util.Map;
public class DownloadDetailsSettingsFragment extends PreferenceFragmentCompat {
@Override
public void onCreatePreferencesFix(Bundle savedInstanceState, String rootKey) {
DownloadDetailsActivity mActivity = (DownloadDetailsActivity) getActivity();
if (mActivity == null) {
return;
}
final Module module = mActivity.getModule();
if (module == null) {
return;
}
final String packageName = module.packageName;
PreferenceManager prefManager = getPreferenceManager();
prefManager.setSharedPreferencesName("module_settings");
PrefixedSharedPreferences.injectToPreferenceManager(prefManager, module.packageName);
addPreferencesFromResource(R.xml.module_prefs);
SharedPreferences prefs = getActivity().getSharedPreferences("module_settings", Context.MODE_PRIVATE);
SharedPreferences.Editor editor = prefs.edit();
if (prefs.getBoolean("no_global", true)) {
for (Map.Entry<String, ?> k : prefs.getAll().entrySet()) {
if (("global").equals(prefs.getString(k.getKey(), ""))) {
editor.putString(k.getKey(), "").apply();
}
}
editor.putBoolean("no_global", false).apply();
}
Preference releaseType = findPreference("release_type");
if (releaseType != null) {
releaseType.setOnPreferenceChangeListener((preference, newValue) -> {
RepoLoader.getInstance().setReleaseTypeLocal(packageName, (String) newValue);
return true;
});
}
}
}

View File

@ -1,188 +0,0 @@
package org.meowcat.edxposed.manager.ui.fragment;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.Resources;
import android.os.Bundle;
import android.text.method.LinkMovementMethod;
import android.util.TypedValue;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.core.content.ContextCompat;
import androidx.fragment.app.ListFragment;
import org.meowcat.edxposed.manager.R;
import org.meowcat.edxposed.manager.repo.Module;
import org.meowcat.edxposed.manager.repo.ModuleVersion;
import org.meowcat.edxposed.manager.repo.ReleaseType;
import org.meowcat.edxposed.manager.repo.RepoParser;
import org.meowcat.edxposed.manager.ui.activity.BaseActivity;
import org.meowcat.edxposed.manager.ui.activity.DownloadDetailsActivity;
import org.meowcat.edxposed.manager.ui.widget.DownloadView;
import org.meowcat.edxposed.manager.util.ModuleUtil.InstalledModule;
import org.meowcat.edxposed.manager.util.RepoLoader;
import org.meowcat.edxposed.manager.util.chrome.LinkTransformationMethod;
import java.text.DateFormat;
import java.util.Date;
public class DownloadDetailsVersionsFragment extends ListFragment {
@SuppressLint("StaticFieldLeak")
private DownloadDetailsActivity activity;
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
activity = (DownloadDetailsActivity) getActivity();
if (activity == null) {
return;
}
Module module = activity.getModule();
if (module == null)
return;
if (module.versions.isEmpty()) {
setEmptyText(getString(R.string.download_no_versions));
setListShown(true);
} else {
RepoLoader repoLoader = RepoLoader.getInstance();
if (!repoLoader.isVersionShown(module.versions.get(0))) {
TextView txtHeader = new TextView(getActivity());
txtHeader.setText(R.string.download_test_version_not_shown);
txtHeader.setTextColor(ContextCompat.getColor(activity, R.color.warning));
txtHeader.setOnClickListener(v -> activity.gotoPage(DownloadDetailsActivity.DOWNLOAD_SETTINGS));
getListView().addHeaderView(txtHeader);
}
VersionsAdapter sAdapter = new VersionsAdapter(activity, activity.getInstalledModule()/*, activity.findViewById(R.id.snackbar)*/);
for (ModuleVersion version : module.versions) {
if (repoLoader.isVersionShown(version))
sAdapter.add(version);
}
setListAdapter(sAdapter);
}
}
@Override
public void onDestroyView() {
super.onDestroyView();
setListAdapter(null);
}
static class ViewHolder {
TextView txtStatus;
TextView txtVersion;
TextView txtRelType;
TextView txtUploadDate;
DownloadView downloadView;
TextView txtChangesTitle;
TextView txtChanges;
}
private class VersionsAdapter extends ArrayAdapter<ModuleVersion> {
private final DateFormat dateFormatter = DateFormat
.getDateInstance(DateFormat.SHORT);
private final int colorRelTypeStable;
private final int colorRelTypeOthers;
private final int colorInstalled;
private final int colorUpdateAvailable;
private final String textInstalled;
private final String textUpdateAvailable;
private final long installedVersionCode;
VersionsAdapter(Context context, InstalledModule installed) {
super(context, R.layout.item_version);
TypedValue typedValue = new TypedValue();
Resources.Theme theme = context.getTheme();
theme.resolveAttribute(android.R.attr.textColorPrimary, typedValue, true);
int color = ContextCompat.getColor(context, typedValue.resourceId);
colorRelTypeStable = color;
colorRelTypeOthers = ContextCompat.getColor(activity, R.color.warning);
colorInstalled = color;
colorUpdateAvailable = ContextCompat.getColor(activity, R.color.download_status_update_available);
textInstalled = getString(R.string.download_section_installed) + ":";
textUpdateAvailable = getString(R.string.download_section_update_available) + ":";
installedVersionCode = (installed != null) ? installed.versionCode : -1;
}
@SuppressLint("InflateParams")
@Override
@NonNull
public View getView(int position, View convertView, @NonNull ViewGroup parent) {
View view = convertView;
if (view == null) {
LayoutInflater inflater = (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
view = inflater.inflate(R.layout.item_version, null, true);
ViewHolder viewHolder = new ViewHolder();
viewHolder.txtStatus = view.findViewById(R.id.txtStatus);
viewHolder.txtVersion = view.findViewById(R.id.txtVersion);
viewHolder.txtRelType = view.findViewById(R.id.txtRelType);
viewHolder.txtUploadDate = view.findViewById(R.id.txtUploadDate);
viewHolder.downloadView = view.findViewById(R.id.downloadView);
viewHolder.txtChangesTitle = view.findViewById(R.id.txtChangesTitle);
viewHolder.txtChanges = view.findViewById(R.id.txtChanges);
viewHolder.downloadView.fragment = DownloadDetailsVersionsFragment.this;
view.setTag(viewHolder);
}
ViewHolder holder = (ViewHolder) view.getTag();
ModuleVersion item = getItem(position);
if (item == null) {
return view;
}
holder.txtVersion.setText(item.name);
holder.txtRelType.setText(item.relType.getTitleId());
holder.txtRelType.setTextColor(item.relType == ReleaseType.STABLE
? colorRelTypeStable : colorRelTypeOthers);
if (item.uploaded > 0) {
holder.txtUploadDate.setText(
dateFormatter.format(new Date(item.uploaded)));
holder.txtUploadDate.setVisibility(View.VISIBLE);
} else {
holder.txtUploadDate.setVisibility(View.GONE);
}
if (item.code <= 0 || installedVersionCode <= 0
|| item.code < installedVersionCode) {
holder.txtStatus.setVisibility(View.GONE);
} else if (item.code == installedVersionCode) {
holder.txtStatus.setText(textInstalled);
holder.txtStatus.setTextColor(colorInstalled);
holder.txtStatus.setVisibility(View.VISIBLE);
} else { // item.code > installedVersionCode
holder.txtStatus.setText(textUpdateAvailable);
holder.txtStatus.setTextColor(colorUpdateAvailable);
holder.txtStatus.setVisibility(View.VISIBLE);
}
holder.downloadView.setUrl(item.downloadLink);
holder.downloadView.setTitle(activity.getModule().name);
if (item.changelog != null && !item.changelog.isEmpty()) {
holder.txtChangesTitle.setVisibility(View.VISIBLE);
holder.txtChanges.setVisibility(View.VISIBLE);
if (item.changelogIsHtml) {
holder.txtChanges.setText(RepoParser.parseSimpleHtml(getActivity(), item.changelog, holder.txtChanges));
holder.txtChanges.setTransformationMethod(new LinkTransformationMethod((BaseActivity) getActivity()));
holder.txtChanges.setMovementMethod(LinkMovementMethod.getInstance());
} else {
holder.txtChanges.setText(item.changelog);
holder.txtChanges.setMovementMethod(null);
}
} else {
holder.txtChangesTitle.setVisibility(View.GONE);
holder.txtChanges.setVisibility(View.GONE);
}
return view;
}
}
}

View File

@ -1,55 +0,0 @@
package org.meowcat.edxposed.manager.ui.widget;
import android.content.Context;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.LinearLayout;
import androidx.fragment.app.Fragment;
import org.meowcat.edxposed.manager.R;
import org.meowcat.edxposed.manager.databinding.DownloadViewBinding;
import org.meowcat.edxposed.manager.ui.activity.BaseActivity;
import org.meowcat.edxposed.manager.util.NavUtil;
public class DownloadView extends LinearLayout {
public Fragment fragment;
private String mUrl = null;
private String mTitle = null;
private final DownloadViewBinding binding;
public DownloadView(Context context, final AttributeSet attrs) {
super(context, attrs);
setFocusable(false);
setOrientation(LinearLayout.VERTICAL);
binding = DownloadViewBinding.inflate(LayoutInflater.from(context), this);
binding.btnDownload.setOnClickListener(v -> NavUtil.startURL((BaseActivity) context, mUrl));
}
public String getUrl() {
return mUrl;
}
public void setUrl(String url) {
mUrl = url;
if (mUrl != null) {
binding.btnDownload.setVisibility(View.VISIBLE);
binding.txtInfo.setVisibility(View.GONE);
} else {
binding.btnDownload.setVisibility(View.GONE);
binding.txtInfo.setVisibility(View.VISIBLE);
binding.txtInfo.setText(R.string.download_view_no_url);
}
}
public String getTitle() {
return mTitle;
}
public void setTitle(String title) {
this.mTitle = title;
}
}

View File

@ -1,129 +0,0 @@
package org.meowcat.edxposed.manager.util;
import android.content.Context;
import android.content.SharedPreferences;
import org.meowcat.edxposed.manager.App;
import org.meowcat.edxposed.manager.R;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLConnection;
public class DownloadsUtil {
private static final SharedPreferences pref = App.getInstance().getSharedPreferences("download_cache", Context.MODE_PRIVATE);
static SyncDownloadInfo downloadSynchronously(String url, File target) {
final boolean useNotModifiedTags = target.exists();
URLConnection connection = null;
InputStream in = null;
FileOutputStream out = null;
try {
connection = new URL(url).openConnection();
connection.setDoOutput(false);
connection.setConnectTimeout(30000);
connection.setReadTimeout(30000);
if (connection instanceof HttpURLConnection) {
// Disable transparent gzip encoding for gzipped files
if (url.endsWith(".gz")) {
connection.addRequestProperty("Accept-Encoding", "identity");
}
if (useNotModifiedTags) {
String modified = pref.getString("download_" + url + "_modified", null);
String etag = pref.getString("download_" + url + "_etag", null);
if (modified != null) {
connection.addRequestProperty("If-Modified-Since", modified);
}
if (etag != null) {
connection.addRequestProperty("If-None-Match", etag);
}
}
}
connection.connect();
if (connection instanceof HttpURLConnection) {
HttpURLConnection httpConnection = (HttpURLConnection) connection;
int responseCode = httpConnection.getResponseCode();
if (responseCode == HttpURLConnection.HTTP_NOT_MODIFIED) {
return new SyncDownloadInfo(SyncDownloadInfo.STATUS_NOT_MODIFIED, null);
} else if (responseCode < 200 || responseCode >= 300) {
return new SyncDownloadInfo(SyncDownloadInfo.STATUS_FAILED,
App.getInstance().getString(R.string.repo_download_failed_http,
url, responseCode,
httpConnection.getResponseMessage()));
}
}
in = connection.getInputStream();
out = new FileOutputStream(target);
byte[] buf = new byte[1024];
int read;
while ((read = in.read(buf)) != -1) {
out.write(buf, 0, read);
}
if (connection instanceof HttpURLConnection) {
HttpURLConnection httpConnection = (HttpURLConnection) connection;
String modified = httpConnection.getHeaderField("Last-Modified");
String etag = httpConnection.getHeaderField("ETag");
pref.edit()
.putString("download_" + url + "_modified", modified)
.putString("download_" + url + "_etag", etag).apply();
}
return new SyncDownloadInfo(SyncDownloadInfo.STATUS_SUCCESS, null);
} catch (Throwable t) {
return new SyncDownloadInfo(SyncDownloadInfo.STATUS_FAILED,
App.getInstance().getString(R.string.repo_download_failed, url,
t.getMessage()));
} finally {
if (connection instanceof HttpURLConnection)
((HttpURLConnection) connection).disconnect();
if (in != null)
try {
in.close();
} catch (IOException ignored) {
}
if (out != null)
try {
out.close();
} catch (IOException ignored) {
}
}
}
static void clearCache(String url) {
if (url != null) {
pref.edit().remove("download_" + url + "_modified")
.remove("download_" + url + "_etag").apply();
} else {
pref.edit().clear().apply();
}
}
public static class SyncDownloadInfo {
static final int STATUS_SUCCESS = 0;
static final int STATUS_NOT_MODIFIED = 1;
static final int STATUS_FAILED = 2;
public final int status;
final String errorMessage;
private SyncDownloadInfo(int status, String errorMessage) {
this.status = status;
this.errorMessage = errorMessage;
}
}
}

View File

@ -1,64 +0,0 @@
package org.meowcat.edxposed.manager.util;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
public class HashUtil {
private static String hash(String input, @SuppressWarnings("SameParameterValue") String algorithm) {
try {
MessageDigest md = MessageDigest.getInstance(algorithm);
byte[] messageDigest = md.digest(input.getBytes());
return toHexString(messageDigest);
} catch (NoSuchAlgorithmException e) {
throw new IllegalArgumentException(e);
}
}
static String md5(String input) {
return hash(input, "MD5");
}
// public static String sha1(String input) {
// return hash(input, "SHA-1");
// }
private static String hash(File file, @SuppressWarnings("SameParameterValue") String algorithm) throws IOException {
try {
MessageDigest md = MessageDigest.getInstance(algorithm);
InputStream is = new FileInputStream(file);
byte[] buffer = new byte[8192];
int read;
while ((read = is.read(buffer)) > 0) {
md.update(buffer, 0, read);
}
is.close();
byte[] messageDigest = md.digest();
return toHexString(messageDigest);
} catch (NoSuchAlgorithmException e) {
throw new IllegalArgumentException(e);
}
}
public static String md5(File input) throws IOException {
return hash(input, "MD5");
}
// public static String sha1(File input) throws IOException {
// return hash(input, "SHA-1");
// }
private static String toHexString(byte[] bytes) {
StringBuilder sb = new StringBuilder();
for (byte b : bytes) {
int unsignedB = b & 0xff;
if (unsignedB < 0x10)
sb.append("0");
sb.append(Integer.toHexString(unsignedB));
}
return sb.toString();
}
}

View File

@ -1,19 +0,0 @@
package org.meowcat.edxposed.manager.util;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.res.Resources;
public class InstallApkUtil {
public static String getAppLabel(ApplicationInfo info, PackageManager pm) {
try {
if (info.labelRes > 0) {
Resources res = pm.getResourcesForApplication(info);
return res.getString(info.labelRes);
}
} catch (Exception ignored) {
}
return info.loadLabel(pm).toString();
}
}

View File

@ -18,7 +18,6 @@ import org.meowcat.edxposed.manager.App;
import org.meowcat.edxposed.manager.Constants;
import org.meowcat.edxposed.manager.R;
import org.meowcat.edxposed.manager.databinding.ActivityModulesBinding;
import org.meowcat.edxposed.manager.repo.ModuleVersion;
import java.io.IOException;
import java.io.PrintWriter;
@ -33,7 +32,6 @@ public final class ModuleUtil {
public static int MIN_MODULE_VERSION = 2; // reject modules with
private static ModuleUtil instance = null;
private final PackageManager pm;
private final String frameworkPackageName;
private final List<ModuleListener> listeners = new CopyOnWriteArrayList<>();
private final SharedPreferences pref;
//private InstalledModule framework = null;
@ -44,7 +42,6 @@ public final class ModuleUtil {
private ModuleUtil() {
pref = App.getInstance().getSharedPreferences("enabled_modules", Context.MODE_PRIVATE);
pm = App.getInstance().getPackageManager();
frameworkPackageName = App.getInstance().getPackageName();
}
public static synchronized ModuleUtil getInstance() {
@ -76,32 +73,19 @@ public final class ModuleUtil {
}
Map<String, InstalledModule> modules = new HashMap<>();
//RepoDb.beginTransation();
try {
//RepoDb.deleteAllInstalledModules();
for (PackageInfo pkg : pm.getInstalledPackages(PackageManager.GET_META_DATA)) {
ApplicationInfo app = pkg.applicationInfo;
if (!app.enabled)
continue;
for (PackageInfo pkg : pm.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)) {
framework = installed = new InstalledModule(pkg, true);
}*/
//if (installed != null)
// RepoDb.insertInstalledModule(installed);
if (app.metaData != null && app.metaData.containsKey("xposedmodule")) {
InstalledModule installed = new InstalledModule(pkg, false);
modules.put(pkg.packageName, installed);
}
//RepoDb.setTransactionSuccessful();
} finally {
//RepoDb.endTransation();
}
installedModules = modules;
synchronized (this) {
isReloading = false;
@ -129,7 +113,6 @@ public final class ModuleUtil {
ApplicationInfo app = pkg.applicationInfo;
if (app.enabled && app.metaData != null && app.metaData.containsKey("xposedmodule")) {
InstalledModule module = new InstalledModule(pkg, false);
//RepoDb.insertInstalledModule(module);
installedModules.put(packageName, module);
for (ModuleListener listener : listeners) {
listener.onSingleInstalledModuleReloaded(instance, packageName,
@ -137,7 +120,6 @@ public final class ModuleUtil {
}
return module;
} else {
//RepoDb.deleteInstalledModule(packageName);
InstalledModule old = installedModules.remove(packageName);
if (old != null) {
for (ModuleListener listener : listeners) {
@ -148,26 +130,6 @@ public final class ModuleUtil {
}
}
public synchronized boolean isLoading() {
return isReloading;
}
/* public InstalledModule getFramework() {
return framework;
}*/
public String getFrameworkPackageName() {
return frameworkPackageName;
}
/* private boolean isFramework(String packageName) {
return frameworkPackageName.equals(packageName);
}*/
// public boolean isInstalled(String packageName) {
// return installedModules.containsKey(packageName) || isFramework(packageName);
// }
public InstalledModule getModule(String packageName) {
return installedModules.get(packageName);
}
@ -370,10 +332,6 @@ public final class ModuleUtil {
return this.description;
}
public boolean isUpdate(ModuleVersion version) {
return (version != null) && version.code > versionCode;
}
public PackageInfo getPackageInfo() {
return pkg;
}

View File

@ -25,7 +25,6 @@ public final class NotificationUtil {
public static final int NOTIFICATION_MODULE_NOT_ACTIVATED_YET = 0;
private static final int NOTIFICATION_MODULES_UPDATED = 1;
private static final int NOTIFICATION_INSTALLER_UPDATE = 2;
private static final int PENDING_INTENT_OPEN_MODULES = 0;
private static final int PENDING_INTENT_OPEN_INSTALL = 1;
private static final int PENDING_INTENT_SOFT_REBOOT = 2;
@ -36,7 +35,6 @@ public final class NotificationUtil {
private static final String HEADS_UP = "heads_up";
private static final String FRAGMENT_ID = "fragment";
private static final String NOTIFICATION_UPDATE_CHANNEL = "app_update_channel";
private static final String NOTIFICATION_MODULES_CHANNEL = "modules_channel";
@SuppressLint("StaticFieldLeak")
@ -54,9 +52,7 @@ public final class NotificationUtil {
notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
NotificationChannel channelUpdate = new NotificationChannel(NOTIFICATION_UPDATE_CHANNEL, context.getString(R.string.download_section_update_available), NotificationManager.IMPORTANCE_DEFAULT);
NotificationChannel channelModule = new NotificationChannel(NOTIFICATION_MODULES_CHANNEL, context.getString(R.string.nav_item_modules), NotificationManager.IMPORTANCE_DEFAULT);
notificationManager.createNotificationChannel(channelUpdate);
notificationManager.createNotificationChannel(channelModule);
}
}
@ -137,30 +133,6 @@ public final class NotificationUtil {
notificationManager.notify(null, NOTIFICATION_MODULES_UPDATED, builder.build());
}
public static void showInstallerUpdateNotification() {
Intent intent = new Intent(context, MainActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.putExtra(FRAGMENT_ID, 0);
PendingIntent pInstallTab = PendingIntent.getActivity(context, PENDING_INTENT_OPEN_INSTALL,
intent, PendingIntent.FLAG_UPDATE_CURRENT);
String title = context.getString(R.string.app_name);
String message = context.getString(R.string.newVersion);
NotificationCompat.Builder builder = getNotificationBuilder(title, message, NOTIFICATION_UPDATE_CHANNEL)
.setContentIntent(pInstallTab);
if (prefs.getBoolean(HEADS_UP, true)) {
builder.setPriority(2);
}
NotificationCompat.BigTextStyle notiStyle = new NotificationCompat.BigTextStyle();
notiStyle.setBigContentTitle(title);
notiStyle.bigText(message);
builder.setStyle(notiStyle);
notificationManager.notify(null, NOTIFICATION_INSTALLER_UPDATE, builder.build());
}
private static NotificationCompat.Builder getNotificationBuilder(String title, String message, String channel) {
return new NotificationCompat.Builder(context, channel)
.setContentTitle(title)

View File

@ -1,161 +0,0 @@
package org.meowcat.edxposed.manager.util;
import android.annotation.SuppressLint;
import android.content.SharedPreferences;
import androidx.preference.PreferenceManager;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
public class PrefixedSharedPreferences implements SharedPreferences {
private final SharedPreferences mBase;
private final String mPrefix;
private PrefixedSharedPreferences(SharedPreferences base, String prefix) {
mBase = base;
mPrefix = prefix + "_";
}
public static void injectToPreferenceManager(PreferenceManager manager, String prefix) {
SharedPreferences prefixedPrefs = new PrefixedSharedPreferences(manager.getSharedPreferences(), prefix);
try {
Field fieldSharedPref = PreferenceManager.class.getDeclaredField("mSharedPreferences");
fieldSharedPref.setAccessible(true);
fieldSharedPref.set(manager, prefixedPrefs);
} catch (Throwable t) {
throw new RuntimeException(t);
}
}
@Override
public Map<String, ?> getAll() {
Map<String, ?> baseResult = mBase.getAll();
Map<String, Object> prefixedResult = new HashMap<>(baseResult);
for (Entry<String, ?> entry : baseResult.entrySet()) {
prefixedResult.put(mPrefix + entry.getKey(), entry.getValue());
}
return prefixedResult;
}
@Override
public String getString(String key, String defValue) {
return mBase.getString(mPrefix + key, defValue);
}
@Override
public Set<String> getStringSet(String key, Set<String> defValues) {
return mBase.getStringSet(mPrefix + key, defValues);
}
@Override
public int getInt(String key, int defValue) {
return mBase.getInt(mPrefix + key, defValue);
}
@Override
public long getLong(String key, long defValue) {
return mBase.getLong(mPrefix + key, defValue);
}
@Override
public float getFloat(String key, float defValue) {
return mBase.getFloat(mPrefix + key, defValue);
}
@Override
public boolean getBoolean(String key, boolean defValue) {
return mBase.getBoolean(mPrefix + key, defValue);
}
@Override
public boolean contains(String key) {
return mBase.contains(mPrefix + key);
}
@SuppressLint("CommitPrefEdits")
@Override
public Editor edit() {
return new EditorImpl(mBase.edit());
}
@Override
public void registerOnSharedPreferenceChangeListener(OnSharedPreferenceChangeListener listener) {
throw new UnsupportedOperationException("listeners are not supported in this implementation");
}
@Override
public void unregisterOnSharedPreferenceChangeListener(OnSharedPreferenceChangeListener listener) {
throw new UnsupportedOperationException("listeners are not supported in this implementation");
}
private class EditorImpl implements Editor {
private final Editor mEditorBase;
public EditorImpl(Editor base) {
mEditorBase = base;
}
@Override
public Editor putString(String key, String value) {
mEditorBase.putString(mPrefix + key, value);
return this;
}
@Override
public Editor putStringSet(String key, Set<String> values) {
mEditorBase.putStringSet(mPrefix + key, values);
return this;
}
@Override
public Editor putInt(String key, int value) {
mEditorBase.putInt(mPrefix + key, value);
return this;
}
@Override
public Editor putLong(String key, long value) {
mEditorBase.putLong(mPrefix + key, value);
return this;
}
@Override
public Editor putFloat(String key, float value) {
mEditorBase.putFloat(mPrefix + key, value);
return this;
}
@Override
public Editor putBoolean(String key, boolean value) {
mEditorBase.putBoolean(mPrefix + key, value);
return this;
}
@Override
public Editor remove(String key) {
mEditorBase.remove(mPrefix + key);
return this;
}
@Override
public Editor clear() {
mEditorBase.clear();
return this;
}
@Override
public boolean commit() {
return mEditorBase.commit();
}
@Override
public void apply() {
mEditorBase.apply();
}
}
}

View File

@ -1,434 +0,0 @@
package org.meowcat.edxposed.manager.util;
import android.app.AlarmManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.database.sqlite.SQLiteException;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.text.TextUtils;
import android.util.Log;
import android.widget.Toast;
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import org.meowcat.edxposed.manager.App;
import org.meowcat.edxposed.manager.R;
import org.meowcat.edxposed.manager.repo.Module;
import org.meowcat.edxposed.manager.repo.ModuleVersion;
import org.meowcat.edxposed.manager.repo.ReleaseType;
import org.meowcat.edxposed.manager.repo.RepoDb;
import org.meowcat.edxposed.manager.repo.RepoParser;
import org.meowcat.edxposed.manager.repo.RepoParser.RepoParserCallback;
import org.meowcat.edxposed.manager.repo.Repository;
import org.meowcat.edxposed.manager.ui.activity.DownloadActivity;
import org.meowcat.edxposed.manager.util.DownloadsUtil.SyncDownloadInfo;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.zip.GZIPInputStream;
public class RepoLoader {
private static final int UPDATE_FREQUENCY = 24 * 60 * 60 * 1000;
private static String DEFAULT_REPOSITORIES;
private static RepoLoader instance = null;
private final List<RepoListener> listeners = new CopyOnWriteArrayList<>();
private final Map<String, ReleaseType> localReleaseTypesCache = new HashMap<>();
private final App app;
private final SharedPreferences pref;
private final SharedPreferences modulePref;
private final ConnectivityManager conMgr;
private boolean isLoading = false;
private boolean reloadTriggeredOnce = false;
private Map<Long, Repository> repositories = null;
private ReleaseType globalReleaseType;
private SwipeRefreshLayout swipeRefreshLayout;
private RepoLoader() {
instance = this;
app = App.getInstance();
pref = app.getSharedPreferences("repo", Context.MODE_PRIVATE);
DEFAULT_REPOSITORIES = App.getPreferences().getBoolean("custom_list", false) ? "https://cdn.jsdelivr.net/gh/ElderDrivers/Repository-Website@gh-pages/assets/full.xml.gz" : "https://dl-xda.xposed.info/repo/full.xml.gz";
modulePref = app.getSharedPreferences("module_settings", Context.MODE_PRIVATE);
conMgr = (ConnectivityManager) app.getSystemService(Context.CONNECTIVITY_SERVICE);
globalReleaseType = ReleaseType.fromString(App.getPreferences().getString("release_type_global", "stable"));
refreshRepositories();
}
public static synchronized RepoLoader getInstance() {
if (instance == null)
new RepoLoader();
return instance;
}
private boolean refreshRepositories() {
repositories = RepoDb.getRepositories();
// Unlikely case (usually only during initial load): DB state doesn't
// fit to configuration
boolean needReload = false;
String[] config = (pref.getString("repositories", DEFAULT_REPOSITORIES) + "").split("\\|");
if (repositories.size() != config.length) {
needReload = true;
} else {
int i = 0;
for (Repository repo : repositories.values()) {
if (!repo.url.equals(config[i++])) {
needReload = true;
break;
}
}
}
if (!needReload)
return false;
clear(false);
for (String url : config) {
RepoDb.insertRepository(url);
}
repositories = RepoDb.getRepositories();
return true;
}
public void setReleaseTypeGlobal(String relTypeString) {
ReleaseType relType = ReleaseType.fromString(relTypeString);
if (globalReleaseType == relType)
return;
globalReleaseType = relType;
// Updating the latest version for all modules takes a moment
new Thread("DBUpdate") {
@Override
public void run() {
RepoDb.updateAllModulesLatestVersion();
notifyListeners();
}
}.start();
}
public void setReleaseTypeLocal(String packageName, String relTypeString) {
ReleaseType relType = (!TextUtils.isEmpty(relTypeString)) ? ReleaseType.fromString(relTypeString) : null;
if (getReleaseTypeLocal(packageName) == relType)
return;
synchronized (localReleaseTypesCache) {
if (relType != null) {
localReleaseTypesCache.put(packageName, relType);
}
}
RepoDb.updateModuleLatestVersion(packageName);
notifyListeners();
}
private ReleaseType getReleaseTypeLocal(String packageName) {
synchronized (localReleaseTypesCache) {
if (localReleaseTypesCache.containsKey(packageName))
return localReleaseTypesCache.get(packageName);
String value = modulePref.getString(packageName + "_release_type",
null);
ReleaseType result = (!TextUtils.isEmpty(value)) ? ReleaseType.fromString(value) : null;
if (result != null) {
localReleaseTypesCache.put(packageName, result);
}
return result;
}
}
public Repository getRepository(long repoId) {
return repositories.get(repoId);
}
public Module getModule(String packageName) {
return RepoDb.getModuleByPackageName(packageName);
}
public ModuleVersion getLatestVersion(Module module) {
if (module == null || module.versions.isEmpty())
return null;
for (ModuleVersion version : module.versions) {
if (version.downloadLink != null && isVersionShown(version))
return version;
}
return null;
}
public boolean isVersionShown(ModuleVersion version) {
return version.relType
.ordinal() <= getMaxShownReleaseType(version.module.packageName).ordinal();
}
public ReleaseType getMaxShownReleaseType(String packageName) {
ReleaseType localSetting = getReleaseTypeLocal(packageName);
if (localSetting != null)
return localSetting;
else
return globalReleaseType;
}
public void triggerReload(final boolean force) {
reloadTriggeredOnce = true;
if (force) {
resetLastUpdateCheck();
} else {
long lastUpdateCheck = pref.getLong("last_update_check", 0);
if (System.currentTimeMillis() < lastUpdateCheck + UPDATE_FREQUENCY)
return;
}
NetworkInfo netInfo = conMgr.getActiveNetworkInfo();
if (netInfo == null || !netInfo.isConnected())
return;
synchronized (this) {
if (isLoading)
return;
isLoading = true;
}
//app.updateProgressIndicator(swipeRefreshLayout);
new Thread("RepositoryReload") {
public void run() {
final List<String> messages = new LinkedList<>();
boolean hasChanged = downloadAndParseFiles(messages);
pref.edit().putLong("last_update_check", System.currentTimeMillis()).apply();
if (!messages.isEmpty()) {
App.runOnUiThread(() -> {
for (String message : messages) {
Toast.makeText(app, message, Toast.LENGTH_LONG).show();
}
});
}
if (hasChanged)
notifyListeners();
synchronized (this) {
isLoading = false;
}
//app.updateProgressIndicator(swipeRefreshLayout);
}
}.start();
}
public void setSwipeRefreshLayout(SwipeRefreshLayout swipeRefreshLayout) {
this.swipeRefreshLayout = swipeRefreshLayout;
}
public void triggerFirstLoadIfNecessary() {
if (!reloadTriggeredOnce)
triggerReload(false);
}
public void resetLastUpdateCheck() {
pref.edit().remove("last_update_check").apply();
}
public synchronized boolean isLoading() {
return isLoading;
}
public void clear(boolean notify) {
synchronized (this) {
// TODO Stop reloading repository when it should be cleared
if (isLoading)
return;
RepoDb.deleteRepositories();
repositories = new LinkedHashMap<>(0);
DownloadsUtil.clearCache(null);
resetLastUpdateCheck();
}
if (notify)
notifyListeners();
}
public void setRepositories(String... repos) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < repos.length; i++) {
if (i > 0)
sb.append("|");
sb.append(repos[i]);
}
pref.edit().putString("repositories", sb.toString()).apply();
if (refreshRepositories())
triggerReload(true);
}
public boolean hasModuleUpdates() {
return RepoDb.hasModuleUpdates();
}
public String getFrameworkUpdateVersion() {
return RepoDb.getFrameworkUpdateVersion();
}
private File getRepoCacheFile(String repo) {
String filename = "repo_" + HashUtil.md5(repo) + ".xml";
if (repo.endsWith(".gz"))
filename += ".gz";
return new File(app.getCacheDir(), filename);
}
private boolean downloadAndParseFiles(List<String> messages) {
// These variables don't need to be atomic, just mutable
final AtomicBoolean hasChanged = new AtomicBoolean(false);
final AtomicInteger insertCounter = new AtomicInteger();
final AtomicInteger deleteCounter = new AtomicInteger();
for (Entry<Long, Repository> repoEntry : repositories.entrySet()) {
final long repoId = repoEntry.getKey();
final Repository repo = repoEntry.getValue();
String url = (repo.partialUrl != null && repo.version != null) ? String.format(repo.partialUrl, repo.version) : repo.url;
File cacheFile = getRepoCacheFile(url);
SyncDownloadInfo info = DownloadsUtil.downloadSynchronously(url,
cacheFile);
Log.i(App.TAG, String.format(
"RepoLoader -> Downloaded %s with status %d (error: %s), size %d bytes",
url, info.status, info.errorMessage, cacheFile.length()));
if (info.status != SyncDownloadInfo.STATUS_SUCCESS) {
if (info.errorMessage != null)
messages.add(info.errorMessage);
continue;
}
InputStream in = null;
RepoDb.beginTransation();
try {
in = new FileInputStream(cacheFile);
if (url.endsWith(".gz"))
in = new GZIPInputStream(in);
RepoParser.parse(in, new RepoParserCallback() {
@Override
public void onRepositoryMetadata(Repository repository) {
if (!repository.isPartial) {
RepoDb.deleteAllModules(repoId);
hasChanged.set(true);
}
}
@Override
public void onNewModule(Module module) {
RepoDb.insertModule(repoId, module);
hasChanged.set(true);
insertCounter.incrementAndGet();
}
@Override
public void onRemoveModule(String packageName) {
RepoDb.deleteModule(repoId, packageName);
hasChanged.set(true);
deleteCounter.decrementAndGet();
}
@Override
public void onCompleted(Repository repository) {
if (!repository.isPartial) {
RepoDb.updateRepository(repoId, repository);
repo.name = repository.name;
repo.partialUrl = repository.partialUrl;
repo.version = repository.version;
} else {
RepoDb.updateRepositoryVersion(repoId, repository.version);
repo.version = repository.version;
}
Log.i(App.TAG, String.format(
"RepoLoader -> Updated repository %s to version %s (%d new / %d removed modules)",
repo.url, repo.version, insertCounter.get(),
deleteCounter.get()));
}
});
RepoDb.setTransactionSuccessful();
} catch (SQLiteException e) {
App.runOnUiThread(() -> new MaterialAlertDialogBuilder(app)
.setTitle(R.string.restart_needed)
.setMessage(R.string.cache_cleaned)
.setPositiveButton(android.R.string.ok, (dialog, which) -> {
Intent i = new Intent(app, DownloadActivity.class);
PendingIntent pi = PendingIntent.getActivity(app, 0, i, PendingIntent.FLAG_CANCEL_CURRENT);
AlarmManager mgr = (AlarmManager) app.getSystemService(Context.ALARM_SERVICE);
mgr.set(AlarmManager.RTC, System.currentTimeMillis() + 100, pi);
System.exit(0);
})
.setCancelable(false)
.show());
DownloadsUtil.clearCache(url);
} catch (Throwable t) {
Log.e(App.TAG, "RepoLoader -> Cannot load repository from " + url, t);
messages.add(app.getString(R.string.repo_load_failed, url, t.getMessage()));
DownloadsUtil.clearCache(url);
} finally {
if (in != null)
try {
in.close();
} catch (IOException ignored) {
}
//noinspection ResultOfMethodCallIgnored
cacheFile.delete();
RepoDb.endTransation();
}
}
// TODO Set ModuleColumns.PREFERRED for modules which appear in multiple
// repositories
return hasChanged.get();
}
public void addListener(RepoListener listener, boolean triggerImmediately) {
if (!listeners.contains(listener))
listeners.add(listener);
if (triggerImmediately)
listener.onRepoReloaded(this);
}
public void removeListener(RepoListener listener) {
listeners.remove(listener);
}
private void notifyListeners() {
for (RepoListener listener : listeners) {
listener.onRepoReloaded(instance);
}
}
public interface RepoListener {
/**
* Called whenever the list of modules from repositories has been
* successfully reloaded
*/
void onRepoReloaded(RepoLoader loader);
}
}

View File

@ -1,19 +0,0 @@
package org.meowcat.edxposed.manager.util;
import java.util.concurrent.Callable;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
public class TaskRunner {
private final Executor executor = Executors.newSingleThreadExecutor(); // change according to your requirements
public <R> void executeAsync(Callable<R> callable) {
executor.execute(() -> {
try {
callable.call();
} catch (Exception e) {
e.printStackTrace();
}
});
}
}

View File

@ -1,10 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:tint="?attr/colorControlNormal"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="@android:color/white"
android:pathData="M13,5v6h1.17L12,13.17 9.83,11L11,11L11,5h2m2,-2L9,3v6L5,9l7,7 7,-7h-4L15,3zM19,18L5,18v2h14v-2z" />
</vector>

View File

@ -1,10 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:tint="?attr/colorControlNormal"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="@android:color/white"
android:pathData="M18,16.08c-0.76,0 -1.44,0.3 -1.96,0.77L8.91,12.7c0.05,-0.23 0.09,-0.46 0.09,-0.7s-0.04,-0.47 -0.09,-0.7l7.05,-4.11c0.54,0.5 1.25,0.81 2.04,0.81 1.66,0 3,-1.34 3,-3s-1.34,-3 -3,-3 -3,1.34 -3,3c0,0.24 0.04,0.47 0.09,0.7L8.04,9.81C7.5,9.31 6.79,9 6,9c-1.66,0 -3,1.34 -3,3s1.34,3 3,3c0.79,0 1.5,-0.31 2.04,-0.81l7.12,4.16c-0.05,0.21 -0.08,0.43 -0.08,0.65 0,1.61 1.31,2.92 2.92,2.92s2.92,-1.31 2.92,-2.92c0,-1.61 -1.31,-2.92 -2.92,-2.92zM18,4c0.55,0 1,0.45 1,1s-0.45,1 -1,1 -1,-0.45 -1,-1 0.45,-1 1,-1zM6,13c-0.55,0 -1,-0.45 -1,-1s0.45,-1 1,-1 1,0.45 1,1 -0.45,1 -1,1zM18,20.02c-0.55,0 -1,-0.45 -1,-1s0.45,-1 1,-1 1,0.45 1,1 -0.45,1 -1,1z" />
</vector>

View File

@ -1,10 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:tint="?attr/colorControlNormal"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="@android:color/white"
android:pathData="M3,18h6v-2L3,16v2zM3,6v2h18L21,6L3,6zM3,13h12v-2L3,11v2z" />
</vector>

View File

@ -1,41 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/snackbar"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipChildren="false"
android:clipToPadding="false">
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
android:id="@+id/swipeRefreshLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipChildren="false"
android:clipToPadding="false"
android:orientation="vertical"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
<org.meowcat.edxposed.manager.ui.widget.RecyclerViewBugFixed
android:id="@+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipChildren="false"
android:clipToPadding="false" />
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
<com.google.android.material.appbar.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="?actionBarTheme"
app:liftOnScrollTargetViewId="@id/recyclerView">
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?colorActionBar"
app:popupTheme="?actionBarPopupTheme" />
</com.google.android.material.appbar.AppBarLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View File

@ -1,40 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/snackbar"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipChildren="false"
android:clipToPadding="false">
<androidx.viewpager.widget.ViewPager
android:id="@+id/download_pager"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipChildren="false"
android:clipToPadding="false"
app:layout_behavior="@string/appbar_scrolling_view_behavior" />
<com.google.android.material.appbar.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="?actionBarTheme"
app:liftOnScroll="false">
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?colorActionBar"
app:popupTheme="?actionBarPopupTheme" />
<com.google.android.material.tabs.TabLayout
android:id="@+id/sliding_tabs"
style="?tabLayoutTheme"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?colorActionBar"
app:tabMode="fixed" />
</com.google.android.material.appbar.AppBarLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View File

@ -1,30 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical"
android:padding="8dp"
tools:context=".ui.activity.DownloadDetailsActivity">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
tools:ignore="ContentDescription" />
<TextView
android:id="@android:id/message"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:paddingBottom="8dp"
android:textAppearance="?android:attr/textAppearanceMedium" />
<Button
android:id="@+id/reload"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/menuReload" />
</LinearLayout>

View File

@ -172,57 +172,6 @@
</RelativeLayout>
</com.google.android.material.card.MaterialCardView>
<!-- <com.google.android.material.card.MaterialCardView
android:id="@+id/downloads"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="16dp"
android:layout_marginTop="10dp"
android:clickable="true"
android:focusable="true"
android:foreground="?attr/selectableItemBackground"
app:cardCornerRadius="8dp"
app:cardElevation="4dp"
app:cardPreventCornerOverlap="false">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingStart="18dp"
android:paddingTop="16dp"
android:paddingEnd="18dp"
android:paddingBottom="16dp">
<ImageView
android:id="@+id/downloads_icon"
android:layout_width="28dp"
android:layout_height="28dp"
android:layout_centerVertical="true"
android:contentDescription="@string/Downloads"
app:srcCompat="@drawable/ic_get_app" />
<TextView
android:id="@+id/downloads_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="25dp"
android:layout_toEndOf="@id/downloads_icon"
android:text="@string/Downloads"
android:textAppearance="@style/TextAppearance.AppCompat.Medium" />
<TextView
android:id="@+id/download_summary"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/downloads_title"
android:layout_alignStart="@id/downloads_title"
android:layout_marginTop="2dp"
android:text="@string/ModuleUpgradable"
android:textAppearance="@style/TextAppearance.AppCompat.Small"
android:textColor="@android:color/darker_gray" />
</RelativeLayout>
</com.google.android.material.card.MaterialCardView>
-->
<LinearLayout
android:id="@+id/apps"
android:layout_width="match_parent"

View File

@ -1,55 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".ui.fragment.DownloadDetailsFragment">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:clipToPadding="false"
android:orientation="vertical"
android:padding="8dp"
tools:ignore="RtlSymmetry">
<TextView
android:id="@+id/download_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceLarge"
android:textStyle="bold" />
<TextView
android:id="@+id/download_author"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="3dp"
android:textAppearance="?android:attr/textAppearanceSmall"
android:textStyle="italic" />
<TextView
android:id="@+id/download_description"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceSmall" />
<com.google.android.material.card.MaterialCardView
android:id="@+id/activity_main_status"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:layout_marginBottom="10dp"
app:cardCornerRadius="8dp">
<LinearLayout
android:id="@+id/download_moreinfo_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="8dp" />
</com.google.android.material.card.MaterialCardView>
</LinearLayout>
</ScrollView>

View File

@ -1,19 +0,0 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:id="@android:id/title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceSmall"
android:textStyle="bold" />
<TextView
android:id="@android:id/message"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceSmall" />
</LinearLayout>

View File

@ -1,24 +0,0 @@
<merge xmlns:android="http://schemas.android.com/apk/res/android">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/txtInfo"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_weight="1"
android:textAppearance="?android:attr/textAppearanceSmall" />
<com.google.android.material.button.MaterialButton
android:id="@+id/btnDownload"
style="@style/Widget.MaterialComponents.Button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="4dp"
android:text="@string/download_view_download"
android:theme="@style/Widget.AppCompat.Button.Colored" />
</LinearLayout>
</merge>

View File

@ -1,41 +0,0 @@
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?listItemBackground"
android:paddingHorizontal="16dp"
android:paddingVertical="?downloadVerticalPadding">
<TextView
android:id="@+id/title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAppearance="?attr/textAppearanceListItem"
android:textSize="16sp" />
<TextView
android:id="@+id/description"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/title"
android:layout_alignStart="@id/title"
android:layout_marginBottom="8dip"
android:textAppearance="?android:attr/textAppearanceSmall" />
<TextView
android:id="@+id/downloadStatus"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/description"
android:layout_alignStart="@id/description"
android:textAppearance="?android:attr/textAppearanceSmall"
android:textColor="?android:textColorSecondary"
android:visibility="gone" />
<TextView
android:id="@+id/timestamps"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/downloadStatus"
android:layout_alignStart="@id/description"
android:textAppearance="?android:attr/textAppearanceSmall" />
</RelativeLayout>

View File

@ -1,87 +0,0 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:clipChildren="false"
android:clipToPadding="false"
android:minHeight="?attr/listPreferredItemHeight"
android:orientation="vertical"
android:paddingHorizontal="8dp"
android:paddingVertical="8dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:baselineAligned="false">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginEnd="4dp"
android:layout_weight="1"
android:orientation="vertical">
<TextView
android:id="@+id/txtStatus"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceSmall" />
<TextView
android:id="@+id/txtVersion"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceLarge"
android:textIsSelectable="false" />
</LinearLayout>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="4dp"
android:layout_weight="1"
android:gravity="end"
android:orientation="vertical">
<TextView
android:id="@+id/txtRelType"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAllCaps="true"
android:textAppearance="?android:attr/textAppearanceSmall"
android:textIsSelectable="false"
android:textStyle="italic" />
<TextView
android:id="@+id/txtUploadDate"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceSmall"
android:textColor="?android:attr/textColorTertiary"
android:textIsSelectable="false"
android:textStyle="italic" />
</LinearLayout>
</LinearLayout>
<org.meowcat.edxposed.manager.ui.widget.DownloadView
android:id="@+id/downloadView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:clipChildren="false" />
<TextView
android:id="@+id/txtChangesTitle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="@string/changes"
android:textAppearance="?android:attr/textAppearanceSmall"
android:textStyle="bold" />
<TextView
android:id="@+id/txtChanges"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceSmall" />
</LinearLayout>

View File

@ -1,14 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?colorStickyHeader"
android:padding="8dp">
<TextView
android:id="@android:id/title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="4dip"
android:textAllCaps="true" />
</LinearLayout>

View File

@ -1,17 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/menu_search"
android:title="@string/menuSearch"
app:actionViewClass="androidx.appcompat.widget.SearchView"
app:showAsAction="always" />
<item
android:id="@+id/menu_sort"
android:icon="@drawable/ic_sort"
android:title="@string/download_sorting_title"
app:showAsAction="ifRoom|withText" />
</menu>

View File

@ -1,22 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/menu_share"
android:icon="@drawable/ic_share"
android:title="@string/share"
app:showAsAction="ifRoom" />
<item
android:id="@+id/menu_refresh"
android:icon="@drawable/ic_refresh"
android:title="@string/menuReload"
app:showAsAction="ifRoom" />
<item
android:id="@+id/ignoreUpdate"
android:checkable="true"
android:title="@string/ignore_updates" />
</menu>

View File

@ -1,12 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:tools="http://schemas.android.com/tools" tools:ignore="MissingTranslation">
<string-array name="download_sort_order" translatable="false">
<item>@string/download_sorting_status</item>
<item>@string/download_sorting_updated</item>
<item>@string/download_sorting_created</item>
</string-array>
<string-array name="theme_texts" translatable="false">
<item>@string/follow_system</item>
<item>@string/settings_theme_light</item>
@ -19,52 +13,6 @@
<item>2</item>
</string-array>
<string-array name="release_type_texts" translatable="false">
<item>@string/reltype_stable_summary</item>
<item>@string/reltype_beta_summary</item>
<item>@string/reltype_experimental_summary</item>
</string-array>
<string-array name="release_type_values" translatable="false">
<item>stable</item>
<item>beta</item>
<item>experimental</item>
</string-array>
<string-array name="module_release_type_texts" translatable="false">
<item>@string/reltype_use_global_summary</item>
<item>@string/reltype_stable_summary</item>
<item>@string/reltype_beta_summary</item>
<item>@string/reltype_experimental_summary</item>
</string-array>
<string-array name="list_sort_texts">
<item>@string/sort_by_name</item>
<item>@string/sort_by_name_reverse</item>
<item>@string/sort_by_package_name</item>
<item>@string/sort_by_package_name_reverse</item>
<item>@string/sort_by_install_time</item>
<item>@string/sort_by_install_time_reverse</item>
<item>@string/sort_by_update_time</item>
<item>@string/sort_by_update_time_reverse</item>
</string-array>
<string-array name="list_sort_values" translatable="false">
<item>0</item>
<item>1</item>
<item>2</item>
<item>3</item>
<item>4</item>
<item>5</item>
<item>6</item>
<item>7</item>
</string-array>
<string-array name="module_release_type_values" translatable="false">
<item />
<item>stable</item>
<item>beta</item>
<item>experimental</item>
</string-array>
<string-array name="variant_texts" translatable="false">
<item>YAHFA</item>
<item>SandHook</item>

View File

@ -1,14 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<SimpleMenuPreference
android:defaultValue=""
android:entries="@array/module_release_type_texts"
android:entryValues="@array/module_release_type_values"
android:key="release_type"
android:summary="%s"
android:title="@string/settings_release_type"
app:iconSpaceReserved="false" />
</PreferenceScreen>