Bug fixes

This commit is contained in:
NekoInverter 2020-03-07 11:53:16 +08:00
parent 4ee0d48af6
commit cc938da982
No known key found for this signature in database
GPG Key ID: 280D6CCCF95715F9
22 changed files with 716 additions and 679 deletions

View File

@ -16,11 +16,11 @@ import static de.robv.android.xposed.installer.util.InstallZipUtil.parseXposedPr
public class XposedApp extends Application {
public static final String TAG = "XposedApp";
private static final File EDXPOSED_PROP_FILE = new File("/system/framework/edconfig.jar");
private static XposedApp mInstance = null;
public InstallZipUtil.XposedProp mXposedProp;
private static XposedApp instance = null;
public InstallZipUtil.XposedProp xposedProp;
public static XposedApp getInstance() {
return mInstance;
return instance;
}
// This method is hooked by XposedBridge to return the current version
@ -31,7 +31,7 @@ public class XposedApp extends Application {
public void onCreate() {
super.onCreate();
mInstance = this;
instance = this;
reloadXposedProp();
}
@ -51,7 +51,7 @@ public class XposedApp extends Application {
}
}
synchronized (this) {
mXposedProp = prop;
xposedProp = prop;
}
}
}

View File

@ -66,7 +66,7 @@ public class BaseActivity extends AppCompatActivity {
protected void setupWindowInsets(View rootView, View secondView) {
rootView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION);
ViewCompat.setOnApplyWindowInsetsListener(rootView, (v, insets) -> {
if (secondView != null) {
if (secondView != null && insets.getTappableElementInsets().bottom != insets.getSystemWindowInsetBottom()) {
secondView.setPadding(0, 0, 0, insets.getSystemWindowInsetBottom());
}
rootView.setPadding(insets.getSystemWindowInsetLeft(), insets.getSystemWindowInsetTop(), insets.getSystemWindowInsetRight(), insets.getTappableElementInsets().bottom);

View File

@ -3,6 +3,7 @@ package org.meowcat.edxposed.manager;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.os.Bundle;
import android.os.Handler;
import android.view.Menu;
import android.view.View;
@ -17,15 +18,22 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import org.meowcat.edxposed.manager.adapters.AppAdapter;
import org.meowcat.edxposed.manager.adapters.AppHelper;
import org.meowcat.edxposed.manager.adapters.CompatListAdapter;
import org.meowcat.edxposed.manager.adapters.BlackListAdapter;
import org.meowcat.edxposed.manager.databinding.ActivityBlackListBinding;
public class BlackListActivity extends BaseActivity implements AppAdapter.Callback {
private SearchView searchView;
private CompatListAdapter appAdapter;
private BlackListAdapter appAdapter;
private SearchView.OnQueryTextListener searchListener;
private ActivityBlackListBinding binding;
private Runnable runnable = new Runnable() {
@Override
public void run() {
binding.swipeRefreshLayout.setRefreshing(true);
}
};
private Handler handler = new Handler();
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
@ -40,16 +48,17 @@ public class BlackListActivity extends BaseActivity implements AppAdapter.Callba
}
setupWindowInsets(binding.snackbar, binding.recyclerView);
binding.recyclerView.setLayoutManager(new LinearLayoutManager(this));
appAdapter = new CompatListAdapter(this);
final boolean isWhiteListMode = isWhiteListMode();
appAdapter = new BlackListAdapter(this, isWhiteListMode, binding);
binding.recyclerView.setAdapter(appAdapter);
DividerItemDecoration dividerItemDecoration = new DividerItemDecoration(this,
DividerItemDecoration.VERTICAL);
binding.recyclerView.addItemDecoration(dividerItemDecoration);
appAdapter.setCallback(this);
binding.swipeRefreshLayout.setRefreshing(true);
handler.postDelayed(runnable, 300);
binding.swipeRefreshLayout.setOnRefreshListener(appAdapter::refresh);
searchListener = new SearchView.OnQueryTextListener() {
@Override
public boolean onQueryTextSubmit(String query) {
@ -110,6 +119,7 @@ public class BlackListActivity extends BaseActivity implements AppAdapter.Callba
@Override
public void onDataReady() {
handler.removeCallbacks(runnable);
binding.swipeRefreshLayout.setRefreshing(false);
String queryStr = searchView != null ? searchView.getQuery().toString() : "";
appAdapter.filter(queryStr);

View File

@ -38,7 +38,7 @@ public class CompatListActivity extends BaseActivity implements AppAdapter.Callb
}
setupWindowInsets(binding.snackbar, binding.recyclerView);
binding.recyclerView.setLayoutManager(new LinearLayoutManager(this));
appAdapter = new CompatListAdapter(this);
appAdapter = new CompatListAdapter(this, binding);
binding.recyclerView.setAdapter(appAdapter);
DividerItemDecoration dividerItemDecoration = new DividerItemDecoration(this,
DividerItemDecoration.VERTICAL);

View File

@ -25,6 +25,7 @@ import androidx.appcompat.widget.SearchView;
import androidx.recyclerview.widget.DividerItemDecoration;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import androidx.transition.TransitionManager;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.timehop.stickyheadersrecyclerview.StickyRecyclerHeadersAdapter;
@ -173,6 +174,7 @@ public class DownloadActivity extends BaseActivity implements RepoLoader.RepoLis
private void reloadItems() {
runOnUiThread(() -> {
adapter.swapCursor(RepoDb.queryModuleOverview(sortingOrder, filterText));
TransitionManager.beginDelayedTransition(binding.recyclerView);
adapter.notifyDataSetChanged();
});
}

View File

@ -36,7 +36,9 @@ public class DownloadDetailsFragment extends Fragment {
}
DownloadDetailsBinding binding = DownloadDetailsBinding.inflate(inflater, container, false);
ViewCompat.setOnApplyWindowInsetsListener(binding.getRoot(), (v, insets) -> {
binding.getRoot().setPadding(0, 0, 0, insets.getSystemWindowInsetBottom());
if (insets.getTappableElementInsets().bottom != insets.getSystemWindowInsetBottom()) {
binding.getRoot().setPadding(0, 0, 0, insets.getSystemWindowInsetBottom());
}
return insets;
});
binding.downloadTitle.setText(module.name);

View File

@ -92,7 +92,9 @@ public class DownloadDetailsVersionsFragment extends ListFragment {
getListView().setClipToPadding(false);
getListView().setClipToPadding(false);
ViewCompat.setOnApplyWindowInsetsListener(view, (v, insets) -> {
getListView().setPadding(0, 0, 0, insets.getSystemWindowInsetBottom());
if (insets.getTappableElementInsets().bottom != insets.getSystemWindowInsetBottom()) {
getListView().setPadding(0, 0, 0, insets.getSystemWindowInsetBottom());
}
return insets;
});
}

View File

@ -12,6 +12,7 @@ import androidx.appcompat.app.ActionBar;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentPagerAdapter;
import androidx.transition.TransitionManager;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.google.gson.Gson;
@ -93,6 +94,7 @@ public class EdDownloadActivity extends BaseActivity {
try {
final JSONUtils.XposedJson xposedJson = new Gson().fromJson(result, JSONUtils.XposedJson.class);
TransitionManager.beginDelayedTransition(binding.tabLayout);
for (XposedTab tab : xposedJson.tabs) {
if (tab.sdks.contains(Build.VERSION.SDK_INT)) {
tabsAdapter.addFragment(tab.name, BaseAdvancedInstaller.newInstance(tab));

View File

@ -237,7 +237,7 @@ public class LogsActivity extends BaseActivity {
mProgressDialog = new MaterialAlertDialogBuilder(LogsActivity.this).create();
mProgressDialog.setMessage(getString(R.string.loading));
mProgressDialog.setCancelable(false);
handler.postDelayed(mRunnable, 500);
handler.postDelayed(mRunnable, 300);
}
@Override

View File

@ -13,7 +13,7 @@ import org.meowcat.edxposed.manager.util.RepoLoader;
public class MainActivity extends BaseActivity implements RepoLoader.RepoListener, ModuleUtil.ModuleListener {
ActivityMainBinding binding;
private RepoLoader mRepoLoader;
private RepoLoader repoLoader;
@SuppressLint("PrivateResource")
@Override
@ -21,10 +21,10 @@ public class MainActivity extends BaseActivity implements RepoLoader.RepoListene
super.onCreate(savedInstanceState);
binding = ActivityMainBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
mRepoLoader = RepoLoader.getInstance();
setupWindowInsets(binding.snackbar, binding.nestedScrollView);
repoLoader = RepoLoader.getInstance();
ModuleUtil.getInstance().addListener(this);
mRepoLoader.addListener(this, false);
repoLoader.addListener(this, false);
binding.modules.setOnClickListener(v -> {
Intent intent = new Intent();
intent.setClass(getApplicationContext(), ModulesActivity.class);
@ -110,8 +110,8 @@ public class MainActivity extends BaseActivity implements RepoLoader.RepoListene
private void notifyDataSetChanged() {
runOnUiThread(() -> {
String frameworkUpdateVersion = mRepoLoader.getFrameworkUpdateVersion();
boolean moduleUpdateAvailable = mRepoLoader.hasModuleUpdates();
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) {
@ -145,7 +145,7 @@ public class MainActivity extends BaseActivity implements RepoLoader.RepoListene
protected void onDestroy() {
super.onDestroy();
ModuleUtil.getInstance().removeListener(this);
mRepoLoader.removeListener(this);
repoLoader.removeListener(this);
}
}

View File

@ -22,6 +22,7 @@ import androidx.appcompat.widget.SwitchCompat;
import androidx.recyclerview.widget.DividerItemDecoration;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import androidx.transition.TransitionManager;
import com.google.android.material.snackbar.Snackbar;
@ -86,6 +87,7 @@ public class ModulesActivity extends BaseActivity implements ModuleUtil.ModuleLi
}
}
adapter.addAll(showList);
TransitionManager.beginDelayedTransition(binding.recyclerView);
adapter.notifyDataSetChanged();
moduleUtil.updateModulesList(false);
binding.swipeRefreshLayout.setRefreshing(false);

View File

@ -4,6 +4,7 @@ import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.DialogInterface;
import android.content.Intent;
import android.graphics.Color;
import android.os.Bundle;
import android.os.FileUtils;
import android.view.View;
@ -344,7 +345,7 @@ public class SettingsActivity extends BaseActivity {
transparent.setOnPreferenceChangeListener((preference, newValue) -> {
boolean enabled = (Boolean) newValue;
Activity activity = getActivity();
if (activity != null && !XposedApp.getPreferences().getBoolean("black_dark_theme", false)) {
if (activity != null && activity.getWindow().getStatusBarColor() != Color.BLACK) {
if (enabled) {
activity.getWindow().setStatusBarColor(ContextCompat.getColor(activity, R.color.colorActionBar));
} else {
@ -408,7 +409,9 @@ public class SettingsActivity extends BaseActivity {
((FrameLayout) getListView().getParent()).setClipChildren(false);
((FrameLayout) getListView().getParent()).setClipToPadding(false);
ViewCompat.setOnApplyWindowInsetsListener(view, (v, insets) -> {
getListView().setPadding(0, 0, 0, insets.getSystemWindowInsetBottom());
if (insets.getTappableElementInsets().bottom != insets.getSystemWindowInsetBottom()) {
getListView().setPadding(0, 0, 0, insets.getSystemWindowInsetBottom());
}
return insets;
});
}

View File

@ -60,7 +60,7 @@ public class XposedApp extends de.robv.android.xposed.installer.XposedApp implem
}
public static InstallZipUtil.XposedProp getXposedProp() {
return de.robv.android.xposed.installer.XposedApp.getInstance().mXposedProp;
return de.robv.android.xposed.installer.XposedApp.getInstance().xposedProp;
}
public static void runOnUiThread(Runnable action) {

View File

@ -16,9 +16,11 @@ import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.appcompat.widget.SwitchCompat;
import androidx.recyclerview.widget.RecyclerView;
import androidx.transition.TransitionManager;
import org.meowcat.edxposed.manager.R;
import org.meowcat.edxposed.manager.XposedApp;
import org.meowcat.edxposed.manager.databinding.ActivityBlackListBinding;
import org.meowcat.edxposed.manager.util.InstallApkUtil;
import java.text.DateFormat;
@ -41,9 +43,11 @@ public class AppAdapter extends RecyclerView.Adapter<AppAdapter.ViewHolder> {
private PackageManager pm;
private ApplicationFilter filter;
private Comparator<ApplicationInfo> cmp;
private ActivityBlackListBinding binding;
AppAdapter(Context context) {
AppAdapter(Context context, ActivityBlackListBinding binding) {
this.context = context;
this.binding = binding;
fullList = showList = Collections.emptyList();
checkedList = Collections.emptyList();
filter = new ApplicationFilter();
@ -262,6 +266,7 @@ public class AppAdapter extends RecyclerView.Adapter<AppAdapter.ViewHolder> {
@Override
protected void publishResults(CharSequence constraint, FilterResults results) {
TransitionManager.beginDelayedTransition(binding.recyclerView);
notifyDataSetChanged();
}
}

View File

@ -6,6 +6,7 @@ import android.widget.CompoundButton;
import org.meowcat.edxposed.manager.R;
import org.meowcat.edxposed.manager.XposedApp;
import org.meowcat.edxposed.manager.databinding.ActivityBlackListBinding;
import org.meowcat.edxposed.manager.util.ModuleUtil;
import org.meowcat.edxposed.manager.util.ToastUtil;
@ -18,8 +19,8 @@ public class BlackListAdapter extends AppAdapter {
private volatile boolean isWhiteListMode;
private List<String> checkedList;
public BlackListAdapter(Context context, boolean isWhiteListMode) {
super(context);
public BlackListAdapter(Context context, boolean isWhiteListMode, ActivityBlackListBinding binding) {
super(context, binding);
this.isWhiteListMode = isWhiteListMode;
}

View File

@ -5,6 +5,7 @@ import android.content.pm.ApplicationInfo;
import android.widget.CompoundButton;
import org.meowcat.edxposed.manager.R;
import org.meowcat.edxposed.manager.databinding.ActivityBlackListBinding;
import org.meowcat.edxposed.manager.util.ToastUtil;
import java.util.List;
@ -13,8 +14,8 @@ public class CompatListAdapter extends AppAdapter {
private List<String> checkedList;
public CompatListAdapter(Context context) {
super(context);
public CompatListAdapter(Context context, ActivityBlackListBinding binding) {
super(context, binding);
}
@Override

View File

@ -1,6 +1,5 @@
package org.meowcat.edxposed.manager.util;
import android.annotation.SuppressLint;
import android.app.DownloadManager;
import android.app.DownloadManager.Query;
import android.app.DownloadManager.Request;
@ -34,31 +33,28 @@ import java.util.Objects;
public class DownloadsUtil {
public static final String MIME_TYPE_APK = "application/vnd.android.package-archive";
//private static final String MIME_TYPE_ZIP = "application/zip";
private static final Map<String, DownloadFinishedCallback> mCallbacks = new HashMap<>();
@SuppressLint("StaticFieldLeak")
private static final XposedApp mApp = XposedApp.getInstance();
private static final SharedPreferences mPref = mApp
.getSharedPreferences("download_cache", Context.MODE_PRIVATE);
private static final Map<String, DownloadFinishedCallback> callbacks = new HashMap<>();
private static final SharedPreferences pref = XposedApp.getInstance().getSharedPreferences("download_cache", Context.MODE_PRIVATE);
private static DownloadInfo add(Builder b) {
Context context = b.mContext;
removeAllForUrl(context, b.mUrl);
Context context = b.context;
removeAllForUrl(context, b.url);
synchronized (mCallbacks) {
mCallbacks.put(b.mUrl, b.mCallback);
synchronized (callbacks) {
callbacks.put(b.url, b.callback);
}
Request request = new Request(Uri.parse(b.mUrl));
request.setTitle(b.mTitle);
request.setMimeType(b.mMimeType.toString());
Request request = new Request(Uri.parse(b.url));
request.setTitle(b.title);
request.setMimeType(b.mimeType.toString());
/*if (b.mSave) {
try {
request.setDestinationInExternalPublicDir(savePath, b.mTitle + b.mMimeType.getExtension());
request.setDestinationInExternalPublicDir(savePath, b.title + b.mimeType.getExtension());
} catch (IllegalStateException e) {
Toast.makeText(context, e.getMessage(), Toast.LENGTH_SHORT).show();
}
} else */
File destination = new File(context.getExternalCacheDir(), "/downloads/" + b.mTitle + b.mMimeType.getExtension());
File destination = new File(context.getExternalCacheDir(), "/downloads/" + b.title + b.mimeType.getExtension());
removeAllForLocalFile(context, destination);
request.setDestinationUri(Uri.fromFile(destination));
request.setNotificationVisibility(Request.VISIBILITY_VISIBLE);
@ -295,8 +291,8 @@ public class DownloadsUtil {
return;
DownloadFinishedCallback callback;
synchronized (mCallbacks) {
callback = mCallbacks.get(info.url);
synchronized (callbacks) {
callback = callbacks.get(info.url);
}
if (callback == null)
@ -345,8 +341,8 @@ public class DownloadsUtil {
}
if (useNotModifiedTags) {
String modified = mPref.getString("download_" + url + "_modified", null);
String etag = mPref.getString("download_" + url + "_etag", null);
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);
@ -366,7 +362,7 @@ public class DownloadsUtil {
return new SyncDownloadInfo(SyncDownloadInfo.STATUS_NOT_MODIFIED, null);
} else if (responseCode < 200 || responseCode >= 300) {
return new SyncDownloadInfo(SyncDownloadInfo.STATUS_FAILED,
mApp.getString(R.string.repo_download_failed_http,
XposedApp.getInstance().getString(R.string.repo_download_failed_http,
url, responseCode,
httpConnection.getResponseMessage()));
}
@ -385,7 +381,7 @@ public class DownloadsUtil {
String modified = httpConnection.getHeaderField("Last-Modified");
String etag = httpConnection.getHeaderField("ETag");
mPref.edit()
pref.edit()
.putString("download_" + url + "_modified", modified)
.putString("download_" + url + "_etag", etag).apply();
}
@ -394,7 +390,7 @@ public class DownloadsUtil {
} catch (Throwable t) {
return new SyncDownloadInfo(SyncDownloadInfo.STATUS_FAILED,
mApp.getString(R.string.repo_download_failed, url,
XposedApp.getInstance().getString(R.string.repo_download_failed, url,
t.getMessage()));
} finally {
@ -415,10 +411,10 @@ public class DownloadsUtil {
static void clearCache(String url) {
if (url != null) {
mPref.edit().remove("download_" + url + "_modified")
pref.edit().remove("download_" + url + "_modified")
.remove("download_" + url + "_etag").apply();
} else {
mPref.edit().clear().apply();
pref.edit().clear().apply();
}
}
@ -453,39 +449,39 @@ public class DownloadsUtil {
}
public static class Builder {
private final Context mContext;
boolean mModule = false;
private String mTitle = null;
private String mUrl = null;
private DownloadFinishedCallback mCallback = null;
private MIME_TYPES mMimeType = MIME_TYPES.APK;
private final Context context;
boolean module = false;
private String title = null;
private String url = null;
private DownloadFinishedCallback callback = null;
private MIME_TYPES mimeType = MIME_TYPES.APK;
public Builder(Context context) {
mContext = context;
this.context = context;
}
public Builder setTitle(String title) {
mTitle = title;
this.title = title;
return this;
}
public Builder setUrl(String url) {
mUrl = url;
this.url = url;
return this;
}
public Builder setCallback(DownloadFinishedCallback callback) {
mCallback = callback;
this.callback = callback;
return this;
}
Builder setMimeType(@SuppressWarnings("SameParameterValue") MIME_TYPES mimeType) {
mMimeType = mimeType;
Builder setMimeType(MIME_TYPES mimeType) {
this.mimeType = mimeType;
return this;
}
public Builder setModule(boolean module) {
this.mModule = module;
this.module = module;
return this;
}

View File

@ -35,30 +35,28 @@ public final class ModuleUtil {
private static final String MODULES_LIST_FILE = XposedApp.BASE_DIR + "conf/modules.list";
private static final String PLAY_STORE_PACKAGE = "com.android.vending";
public static int MIN_MODULE_VERSION = 2; // reject modules with
private static ModuleUtil mInstance = null;
private final XposedApp mApp;
private final PackageManager mPm;
private final String mFrameworkPackageName;
private final List<ModuleListener> mListeners = new CopyOnWriteArrayList<>();
private SharedPreferences mPref;
private InstalledModule mFramework = null;
private Map<String, InstalledModule> mInstalledModules;
private boolean mIsReloading = false;
private Toast mToast;
private static ModuleUtil instance = null;
private final PackageManager pm;
private final String frameworkPackageName;
private final List<ModuleListener> listeners = new CopyOnWriteArrayList<>();
private SharedPreferences pref;
private InstalledModule framework = null;
private Map<String, InstalledModule> installedModules;
private boolean isReloading = false;
private Toast toast;
private ModuleUtil() {
mApp = XposedApp.getInstance();
mPref = mApp.getSharedPreferences("enabled_modules", Context.MODE_PRIVATE);
mPm = mApp.getPackageManager();
mFrameworkPackageName = mApp.getPackageName();
pref = XposedApp.getInstance().getSharedPreferences("enabled_modules", Context.MODE_PRIVATE);
pm = XposedApp.getInstance().getPackageManager();
frameworkPackageName = XposedApp.getInstance().getPackageName();
}
public static synchronized ModuleUtil getInstance() {
if (mInstance == null) {
mInstance = new ModuleUtil();
mInstance.reloadInstalledModules();
if (instance == null) {
instance = new ModuleUtil();
instance.reloadInstalledModules();
}
return mInstance;
return instance;
}
public static int extractIntPart(String str) {
@ -76,9 +74,9 @@ public final class ModuleUtil {
@SuppressWarnings("MismatchedQueryAndUpdateOfCollection")
public void reloadInstalledModules() {
synchronized (this) {
if (mIsReloading)
if (isReloading)
return;
mIsReloading = true;
isReloading = true;
}
Map<String, InstalledModule> modules = new HashMap<>();
@ -86,7 +84,7 @@ public final class ModuleUtil {
try {
RepoDb.deleteAllInstalledModules();
for (PackageInfo pkg : mPm.getInstalledPackages(PackageManager.GET_META_DATA)) {
for (PackageInfo pkg : pm.getInstalledPackages(PackageManager.GET_META_DATA)) {
ApplicationInfo app = pkg.applicationInfo;
if (!app.enabled)
continue;
@ -96,7 +94,7 @@ public final class ModuleUtil {
installed = new InstalledModule(pkg, false);
modules.put(pkg.packageName, installed);
} else if (isFramework(pkg.packageName)) {
mFramework = installed = new InstalledModule(pkg, true);
framework = installed = new InstalledModule(pkg, true);
}
if (installed != null)
@ -108,25 +106,25 @@ public final class ModuleUtil {
RepoDb.endTransation();
}
mInstalledModules = modules;
installedModules = modules;
synchronized (this) {
mIsReloading = false;
isReloading = false;
}
for (ModuleListener listener : mListeners) {
listener.onInstalledModulesReloaded(mInstance);
for (ModuleListener listener : listeners) {
listener.onInstalledModulesReloaded(instance);
}
}
public InstalledModule reloadSingleModule(String packageName) {
PackageInfo pkg;
try {
pkg = mPm.getPackageInfo(packageName, PackageManager.GET_META_DATA);
pkg = pm.getPackageInfo(packageName, PackageManager.GET_META_DATA);
} catch (NameNotFoundException e) {
RepoDb.deleteInstalledModule(packageName);
InstalledModule old = mInstalledModules.remove(packageName);
InstalledModule old = installedModules.remove(packageName);
if (old != null) {
for (ModuleListener listener : mListeners) {
listener.onSingleInstalledModuleReloaded(mInstance, packageName, null);
for (ModuleListener listener : listeners) {
listener.onSingleInstalledModuleReloaded(instance, packageName, null);
}
}
return null;
@ -136,18 +134,18 @@ public final class ModuleUtil {
if (app.enabled && app.metaData != null && app.metaData.containsKey("xposedmodule")) {
InstalledModule module = new InstalledModule(pkg, false);
RepoDb.insertInstalledModule(module);
mInstalledModules.put(packageName, module);
for (ModuleListener listener : mListeners) {
listener.onSingleInstalledModuleReloaded(mInstance, packageName,
installedModules.put(packageName, module);
for (ModuleListener listener : listeners) {
listener.onSingleInstalledModuleReloaded(instance, packageName,
module);
}
return module;
} else {
RepoDb.deleteInstalledModule(packageName);
InstalledModule old = mInstalledModules.remove(packageName);
InstalledModule old = installedModules.remove(packageName);
if (old != null) {
for (ModuleListener listener : mListeners) {
listener.onSingleInstalledModuleReloaded(mInstance, packageName, null);
for (ModuleListener listener : listeners) {
listener.onSingleInstalledModuleReloaded(instance, packageName, null);
}
}
return null;
@ -155,49 +153,49 @@ public final class ModuleUtil {
}
public synchronized boolean isLoading() {
return mIsReloading;
return isReloading;
}
public InstalledModule getFramework() {
return mFramework;
return framework;
}
public String getFrameworkPackageName() {
return mFrameworkPackageName;
return frameworkPackageName;
}
private boolean isFramework(String packageName) {
return mFrameworkPackageName.equals(packageName);
return frameworkPackageName.equals(packageName);
}
// public boolean isInstalled(String packageName) {
// return mInstalledModules.containsKey(packageName) || isFramework(packageName);
// return installedModules.containsKey(packageName) || isFramework(packageName);
// }
public InstalledModule getModule(String packageName) {
return mInstalledModules.get(packageName);
return installedModules.get(packageName);
}
public Map<String, InstalledModule> getModules() {
return mInstalledModules;
return installedModules;
}
public void setModuleEnabled(String packageName, boolean enabled) {
if (enabled) {
mPref.edit().putInt(packageName, 1).apply();
pref.edit().putInt(packageName, 1).apply();
} else {
mPref.edit().remove(packageName).apply();
pref.edit().remove(packageName).apply();
}
}
public boolean isModuleEnabled(String packageName) {
return mPref.contains(packageName);
return pref.contains(packageName);
}
public List<InstalledModule> getEnabledModules() {
LinkedList<InstalledModule> result = new LinkedList<>();
for (String packageName : mPref.getAll().keySet()) {
for (String packageName : pref.getAll().keySet()) {
InstalledModule module = getModule(packageName);
if (module != null)
result.add(module);
@ -213,7 +211,7 @@ public final class ModuleUtil {
Log.i(XposedApp.TAG, "ModuleUtil -> updating modules.list");
int installedXposedVersion = XposedApp.getXposedVersion();
if (!XposedApp.getPreferences().getBoolean("skip_xposedminversion_check", false) && installedXposedVersion <= 0 && showToast) {
Toast.makeText(mApp, R.string.notinstalled, Toast.LENGTH_SHORT).show();
Toast.makeText(XposedApp.getInstance(), R.string.notinstalled, Toast.LENGTH_SHORT).show();
return;
}
@ -223,14 +221,14 @@ public final class ModuleUtil {
for (InstalledModule module : enabledModules) {
if (!XposedApp.getPreferences().getBoolean("skip_xposedminversion_check", false) && (module.minVersion > installedXposedVersion || module.minVersion < MIN_MODULE_VERSION) && showToast) {
Toast.makeText(mApp, R.string.notinstalled, Toast.LENGTH_SHORT).show();
Toast.makeText(XposedApp.getInstance(), R.string.notinstalled, Toast.LENGTH_SHORT).show();
continue;
}
modulesList.println(module.app.sourceDir);
try {
String installer = mPm.getInstallerPackageName(module.app.packageName);
String installer = pm.getInstallerPackageName(module.app.packageName);
if (!PLAY_STORE_PACKAGE.equals(installer))
enabledModulesList.println(module.app.packageName);
} catch (Exception ignored) {
@ -247,27 +245,27 @@ public final class ModuleUtil {
}
} catch (IOException e) {
Log.e(XposedApp.TAG, "ModuleUtil -> cannot write " + MODULES_LIST_FILE, e);
Toast.makeText(mApp, "cannot write " + MODULES_LIST_FILE + e, Toast.LENGTH_SHORT).show();
Toast.makeText(XposedApp.getInstance(), "cannot write " + MODULES_LIST_FILE + e, Toast.LENGTH_SHORT).show();
}
}
@SuppressWarnings("SameParameterValue")
private void showToast(int message) {
if (mToast != null) {
mToast.cancel();
mToast = null;
if (toast != null) {
toast.cancel();
toast = null;
}
mToast = Toast.makeText(mApp, mApp.getString(message), Toast.LENGTH_SHORT);
mToast.show();
toast = Toast.makeText(XposedApp.getInstance(), XposedApp.getInstance().getString(message), Toast.LENGTH_SHORT);
toast.show();
}
public void addListener(ModuleListener listener) {
if (!mListeners.contains(listener))
mListeners.add(listener);
if (!listeners.contains(listener))
listeners.add(listener);
}
public void removeListener(ModuleListener listener) {
mListeners.remove(listener);
listeners.remove(listener);
}
public interface ModuleListener {
@ -337,7 +335,7 @@ public final class ModuleUtil {
public String getAppName() {
if (appName == null)
appName = app.loadLabel(mPm).toString();
appName = app.loadLabel(pm).toString();
return appName;
}
@ -351,7 +349,7 @@ public final class ModuleUtil {
try {
int resId = (Integer) descriptionRaw;
if (resId != 0)
descriptionTmp = mPm.getResourcesForApplication(app).getString(resId).trim();
descriptionTmp = pm.getResourcesForApplication(app).getString(resId).trim();
} catch (Exception ignored) {
}
}
@ -371,13 +369,13 @@ public final class ModuleUtil {
Intent mIntent = new Intent(Intent.ACTION_MAIN);
//mIntent.addCategory(ModulesFragment.SETTINGS_CATEGORY);
mIntent.setPackage(app.packageName);
List<ResolveInfo> ris = mPm.queryIntentActivities(mIntent, 0);
List<ResolveInfo> ris = pm.queryIntentActivities(mIntent, 0);
Drawable result;
if (ris == null || ris.size() <= 0)
result = app.loadIcon(mPm);
result = app.loadIcon(pm);
else
result = ris.get(0).activityInfo.loadIcon(mPm);
result = ris.get(0).activityInfo.loadIcon(pm);
iconCache = result.getConstantState();
return result;

View File

@ -1,5 +1,6 @@
package org.meowcat.edxposed.manager.util;
import android.annotation.SuppressLint;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
@ -21,9 +22,6 @@ import org.meowcat.edxposed.manager.MainActivity;
import org.meowcat.edxposed.manager.R;
import org.meowcat.edxposed.manager.XposedApp;
import java.util.LinkedList;
import java.util.List;
public final class NotificationUtil {
public static final int NOTIFICATION_MODULE_NOT_ACTIVATED_YET = 0;
@ -39,195 +37,188 @@ public final class NotificationUtil {
private static final int PENDING_INTENT_ACTIVATE_MODULE = 5;
private static final int PENDING_INTENT_INSTALL_APK = 6;
private static final String COLORED_NOTIFICATION = "colored_notification";
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";
private static Context sContext = null;
private static NotificationManager sNotificationManager;
@SuppressLint("StaticFieldLeak")
private static Context context = null;
private static NotificationManager notificationManager;
private static SharedPreferences prefs;
public static void init() {
if (sContext != null) return;
if (context != null) {
return;
}
sContext = XposedApp.getInstance();
context = XposedApp.getInstance();
prefs = XposedApp.getPreferences();
sNotificationManager = (NotificationManager) sContext.getSystemService(Context.NOTIFICATION_SERVICE);
notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
NotificationChannel channel = new NotificationChannel(NOTIFICATION_UPDATE_CHANNEL, sContext.getString(R.string.download_section_update_available), NotificationManager.IMPORTANCE_DEFAULT);
NotificationChannel channel1 = new NotificationChannel(NOTIFICATION_MODULES_CHANNEL, sContext.getString(R.string.nav_item_modules), NotificationManager.IMPORTANCE_DEFAULT);
sNotificationManager.createNotificationChannel(channel);
sNotificationManager.createNotificationChannel(channel1);
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);
}
}
public static void cancel(int id) {
sNotificationManager.cancel(id);
notificationManager.cancel(id);
}
public static void cancel(String tag, int id) {
sNotificationManager.cancel(tag, id);
notificationManager.cancel(tag, id);
}
public static void cancelAll() {
sNotificationManager.cancelAll();
notificationManager.cancelAll();
}
public static void showNotActivatedNotification(String packageName, String appName) {
Intent intent = new Intent(sContext, MainActivity.class).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK).putExtra(FRAGMENT_ID, 1);
PendingIntent pModulesTab = PendingIntent.getActivity(sContext, PENDING_INTENT_OPEN_MODULES, intent, PendingIntent.FLAG_UPDATE_CURRENT);
Intent intent = new Intent(context, MainActivity.class).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK).putExtra(FRAGMENT_ID, 1);
PendingIntent pModulesTab = PendingIntent.getActivity(context, PENDING_INTENT_OPEN_MODULES, intent, PendingIntent.FLAG_UPDATE_CURRENT);
String title = sContext.getString(R.string.module_is_not_activated_yet);
NotificationCompat.Builder builder = new NotificationCompat.Builder(sContext).setContentTitle(title).setContentText(appName)
.setTicker(title).setContentIntent(pModulesTab)
.setVibrate(new long[]{0}).setAutoCancel(true)
.setSmallIcon(R.drawable.ic_notification);
if (prefs.getBoolean(HEADS_UP, true) && Build.VERSION.SDK_INT >= 21)
String title = context.getString(R.string.module_is_not_activated_yet);
NotificationCompat.Builder builder = getNotificationBuilder(title, appName, NOTIFICATION_MODULES_CHANNEL)
.setContentIntent(pModulesTab);
if (prefs.getBoolean(HEADS_UP, true)) {
builder.setPriority(2);
builder.setColor(ContextCompat.getColor(sContext, R.color.colorPrimary));
Intent iActivateAndReboot = new Intent(sContext, RebootReceiver.class);
}
Intent iActivateAndReboot = new Intent(context, RebootReceiver.class);
iActivateAndReboot.putExtra(RebootReceiver.EXTRA_ACTIVATE_MODULE, packageName);
PendingIntent pActivateAndReboot = PendingIntent.getBroadcast(sContext, PENDING_INTENT_ACTIVATE_MODULE_AND_REBOOT,
PendingIntent pActivateAndReboot = PendingIntent.getBroadcast(context, PENDING_INTENT_ACTIVATE_MODULE_AND_REBOOT,
iActivateAndReboot, PendingIntent.FLAG_UPDATE_CURRENT);
Intent iActivate = new Intent(sContext, RebootReceiver.class);
Intent iActivate = new Intent(context, RebootReceiver.class);
iActivate.putExtra(RebootReceiver.EXTRA_ACTIVATE_MODULE, packageName);
iActivate.putExtra(RebootReceiver.EXTRA_ACTIVATE_MODULE_AND_RETURN, true);
PendingIntent pActivate = PendingIntent.getBroadcast(sContext, PENDING_INTENT_ACTIVATE_MODULE,
PendingIntent pActivate = PendingIntent.getBroadcast(context, PENDING_INTENT_ACTIVATE_MODULE,
iActivate, PendingIntent.FLAG_UPDATE_CURRENT);
NotificationCompat.BigTextStyle notiStyle = new NotificationCompat.BigTextStyle();
notiStyle.setBigContentTitle(title);
notiStyle.bigText(sContext.getString(R.string.module_is_not_activated_yet_detailed, appName));
builder.setStyle(notiStyle).setChannelId(NOTIFICATION_MODULES_CHANNEL);
NotificationCompat.BigTextStyle style = new NotificationCompat.BigTextStyle();
style.setBigContentTitle(title);
style.bigText(context.getString(R.string.module_is_not_activated_yet_detailed, appName));
builder.setStyle(style);
// Only show the quick activation button if any module has been
// enabled before,
// to ensure that the user know the way to disable the module later.
if (!ModuleUtil.getInstance().getEnabledModules().isEmpty()) {
builder.addAction(new NotificationCompat.Action.Builder(R.drawable.ic_apps, sContext.getString(R.string.activate_and_reboot), pActivateAndReboot).build());
builder.addAction(new NotificationCompat.Action.Builder(R.drawable.ic_apps, sContext.getString(R.string.activate_only), pActivate).build());
builder.addAction(new NotificationCompat.Action.Builder(R.drawable.ic_apps, context.getString(R.string.activate_and_reboot), pActivateAndReboot).build());
builder.addAction(new NotificationCompat.Action.Builder(R.drawable.ic_apps, context.getString(R.string.activate_only), pActivate).build());
}
sNotificationManager.notify(packageName, NOTIFICATION_MODULE_NOT_ACTIVATED_YET, builder.build());
notificationManager.notify(packageName, NOTIFICATION_MODULE_NOT_ACTIVATED_YET, builder.build());
}
public static void showModulesUpdatedNotification() {
Intent intent = new Intent(sContext, MainActivity.class);
Intent intent = new Intent(context, MainActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.putExtra(FRAGMENT_ID, 0);
PendingIntent pInstallTab = PendingIntent.getActivity(sContext, PENDING_INTENT_OPEN_INSTALL,
PendingIntent pInstallTab = PendingIntent.getActivity(context, PENDING_INTENT_OPEN_INSTALL,
intent, PendingIntent.FLAG_UPDATE_CURRENT);
String title = sContext
String title = context
.getString(R.string.xposed_module_updated_notification_title);
String message = sContext
String message = context
.getString(R.string.xposed_module_updated_notification);
NotificationCompat.Builder builder = new NotificationCompat.Builder(sContext).setContentTitle(title).setContentText(message)
.setTicker(title).setContentIntent(pInstallTab)
.setVibrate(new long[]{0}).setAutoCancel(true)
.setSmallIcon(R.drawable.ic_notification);
if (prefs.getBoolean(HEADS_UP, true) && Build.VERSION.SDK_INT >= 21)
NotificationCompat.Builder builder = getNotificationBuilder(title, message, NOTIFICATION_MODULES_CHANNEL)
.setContentIntent(pInstallTab);
if (prefs.getBoolean(HEADS_UP, true)) {
builder.setPriority(2);
builder.setColor(ContextCompat.getColor(sContext, R.color.colorPrimary));
Intent iSoftReboot = new Intent(sContext, RebootReceiver.class);
}
Intent iSoftReboot = new Intent(context, RebootReceiver.class);
iSoftReboot.putExtra(RebootReceiver.EXTRA_SOFT_REBOOT, true);
PendingIntent pSoftReboot = PendingIntent.getBroadcast(sContext, PENDING_INTENT_SOFT_REBOOT,
PendingIntent pSoftReboot = PendingIntent.getBroadcast(context, PENDING_INTENT_SOFT_REBOOT,
iSoftReboot, PendingIntent.FLAG_UPDATE_CURRENT);
Intent iReboot = new Intent(sContext, RebootReceiver.class);
PendingIntent pReboot = PendingIntent.getBroadcast(sContext, PENDING_INTENT_REBOOT,
Intent iReboot = new Intent(context, RebootReceiver.class);
PendingIntent pReboot = PendingIntent.getBroadcast(context, PENDING_INTENT_REBOOT,
iReboot, PendingIntent.FLAG_UPDATE_CURRENT);
builder.addAction(new NotificationCompat.Action.Builder(0, sContext.getString(R.string.reboot), pReboot).build());
builder.addAction(new NotificationCompat.Action.Builder(0, sContext.getString(R.string.soft_reboot), pSoftReboot).build());
builder.setChannelId(NOTIFICATION_MODULES_CHANNEL);
builder.addAction(new NotificationCompat.Action.Builder(0, context.getString(R.string.reboot), pReboot).build());
builder.addAction(new NotificationCompat.Action.Builder(0, context.getString(R.string.soft_reboot), pSoftReboot).build());
sNotificationManager.notify(null, NOTIFICATION_MODULES_UPDATED, builder.build());
notificationManager.notify(null, NOTIFICATION_MODULES_UPDATED, builder.build());
}
static void showModuleInstallNotification(@StringRes int title, @StringRes int message, String path, Object... args) {
showModuleInstallNotification(sContext.getString(title), sContext.getString(message, args), path, title == R.string.installation_error);
showModuleInstallNotification(context.getString(title), context.getString(message, args), path, title == R.string.installation_error);
}
private static void showModuleInstallNotification(String title, String message, String path, boolean error) {
NotificationCompat.Builder builder = new NotificationCompat.Builder(
sContext).setContentTitle(title).setContentText(message)
.setVibrate(new long[]{0}).setAutoCancel(true)
.setSmallIcon(R.drawable.ic_notification);
NotificationCompat.Builder builder = getNotificationBuilder(title, message, NOTIFICATION_MODULES_CHANNEL);
if (error) {
Intent iInstallApk = new Intent(sContext, ApkReceiver.class);
Intent iInstallApk = new Intent(context, ApkReceiver.class);
iInstallApk.putExtra(ApkReceiver.EXTRA_APK_PATH, path);
PendingIntent pInstallApk = PendingIntent.getBroadcast(sContext, PENDING_INTENT_INSTALL_APK, iInstallApk, PendingIntent.FLAG_UPDATE_CURRENT);
PendingIntent pInstallApk = PendingIntent.getBroadcast(context, PENDING_INTENT_INSTALL_APK, iInstallApk, PendingIntent.FLAG_UPDATE_CURRENT);
builder.addAction(new NotificationCompat.Action.Builder(0, sContext.getString(R.string.installation_apk_normal), pInstallApk).build());
builder.addAction(new NotificationCompat.Action.Builder(0, context.getString(R.string.installation_apk_normal), pInstallApk).build());
}
if (prefs.getBoolean(HEADS_UP, true) && Build.VERSION.SDK_INT >= 21)
if (prefs.getBoolean(HEADS_UP, true)) {
builder.setPriority(2);
builder.setColor(ContextCompat.getColor(sContext, R.color.colorPrimary));
}
NotificationCompat.BigTextStyle notiStyle = new NotificationCompat.BigTextStyle();
notiStyle.setBigContentTitle(title);
notiStyle.bigText(message);
builder.setStyle(notiStyle).setChannelId(NOTIFICATION_MODULES_CHANNEL);
builder.setStyle(notiStyle);
sNotificationManager.notify(null, NOTIFICATION_MODULE_INSTALLATION, builder.build());
notificationManager.notify(null, NOTIFICATION_MODULE_INSTALLATION, builder.build());
new android.os.Handler().postDelayed(new Runnable() {
@Override
public void run() {
cancel(NOTIFICATION_MODULE_INSTALLATION);
}
}, 10 * 1000);
new android.os.Handler().postDelayed(() -> cancel(NOTIFICATION_MODULE_INSTALLATION), 10 * 1000);
}
public static void showModuleInstallingNotification(String appName) {
String title = sContext.getString(R.string.install_load);
String message = sContext.getString(R.string.install_load_apk, appName);
NotificationCompat.Builder builder = new NotificationCompat.Builder(sContext).setContentTitle(title).setContentText(message)
.setVibrate(new long[]{0}).setProgress(0, 0, true)
.setSmallIcon(R.drawable.ic_notification).setOngoing(true);
builder.setColor(ContextCompat.getColor(sContext, R.color.colorPrimary));
String title = context.getString(R.string.install_load);
String message = context.getString(R.string.install_load_apk, appName);
NotificationCompat.Builder builder = getNotificationBuilder(title, message, NOTIFICATION_MODULES_CHANNEL)
.setProgress(0, 0, true)
.setOngoing(true);
NotificationCompat.BigTextStyle notiStyle = new NotificationCompat.BigTextStyle();
notiStyle.setBigContentTitle(title);
notiStyle.bigText(message);
builder.setStyle(notiStyle).setChannelId(NOTIFICATION_MODULES_CHANNEL);
builder.setStyle(notiStyle);
sNotificationManager.notify(null, NOTIFICATION_MODULE_INSTALLING, builder.build());
notificationManager.notify(null, NOTIFICATION_MODULE_INSTALLING, builder.build());
}
public static void showInstallerUpdateNotification() {
Intent intent = new Intent(sContext, MainActivity.class);
Intent intent = new Intent(context, MainActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.putExtra(FRAGMENT_ID, 0);
PendingIntent pInstallTab = PendingIntent.getActivity(sContext, PENDING_INTENT_OPEN_INSTALL,
PendingIntent pInstallTab = PendingIntent.getActivity(context, PENDING_INTENT_OPEN_INSTALL,
intent, PendingIntent.FLAG_UPDATE_CURRENT);
String title = sContext.getString(R.string.app_name);
String message = sContext.getString(R.string.newVersion);
NotificationCompat.Builder builder = new NotificationCompat.Builder(sContext).setContentTitle(title).setContentText(message)
.setTicker(title).setContentIntent(pInstallTab)
.setVibrate(new long[]{0}).setAutoCancel(true)
.setSmallIcon(R.drawable.ic_notification);
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) && Build.VERSION.SDK_INT >= 21)
if (prefs.getBoolean(HEADS_UP, true)) {
builder.setPriority(2);
builder.setColor(ContextCompat.getColor(sContext, R.color.colorPrimary));
}
NotificationCompat.BigTextStyle notiStyle = new NotificationCompat.BigTextStyle();
notiStyle.setBigContentTitle(title);
notiStyle.bigText(message);
builder.setStyle(notiStyle).setChannelId(NOTIFICATION_UPDATE_CHANNEL);
builder.setStyle(notiStyle);
sNotificationManager.notify(null, NOTIFICATION_INSTALLER_UPDATE, builder.build());
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)
.setContentText(message)
.setVibrate(new long[]{0})
.setAutoCancel(true)
.setSmallIcon(R.drawable.ic_notification)
.setColor(ContextCompat.getColor(context, R.color.colorPrimary));
}
public static class RebootReceiver extends BroadcastReceiver {
@ -244,7 +235,7 @@ public final class NotificationUtil {
* expanded notification panel and is therefore not visible to the
* user.
*/
sContext.sendBroadcast(new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS));
context.sendBroadcast(new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS));
cancelAll();
if (intent.hasExtra(EXTRA_ACTIVATE_MODULE)) {
@ -252,7 +243,7 @@ public final class NotificationUtil {
ModuleUtil moduleUtil = ModuleUtil.getInstance();
moduleUtil.setModuleEnabled(packageName, true);
moduleUtil.updateModulesList(false);
Toast.makeText(sContext, R.string.module_activated, Toast.LENGTH_SHORT).show();
Toast.makeText(context, R.string.module_activated, Toast.LENGTH_SHORT).show();
if (intent.hasExtra(EXTRA_ACTIVATE_MODULE_AND_RETURN)) return;
}
@ -262,7 +253,6 @@ public final class NotificationUtil {
return;
}
List<String> messages = new LinkedList<>();
boolean isSoftReboot = intent.getBooleanExtra(EXTRA_SOFT_REBOOT,
false);
int returnCode = isSoftReboot
@ -287,7 +277,7 @@ public final class NotificationUtil {
* expanded notification panel and is therefore not visible to the
* user.
*/
sContext.sendBroadcast(new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS));
context.sendBroadcast(new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS));
if (intent.hasExtra(EXTRA_APK_PATH)) {
String path = intent.getStringExtra(EXTRA_APK_PATH);

View File

@ -46,48 +46,48 @@ 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 mInstance = null;
private final List<RepoListener> mListeners = new CopyOnWriteArrayList<>();
private final Map<String, ReleaseType> mLocalReleaseTypesCache = new HashMap<>();
private XposedApp mApp;
private SharedPreferences mPref;
private SharedPreferences mModulePref;
private ConnectivityManager mConMgr;
private boolean mIsLoading = false;
private boolean mReloadTriggeredOnce = false;
private Map<Long, Repository> mRepositories = null;
private ReleaseType mGlobalReleaseType;
private SwipeRefreshLayout mSwipeRefreshLayout;
private static RepoLoader instance = null;
private final List<RepoListener> listeners = new CopyOnWriteArrayList<>();
private final Map<String, ReleaseType> localReleaseTypesCache = new HashMap<>();
private XposedApp app;
private SharedPreferences pref;
private SharedPreferences modulePref;
private ConnectivityManager conMgr;
private boolean isLoading = false;
private boolean reloadTriggeredOnce = false;
private Map<Long, Repository> repositories = null;
private ReleaseType globalReleaseType;
private SwipeRefreshLayout swipeRefreshLayout;
private RepoLoader() {
mInstance = this;
mApp = XposedApp.getInstance();
mPref = mApp.getSharedPreferences("repo", Context.MODE_PRIVATE);
instance = this;
app = XposedApp.getInstance();
pref = app.getSharedPreferences("repo", Context.MODE_PRIVATE);
DEFAULT_REPOSITORIES = XposedApp.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";
mModulePref = mApp.getSharedPreferences("module_settings", Context.MODE_PRIVATE);
mConMgr = (ConnectivityManager) mApp.getSystemService(Context.CONNECTIVITY_SERVICE);
mGlobalReleaseType = ReleaseType.fromString(XposedApp.getPreferences().getString("release_type_global", "stable"));
modulePref = app.getSharedPreferences("module_settings", Context.MODE_PRIVATE);
conMgr = (ConnectivityManager) app.getSystemService(Context.CONNECTIVITY_SERVICE);
globalReleaseType = ReleaseType.fromString(XposedApp.getPreferences().getString("release_type_global", "stable"));
refreshRepositories();
}
public static synchronized RepoLoader getInstance() {
if (mInstance == null)
if (instance == null)
new RepoLoader();
return mInstance;
return instance;
}
private boolean refreshRepositories() {
mRepositories = RepoDb.getRepositories();
repositories = RepoDb.getRepositories();
// Unlikely case (usually only during initial load): DB state doesn't
// fit to configuration
boolean needReload = false;
String[] config = (mPref.getString("repositories", DEFAULT_REPOSITORIES) + "").split("\\|");
if (mRepositories.size() != config.length) {
String[] config = (pref.getString("repositories", DEFAULT_REPOSITORIES) + "").split("\\|");
if (repositories.size() != config.length) {
needReload = true;
} else {
int i = 0;
for (Repository repo : mRepositories.values()) {
for (Repository repo : repositories.values()) {
if (!repo.url.equals(config[i++])) {
needReload = true;
break;
@ -102,16 +102,16 @@ public class RepoLoader {
for (String url : config) {
RepoDb.insertRepository(url);
}
mRepositories = RepoDb.getRepositories();
repositories = RepoDb.getRepositories();
return true;
}
public void setReleaseTypeGlobal(String relTypeString) {
ReleaseType relType = ReleaseType.fromString(relTypeString);
if (mGlobalReleaseType == relType)
if (globalReleaseType == relType)
return;
mGlobalReleaseType = relType;
globalReleaseType = relType;
// Updating the latest version for all modules takes a moment
new Thread("DBUpdate") {
@ -129,8 +129,10 @@ public class RepoLoader {
if (getReleaseTypeLocal(packageName) == relType)
return;
synchronized (mLocalReleaseTypesCache) {
mLocalReleaseTypesCache.put(packageName, relType);
synchronized (localReleaseTypesCache) {
if (relType != null) {
localReleaseTypesCache.put(packageName, relType);
}
}
RepoDb.updateModuleLatestVersion(packageName);
@ -138,20 +140,22 @@ public class RepoLoader {
}
private ReleaseType getReleaseTypeLocal(String packageName) {
synchronized (mLocalReleaseTypesCache) {
if (mLocalReleaseTypesCache.containsKey(packageName))
return mLocalReleaseTypesCache.get(packageName);
synchronized (localReleaseTypesCache) {
if (localReleaseTypesCache.containsKey(packageName))
return localReleaseTypesCache.get(packageName);
String value = mModulePref.getString(packageName + "_release_type",
String value = modulePref.getString(packageName + "_release_type",
null);
ReleaseType result = (!TextUtils.isEmpty(value)) ? ReleaseType.fromString(value) : null;
mLocalReleaseTypesCache.put(packageName, result);
if (result != null) {
localReleaseTypesCache.put(packageName, result);
}
return result;
}
}
public Repository getRepository(long repoId) {
return mRepositories.get(repoId);
return repositories.get(repoId);
}
public Module getModule(String packageName) {
@ -179,42 +183,42 @@ public class RepoLoader {
if (localSetting != null)
return localSetting;
else
return mGlobalReleaseType;
return globalReleaseType;
}
public void triggerReload(final boolean force) {
mReloadTriggeredOnce = true;
reloadTriggeredOnce = true;
if (force) {
resetLastUpdateCheck();
} else {
long lastUpdateCheck = mPref.getLong("last_update_check", 0);
long lastUpdateCheck = pref.getLong("last_update_check", 0);
if (System.currentTimeMillis() < lastUpdateCheck + UPDATE_FREQUENCY)
return;
}
NetworkInfo netInfo = mConMgr.getActiveNetworkInfo();
NetworkInfo netInfo = conMgr.getActiveNetworkInfo();
if (netInfo == null || !netInfo.isConnected())
return;
synchronized (this) {
if (mIsLoading)
if (isLoading)
return;
mIsLoading = true;
isLoading = true;
}
mApp.updateProgressIndicator(mSwipeRefreshLayout);
app.updateProgressIndicator(swipeRefreshLayout);
new Thread("RepositoryReload") {
public void run() {
final List<String> messages = new LinkedList<>();
boolean hasChanged = downloadAndParseFiles(messages);
mPref.edit().putLong("last_update_check", System.currentTimeMillis()).apply();
pref.edit().putLong("last_update_check", System.currentTimeMillis()).apply();
if (!messages.isEmpty()) {
XposedApp.runOnUiThread(() -> {
for (String message : messages) {
Toast.makeText(mApp, message, Toast.LENGTH_LONG).show();
Toast.makeText(app, message, Toast.LENGTH_LONG).show();
}
});
}
@ -223,38 +227,38 @@ public class RepoLoader {
notifyListeners();
synchronized (this) {
mIsLoading = false;
isLoading = false;
}
mApp.updateProgressIndicator(mSwipeRefreshLayout);
app.updateProgressIndicator(swipeRefreshLayout);
}
}.start();
}
public void setSwipeRefreshLayout(SwipeRefreshLayout mSwipeRefreshLayout) {
this.mSwipeRefreshLayout = mSwipeRefreshLayout;
public void setSwipeRefreshLayout(SwipeRefreshLayout swipeRefreshLayout) {
this.swipeRefreshLayout = swipeRefreshLayout;
}
public void triggerFirstLoadIfNecessary() {
if (!mReloadTriggeredOnce)
if (!reloadTriggeredOnce)
triggerReload(false);
}
public void resetLastUpdateCheck() {
mPref.edit().remove("last_update_check").apply();
pref.edit().remove("last_update_check").apply();
}
public synchronized boolean isLoading() {
return mIsLoading;
return isLoading;
}
public void clear(boolean notify) {
synchronized (this) {
// TODO Stop reloading repository when it should be cleared
if (mIsLoading)
if (isLoading)
return;
RepoDb.deleteRepositories();
mRepositories = new LinkedHashMap<>(0);
repositories = new LinkedHashMap<>(0);
DownloadsUtil.clearCache(null);
resetLastUpdateCheck();
}
@ -270,7 +274,7 @@ public class RepoLoader {
sb.append("|");
sb.append(repos[i]);
}
mPref.edit().putString("repositories", sb.toString()).apply();
pref.edit().putString("repositories", sb.toString()).apply();
if (refreshRepositories())
triggerReload(true);
}
@ -287,7 +291,7 @@ public class RepoLoader {
String filename = "repo_" + HashUtil.md5(repo) + ".xml";
if (repo.endsWith(".gz"))
filename += ".gz";
return new File(mApp.getCacheDir(), filename);
return new File(app.getCacheDir(), filename);
}
private boolean downloadAndParseFiles(List<String> messages) {
@ -296,7 +300,7 @@ public class RepoLoader {
final AtomicInteger insertCounter = new AtomicInteger();
final AtomicInteger deleteCounter = new AtomicInteger();
for (Entry<Long, Repository> repoEntry : mRepositories.entrySet()) {
for (Entry<Long, Repository> repoEntry : repositories.entrySet()) {
final long repoId = repoEntry.getKey();
final Repository repo = repoEntry.getValue();
@ -367,13 +371,13 @@ public class RepoLoader {
RepoDb.setTransactionSuccessful();
} catch (SQLiteException e) {
XposedApp.runOnUiThread(() -> new MaterialAlertDialogBuilder(mApp)
XposedApp.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(mApp, DownloadActivity.class);
PendingIntent pi = PendingIntent.getActivity(mApp, 0, i, PendingIntent.FLAG_CANCEL_CURRENT);
AlarmManager mgr = (AlarmManager) mApp.getSystemService(Context.ALARM_SERVICE);
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);
})
@ -383,7 +387,7 @@ public class RepoLoader {
DownloadsUtil.clearCache(url);
} catch (Throwable t) {
Log.e(XposedApp.TAG, "RepoLoader -> Cannot load repository from " + url, t);
messages.add(mApp.getString(R.string.repo_load_failed, url, t.getMessage()));
messages.add(app.getString(R.string.repo_load_failed, url, t.getMessage()));
DownloadsUtil.clearCache(url);
} finally {
if (in != null)
@ -391,6 +395,7 @@ public class RepoLoader {
in.close();
} catch (IOException ignored) {
}
//noinspection ResultOfMethodCallIgnored
cacheFile.delete();
RepoDb.endTransation();
}
@ -402,20 +407,20 @@ public class RepoLoader {
}
public void addListener(RepoListener listener, boolean triggerImmediately) {
if (!mListeners.contains(listener))
mListeners.add(listener);
if (!listeners.contains(listener))
listeners.add(listener);
if (triggerImmediately)
listener.onRepoReloaded(this);
}
public void removeListener(RepoListener listener) {
mListeners.remove(listener);
listeners.remove(listener);
}
private void notifyListeners() {
for (RepoListener listener : mListeners) {
listener.onRepoReloaded(mInstance);
for (RepoListener listener : listeners) {
listener.onRepoReloaded(instance);
}
}

View File

@ -1,308 +1,327 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
<androidx.coordinatorlayout.widget.CoordinatorLayout 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:id="@+id/snackbar"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:animateLayoutChanges="true"
android:orientation="vertical"
tools:context=".MainActivity">
android:clipChildren="false"
android:clipToPadding="false">
<RelativeLayout
<androidx.core.widget.NestedScrollView
android:id="@+id/nestedScrollView"
android:layout_width="match_parent"
android:layout_height="72dp"
android:layout_marginStart="32dp"
android:layout_marginEnd="32dp"
android:layout_height="match_parent"
android:clipChildren="false"
android:clipToPadding="false">
<ImageView
android:id="@+id/app_icon"
android:layout_width="42dp"
android:layout_height="64dp"
android:layout_alignParentStart="true"
android:layout_centerVertical="true"
android:adjustViewBounds="true"
android:contentDescription="@string/app_name"
android:scaleType="centerCrop"
app:srcCompat="@drawable/ic_launcher_foreground" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_marginStart="25dp"
android:layout_toEndOf="@id/app_icon"
android:contentDescription="@string/app_name"
android:text="@string/app_name"
android:textAppearance="@style/TextAppearance.AppCompat.Title"
android:textSize="20sp" />
<ImageView
android:id="@+id/menu_more"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:layout_centerVertical="true"
android:background="?selectableItemBackgroundBorderless"
android:clickable="true"
android:contentDescription="@string/abc_action_menu_overflow_description"
android:focusable="true"
android:padding="5dp"
app:srcCompat="@drawable/ic_more"
tools:ignore="PrivateResource" />
</RelativeLayout>
<com.google.android.material.card.MaterialCardView
android:id="@+id/status"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="16dp"
android:clickable="true"
android:focusable="true"
android:foreground="?attr/selectableItemBackground"
app:cardBackgroundColor="#4CAF50"
app:cardCornerRadius="8dp"
app:cardElevation="8dp">
<RelativeLayout
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingStart="18dp"
android:paddingTop="20dp"
android:paddingEnd="18dp"
android:paddingBottom="20dp">
android:layout_height="match_parent"
android:animateLayoutChanges="true"
android:clipChildren="false"
android:clipToPadding="false"
android:orientation="vertical"
tools:context=".MainActivity">
<ImageView
android:id="@+id/status_icon"
android:layout_width="28dp"
android:layout_height="28dp"
android:layout_centerVertical="true"
android:contentDescription="@string/Activated"
android:tint="@android:color/white"
app:srcCompat="@drawable/ic_check_circle" />
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="72dp"
android:layout_marginStart="32dp"
android:layout_marginEnd="32dp"
android:clipChildren="false"
android:clipToPadding="false">
<TextView
android:id="@+id/status_title"
android:layout_width="wrap_content"
<ImageView
android:id="@+id/app_icon"
android:layout_width="42dp"
android:layout_height="64dp"
android:layout_alignParentStart="true"
android:layout_centerVertical="true"
android:adjustViewBounds="true"
android:contentDescription="@string/app_name"
android:scaleType="centerCrop"
app:srcCompat="@drawable/ic_launcher_foreground" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_marginStart="25dp"
android:layout_toEndOf="@id/app_icon"
android:contentDescription="@string/app_name"
android:text="@string/app_name"
android:textAppearance="@style/TextAppearance.AppCompat.Title"
android:textSize="20sp"
tools:ignore="RelativeOverlap" />
<ImageView
android:id="@+id/menu_more"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:layout_centerVertical="true"
android:background="?selectableItemBackgroundBorderless"
android:clickable="true"
android:contentDescription="@string/abc_action_menu_overflow_description"
android:focusable="true"
android:padding="2dp"
app:srcCompat="@drawable/ic_more"
tools:ignore="PrivateResource" />
</RelativeLayout>
<com.google.android.material.card.MaterialCardView
android:id="@+id/status"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="25dp"
android:layout_toEndOf="@id/status_icon"
android:text="@string/Activated"
android:textAppearance="@style/TextAppearance.AppCompat.Medium"
android:textColor="@android:color/white" />
android:layout_marginHorizontal="16dp"
android:clickable="true"
android:focusable="true"
android:foreground="?attr/selectableItemBackground"
app:cardBackgroundColor="#4CAF50"
app:cardCornerRadius="8dp"
app:cardElevation="8dp">
<TextView
android:id="@+id/status_summary"
android:layout_width="wrap_content"
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingStart="18dp"
android:paddingTop="20dp"
android:paddingEnd="18dp"
android:paddingBottom="20dp">
<ImageView
android:id="@+id/status_icon"
android:layout_width="28dp"
android:layout_height="28dp"
android:layout_centerVertical="true"
android:contentDescription="@string/Activated"
android:tint="@android:color/white"
app:srcCompat="@drawable/ic_check_circle" />
<TextView
android:id="@+id/status_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="25dp"
android:layout_toEndOf="@id/status_icon"
android:text="@string/Activated"
android:textAppearance="@style/TextAppearance.AppCompat.Medium"
android:textColor="@android:color/white" />
<TextView
android:id="@+id/status_summary"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/status_title"
android:layout_alignStart="@id/status_title"
android:layout_marginTop="5dp"
android:text="@string/ActivatedDetail"
android:textAppearance="@style/TextAppearance.AppCompat.Small"
android:textColor="@android:color/white" />
</RelativeLayout>
</com.google.android.material.card.MaterialCardView>
<com.google.android.material.card.MaterialCardView
android:id="@+id/modules"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/status_title"
android:layout_alignStart="@id/status_title"
android:layout_marginHorizontal="16dp"
android:layout_marginTop="10dp"
android:clickable="true"
android:focusable="true"
android:foreground="?attr/selectableItemBackground"
app:cardCornerRadius="8dp">
<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/modules_icon"
android:layout_width="28dp"
android:layout_height="28dp"
android:layout_centerVertical="true"
android:contentDescription="@string/Modules"
app:srcCompat="@drawable/ic_apps" />
<TextView
android:id="@+id/modules_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="25dp"
android:layout_toEndOf="@id/modules_icon"
android:text="@string/Modules"
android:textAppearance="@style/TextAppearance.AppCompat.Medium" />
<TextView
android:id="@+id/modules_summary"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/modules_title"
android:layout_alignStart="@id/modules_title"
android:layout_marginTop="2dp"
android:text="@string/ModulesDetail"
android:textAppearance="@style/TextAppearance.AppCompat.Small"
android:textColor="@android:color/darker_gray" />
</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">
<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"
android:layout_height="wrap_content"
android:layout_marginHorizontal="16dp"
android:layout_marginTop="10dp"
android:background="@drawable/main_item_background"
android:clickable="true"
android:focusable="true"
android:orientation="horizontal"
android:padding="16dp">
<ImageView
android:layout_width="25dp"
android:layout_height="25dp"
android:contentDescription="@string/Apps"
app:srcCompat="@drawable/outline_list_24" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="25dp"
android:text="@string/Apps"
android:textAppearance="@style/TextAppearance.AppCompat.Medium" />
</LinearLayout>
<LinearLayout
android:id="@+id/logs"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="16dp"
android:layout_marginTop="5dp"
android:text="@string/ActivatedDetail"
android:textAppearance="@style/TextAppearance.AppCompat.Small"
android:textColor="@android:color/white" />
</RelativeLayout>
</com.google.android.material.card.MaterialCardView>
android:background="@drawable/main_item_background"
android:clickable="true"
android:focusable="true"
android:orientation="horizontal"
android:padding="16dp">
<com.google.android.material.card.MaterialCardView
android:id="@+id/modules"
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">
<ImageView
android:layout_width="25dp"
android:layout_height="25dp"
android:contentDescription="@string/Logs"
app:srcCompat="@drawable/ic_assignment" />
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingStart="18dp"
android:paddingTop="16dp"
android:paddingEnd="18dp"
android:paddingBottom="16dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="25dp"
android:text="@string/Logs"
android:textAppearance="@style/TextAppearance.AppCompat.Medium" />
</LinearLayout>
<ImageView
android:id="@+id/modules_icon"
android:layout_width="28dp"
android:layout_height="28dp"
android:layout_centerVertical="true"
android:contentDescription="@string/Modules"
app:srcCompat="@drawable/ic_apps" />
<TextView
android:id="@+id/modules_title"
android:layout_width="wrap_content"
<LinearLayout
android:id="@+id/settings"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="25dp"
android:layout_toEndOf="@id/modules_icon"
android:text="@string/Modules"
android:textAppearance="@style/TextAppearance.AppCompat.Medium" />
android:layout_marginHorizontal="16dp"
android:layout_marginTop="5dp"
android:background="@drawable/main_item_background"
android:clickable="true"
android:focusable="true"
android:orientation="horizontal"
android:padding="16dp">
<TextView
android:id="@+id/modules_summary"
android:layout_width="wrap_content"
<ImageView
android:layout_width="25dp"
android:layout_height="25dp"
android:contentDescription="@string/Settings"
app:srcCompat="@drawable/ic_settings" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="25dp"
android:text="@string/Settings"
android:textAppearance="@style/TextAppearance.AppCompat.Medium" />
</LinearLayout>
<LinearLayout
android:id="@+id/about"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/modules_title"
android:layout_alignStart="@id/modules_title"
android:layout_marginTop="2dp"
android:text="@string/ModulesDetail"
android:textAppearance="@style/TextAppearance.AppCompat.Small"
android:textColor="@android:color/darker_gray" />
</RelativeLayout>
</com.google.android.material.card.MaterialCardView>
android:layout_marginHorizontal="16dp"
android:layout_marginTop="5dp"
android:background="@drawable/main_item_background"
android:clickable="true"
android:focusable="true"
android:orientation="horizontal"
android:padding="16dp">
<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">
<ImageView
android:layout_width="25dp"
android:layout_height="25dp"
android:contentDescription="@string/About"
app:srcCompat="@drawable/ic_info" />
<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"
android:layout_height="wrap_content"
android:layout_marginHorizontal="16dp"
android:layout_marginTop="10dp"
android:background="@drawable/main_item_background"
android:clickable="true"
android:focusable="true"
android:orientation="horizontal"
android:padding="16dp">
<ImageView
android:layout_width="25dp"
android:layout_height="25dp"
android:contentDescription="@string/Apps"
app:srcCompat="@drawable/outline_list_24" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="25dp"
android:text="@string/Apps"
android:textAppearance="@style/TextAppearance.AppCompat.Medium" />
</LinearLayout>
<LinearLayout
android:id="@+id/logs"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="16dp"
android:layout_marginTop="5dp"
android:background="@drawable/main_item_background"
android:clickable="true"
android:focusable="true"
android:orientation="horizontal"
android:padding="16dp">
<ImageView
android:layout_width="25dp"
android:layout_height="25dp"
android:contentDescription="@string/Logs"
app:srcCompat="@drawable/ic_assignment" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="25dp"
android:text="@string/Logs"
android:textAppearance="@style/TextAppearance.AppCompat.Medium" />
</LinearLayout>
<LinearLayout
android:id="@+id/settings"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="16dp"
android:layout_marginTop="5dp"
android:background="@drawable/main_item_background"
android:clickable="true"
android:focusable="true"
android:orientation="horizontal"
android:padding="16dp">
<ImageView
android:layout_width="25dp"
android:layout_height="25dp"
android:contentDescription="@string/Settings"
app:srcCompat="@drawable/ic_settings" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="25dp"
android:text="@string/Settings"
android:textAppearance="@style/TextAppearance.AppCompat.Medium" />
</LinearLayout>
<LinearLayout
android:id="@+id/about"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="16dp"
android:layout_marginTop="5dp"
android:background="@drawable/main_item_background"
android:clickable="true"
android:focusable="true"
android:orientation="horizontal"
android:padding="16dp">
<ImageView
android:layout_width="25dp"
android:layout_height="25dp"
android:contentDescription="@string/About"
app:srcCompat="@drawable/ic_info" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="25dp"
android:text="@string/About"
android:textAppearance="@style/TextAppearance.AppCompat.Medium" />
</LinearLayout>
</LinearLayout>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="25dp"
android:text="@string/About"
android:textAppearance="@style/TextAppearance.AppCompat.Medium" />
</LinearLayout>
</LinearLayout>
</androidx.core.widget.NestedScrollView>
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View File

@ -1,114 +1,113 @@
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
<androidx.constraintlayout.widget.ConstraintLayout 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="wrap_content"
android:layout_gravity="center"
android:background="?selectableItemBackground"
android:clickable="true"
android:focusable="true">
android:focusable="true"
android:minHeight="?attr/listPreferredItemHeight"
android:padding="8dp">
<RelativeLayout
<ImageView
android:id="@+id/app_icon"
android:layout_width="48dp"
android:layout_height="48dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:ignore="ContentDescription"
tools:srcCompat="@tools:sample/backgrounds/scenic" />
<TextView
android:id="@+id/app_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:minHeight="?attr/listPreferredItemHeight"
android:orientation="horizontal"
android:padding="8dp"
android:paddingEnd="12dp">
android:layout_marginStart="8dp"
android:singleLine="false"
android:textAppearance="?android:attr/textAppearanceMedium"
android:textIsSelectable="false"
app:layout_constraintStart_toEndOf="@id/app_icon"
app:layout_constraintTop_toTopOf="parent"
tools:text="@tools:sample/first_names" />
<ImageView
android:id="@+id/app_icon"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_alignParentTop="true"
tools:ignore="ContentDescription" />
<TextView
android:id="@+id/version_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:ellipsize="marquee"
android:marqueeRepeatLimit="marquee_forever"
android:maxWidth="100dp"
android:singleLine="true"
android:textAlignment="viewEnd"
android:textAppearance="?android:attr/textAppearanceSmall"
android:textIsSelectable="false"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="1.0"
app:layout_constraintStart_toEndOf="@+id/app_name"
app:layout_constraintTop_toTopOf="parent"
tools:ignore="RtlHardcoded"
tools:text="@tools:sample/last_names" />
<LinearLayout
android:id="@+id/linear"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_toEndOf="@id/app_icon">
<TextView
android:id="@+id/package_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:ellipsize="end"
android:singleLine="true"
android:textAppearance="?android:attr/textAppearanceSmall"
android:textIsSelectable="false"
app:layout_constrainedWidth="true"
app:layout_constraintEnd_toStartOf="@+id/checkbox"
app:layout_constraintHorizontal_bias="0"
app:layout_constraintStart_toStartOf="@+id/app_name"
app:layout_constraintTop_toBottomOf="@+id/app_name"
tools:text="@tools:sample/cities" />
<TextView
android:id="@+id/app_name"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginEnd="4dp"
android:layout_weight="1"
android:singleLine="false"
android:textAppearance="?android:attr/textAppearanceMedium"
android:textIsSelectable="false" />
<TextView
android:id="@+id/timestamps"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:textAppearance="?android:attr/textAppearanceSmall"
app:layout_constraintStart_toStartOf="@+id/package_name"
app:layout_constraintTop_toBottomOf="@+id/package_name"
tools:text="@tools:sample/cities" />
<TextView
android:id="@+id/version_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="4dp"
android:ellipsize="marquee"
android:gravity="end"
android:marqueeRepeatLimit="marquee_forever"
android:maxWidth="100dp"
android:scrollHorizontally="true"
android:singleLine="true"
android:textAppearance="?android:attr/textAppearanceSmall"
android:textIsSelectable="false"
tools:ignore="RtlHardcoded" />
<TextView
android:id="@+id/description"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:textAppearance="?android:attr/textAppearanceSmall"
android:visibility="gone"
app:layout_constrainedWidth="true"
app:layout_constraintEnd_toStartOf="@+id/checkbox"
app:layout_constraintHorizontal_bias="0"
app:layout_constraintStart_toStartOf="@+id/timestamps"
app:layout_constraintTop_toBottomOf="@+id/timestamps"
tools:text="@tools:sample/lorem" />
</LinearLayout>
<androidx.appcompat.widget.SwitchCompat
android:id="@+id/checkbox"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:checked="false"
android:focusable="false"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/linear"
android:layout_marginStart="8dp"
android:layout_toStartOf="@id/checkbox"
android:layout_toEndOf="@id/app_icon"
android:orientation="vertical">
<TextView
android:id="@+id/warning"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginBottom="8dp"
android:textAppearance="?android:attr/textAppearanceSmall"
android:textColor="@color/warning"
android:visibility="gone"
app:layout_constraintStart_toStartOf="@+id/description"
app:layout_constraintTop_toBottomOf="@+id/description"
tools:text="@tools:sample/lorem" />
<TextView
android:id="@+id/package_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:ellipsize="end"
android:singleLine="true"
android:textAppearance="?android:attr/textAppearanceSmall"
android:textIsSelectable="false" />
<TextView
android:id="@+id/timestamps"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:textAppearance="?android:attr/textAppearanceSmall" />
<TextView
android:id="@+id/description"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:textAppearance="?android:attr/textAppearanceSmall"
android:visibility="gone" />
<TextView
android:id="@+id/warning"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:textAppearance="?android:attr/textAppearanceSmall"
android:textColor="@color/warning"
android:visibility="gone" />
</LinearLayout>
<androidx.appcompat.widget.SwitchCompat
android:id="@+id/checkbox"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:layout_centerVertical="true"
android:layout_marginStart="4dp"
android:checked="false"
android:focusable="false" />
</RelativeLayout>
</FrameLayout>
</androidx.constraintlayout.widget.ConstraintLayout>