Remove storage permission

This commit is contained in:
NekoInverter 2020-03-05 17:33:14 +08:00
parent d2914f947d
commit ed8836ca6d
No known key found for this signature in database
GPG Key ID: 280D6CCCF95715F9
24 changed files with 299 additions and 602 deletions

View File

@ -4,8 +4,6 @@
package="org.meowcat.edxposed.manager"> package="org.meowcat.edxposed.manager">
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" /> <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" /> <uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
@ -98,7 +96,7 @@
<provider <provider
android:name="androidx.core.content.FileProvider" android:name="androidx.core.content.FileProvider"
android:authorities="org.meowcat.edxposed.manager.fileprovider" android:authorities="${applicationId}.fileprovider"
android:exported="false" android:exported="false"
android:grantUriPermissions="true"> android:grantUriPermissions="true">
<meta-data <meta-data

View File

@ -32,6 +32,7 @@ public class XposedApp extends Application {
public void onCreate() { public void onCreate() {
super.onCreate(); super.onCreate();
mInstance = this; mInstance = this;
reloadXposedProp();
} }
public void reloadXposedProp() { public void reloadXposedProp() {

View File

@ -1,13 +1,10 @@
package org.meowcat.edxposed.manager; package org.meowcat.edxposed.manager;
import android.Manifest;
import android.annotation.SuppressLint; import android.annotation.SuppressLint;
import android.content.Context; import android.content.Context;
import android.content.DialogInterface; import android.content.DialogInterface;
import android.content.pm.PackageManager;
import android.content.res.Configuration; import android.content.res.Configuration;
import android.content.res.Resources; import android.content.res.Resources;
import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.os.Looper; import android.os.Looper;
import android.text.TextUtils; import android.text.TextUtils;
@ -20,7 +17,6 @@ import androidx.annotation.Nullable;
import androidx.annotation.StyleRes; import androidx.annotation.StyleRes;
import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.app.AppCompatDelegate; import androidx.appcompat.app.AppCompatDelegate;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat; import androidx.core.content.ContextCompat;
import androidx.core.view.ViewCompat; import androidx.core.view.ViewCompat;
@ -152,16 +148,6 @@ public class BaseActivity extends AppCompatActivity {
.show(); .show();
} }
public boolean checkPermissions() {
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
requestPermissions(new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, XposedApp.WRITE_EXTERNAL_PERMISSION);
}
return true;
}
return false;
}
void reboot(String mode) { void reboot(String mode) {
if (!Shell.rootAccess()) { if (!Shell.rootAccess()) {
showAlert(getString(R.string.root_failed)); showAlert(getString(R.string.root_failed));

View File

@ -1,13 +1,9 @@
package org.meowcat.edxposed.manager; package org.meowcat.edxposed.manager;
import android.Manifest;
import android.content.DialogInterface; import android.content.DialogInterface;
import android.content.Intent; import android.content.Intent;
import android.content.pm.PackageManager;
import android.net.Uri; import android.net.Uri;
import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.os.Handler;
import android.text.Html; import android.text.Html;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
@ -20,11 +16,9 @@ import android.widget.TextView;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.fragment.app.Fragment; import androidx.fragment.app.Fragment;
import com.google.android.material.dialog.MaterialAlertDialogBuilder; import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.google.android.material.snackbar.Snackbar;
import org.meowcat.edxposed.manager.util.NavUtil; import org.meowcat.edxposed.manager.util.NavUtil;
import org.meowcat.edxposed.manager.util.json.XposedTab; import org.meowcat.edxposed.manager.util.json.XposedTab;
@ -33,8 +27,6 @@ import org.meowcat.edxposed.manager.util.json.XposedZip;
import java.util.List; import java.util.List;
import java.util.Objects; import java.util.Objects;
import static org.meowcat.edxposed.manager.XposedApp.WRITE_EXTERNAL_PERMISSION;
public class BaseAdvancedInstaller extends Fragment { public class BaseAdvancedInstaller extends Fragment {
private View mClickedButton; private View mClickedButton;
@ -88,16 +80,6 @@ public class BaseAdvancedInstaller extends Fragment {
return Objects.requireNonNull(tab).description; return Objects.requireNonNull(tab).description;
} }
private boolean checkPermissions() {
if (Build.VERSION.SDK_INT < 23) return false;
if (ActivityCompat.checkSelfPermission(Objects.requireNonNull(getActivity()), Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
requestPermissions(new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, WRITE_EXTERNAL_PERMISSION);
return true;
}
return false;
}
@Override @Override
public void onDestroy() { public void onDestroy() {
super.onDestroy(); super.onDestroy();
@ -143,31 +125,21 @@ public class BaseAdvancedInstaller extends Fragment {
.setMessage(s).setPositiveButton(android.R.string.ok, null).show(); .setMessage(s).setPositiveButton(android.R.string.ok, null).show();
}); });
btnInstall.setOnClickListener(v -> { btnInstall.setOnClickListener(v -> areYouSure(R.string.warningArchitecture,
mClickedButton = v;
if (checkPermissions()) return;
areYouSure(R.string.warningArchitecture,
(dialog, which) -> { (dialog, which) -> {
XposedZip selectedInstaller = (XposedZip) chooserInstallers.getSelectedItem(); XposedZip selectedInstaller = (XposedZip) chooserInstallers.getSelectedItem();
Uri uri = Uri.parse(selectedInstaller.link); Uri uri = Uri.parse(selectedInstaller.link);
Intent intent = new Intent(Intent.ACTION_VIEW, uri); Intent intent = new Intent(Intent.ACTION_VIEW, uri);
startActivity(intent); startActivity(intent);
}); }));
});
btnUninstall.setOnClickListener(v -> { btnUninstall.setOnClickListener(v -> areYouSure(R.string.warningArchitecture,
mClickedButton = v;
if (checkPermissions()) return;
areYouSure(R.string.warningArchitecture,
(dialog, which) -> { (dialog, which) -> {
XposedZip selectedUninstaller = (XposedZip) chooserUninstallers.getSelectedItem(); XposedZip selectedUninstaller = (XposedZip) chooserUninstallers.getSelectedItem();
Uri uri = Uri.parse(selectedUninstaller.link); Uri uri = Uri.parse(selectedUninstaller.link);
Intent intent = new Intent(Intent.ACTION_VIEW, uri); Intent intent = new Intent(Intent.ACTION_VIEW, uri);
startActivity(intent); startActivity(intent);
}); }));
});
noticeTv.setText(Html.fromHtml(notice())); noticeTv.setText(Html.fromHtml(notice()));
author.setText(getString(R.string.download_author, author())); author.setText(getString(R.string.download_author, author()));
@ -198,20 +170,6 @@ public class BaseAdvancedInstaller extends Fragment {
return view; return view;
} }
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (requestCode == WRITE_EXTERNAL_PERMISSION) {
if (grantResults.length == 1 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
if (mClickedButton != null) {
new Handler().postDelayed(() -> mClickedButton.performClick(), 500);
}
} else {
Snackbar.make(getActivity().findViewById(R.id.snackbar), R.string.permissionNotGranted, Snackbar.LENGTH_LONG).show();
}
}
}
@SuppressWarnings("SameParameterValue") @SuppressWarnings("SameParameterValue")
private void areYouSure(int contentTextId, DialogInterface.OnClickListener listener) { private void areYouSure(int contentTextId, DialogInterface.OnClickListener listener) {

View File

@ -11,7 +11,6 @@ import androidx.annotation.Nullable;
import androidx.appcompat.app.ActionBar; import androidx.appcompat.app.ActionBar;
import androidx.appcompat.widget.SearchView; import androidx.appcompat.widget.SearchView;
import androidx.appcompat.widget.Toolbar; import androidx.appcompat.widget.Toolbar;
import androidx.core.view.MenuItemCompat;
import androidx.recyclerview.widget.DividerItemDecoration; import androidx.recyclerview.widget.DividerItemDecoration;
import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.RecyclerView;
@ -73,7 +72,7 @@ public class BlackListActivity extends BaseActivity implements AppAdapter.Callba
@Override @Override
public boolean onCreateOptionsMenu(@NonNull Menu menu) { public boolean onCreateOptionsMenu(@NonNull Menu menu) {
getMenuInflater().inflate(R.menu.menu_app_list, menu); getMenuInflater().inflate(R.menu.menu_app_list, menu);
mSearchView = (SearchView) MenuItemCompat.getActionView(menu.findItem(R.id.menu_search)); mSearchView = (SearchView) menu.findItem(R.id.menu_search).getActionView();
mSearchView.setOnQueryTextListener(mSearchListener); mSearchView.setOnQueryTextListener(mSearchListener);
return super.onCreateOptionsMenu(menu); return super.onCreateOptionsMenu(menu);
} }
@ -126,11 +125,6 @@ public class BlackListActivity extends BaseActivity implements AppAdapter.Callba
AppHelper.showMenu(this, getSupportFragmentManager(), v, info); AppHelper.showMenu(this, getSupportFragmentManager(), v, info);
} }
@Override
public void onPointerCaptureChanged(boolean hasCapture) {
}
@Override @Override
public void onBackPressed() { public void onBackPressed() {
if (mSearchView.isIconified()) { if (mSearchView.isIconified()) {

View File

@ -10,7 +10,6 @@ import androidx.annotation.Nullable;
import androidx.appcompat.app.ActionBar; import androidx.appcompat.app.ActionBar;
import androidx.appcompat.widget.SearchView; import androidx.appcompat.widget.SearchView;
import androidx.appcompat.widget.Toolbar; import androidx.appcompat.widget.Toolbar;
import androidx.core.view.MenuItemCompat;
import androidx.recyclerview.widget.DividerItemDecoration; import androidx.recyclerview.widget.DividerItemDecoration;
import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.RecyclerView;
@ -68,15 +67,10 @@ public class CompatListActivity extends BaseActivity implements AppAdapter.Callb
}; };
} }
@Override
public void onResume() {
super.onResume();
}
@Override @Override
public boolean onCreateOptionsMenu(@NonNull Menu menu) { public boolean onCreateOptionsMenu(@NonNull Menu menu) {
getMenuInflater().inflate(R.menu.menu_app_list, menu); getMenuInflater().inflate(R.menu.menu_app_list, menu);
mSearchView = (SearchView) MenuItemCompat.getActionView(menu.findItem(R.id.menu_search)); mSearchView = (SearchView) menu.findItem(R.id.menu_search).getActionView();
mSearchView.setOnQueryTextListener(mSearchListener); mSearchView.setOnQueryTextListener(mSearchListener);
return super.onCreateOptionsMenu(menu); return super.onCreateOptionsMenu(menu);
} }

View File

@ -23,7 +23,6 @@ import androidx.annotation.NonNull;
import androidx.appcompat.app.ActionBar; import androidx.appcompat.app.ActionBar;
import androidx.appcompat.widget.SearchView; import androidx.appcompat.widget.SearchView;
import androidx.appcompat.widget.Toolbar; import androidx.appcompat.widget.Toolbar;
import androidx.core.view.MenuItemCompat;
import androidx.recyclerview.widget.DividerItemDecoration; import androidx.recyclerview.widget.DividerItemDecoration;
import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.RecyclerView;
@ -153,8 +152,7 @@ public class DownloadActivity extends BaseActivity implements RepoLoader.RepoLis
getMenuInflater().inflate(R.menu.menu_download, menu); getMenuInflater().inflate(R.menu.menu_download, menu);
// Setup search button // Setup search button
final MenuItem searchItem = menu.findItem(R.id.menu_search); mSearchView = (SearchView) menu.findItem(R.id.menu_search).getActionView();
mSearchView = (SearchView) MenuItemCompat.getActionView(searchItem);
mSearchView.setIconifiedByDefault(true); mSearchView.setIconifiedByDefault(true);
mSearchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() { mSearchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
@Override @Override

View File

@ -1,5 +1,6 @@
package org.meowcat.edxposed.manager; package org.meowcat.edxposed.manager;
import android.annotation.SuppressLint;
import android.net.Uri; import android.net.Uri;
import android.os.Bundle; import android.os.Bundle;
import android.text.method.LinkMovementMethod; import android.text.method.LinkMovementMethod;
@ -20,6 +21,7 @@ import org.meowcat.edxposed.manager.util.chrome.LinkTransformationMethod;
public class DownloadDetailsFragment extends Fragment { public class DownloadDetailsFragment extends Fragment {
@SuppressLint("SetTextI18n")
@Override @Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
DownloadDetailsActivity mActivity = (DownloadDetailsActivity) getActivity(); DownloadDetailsActivity mActivity = (DownloadDetailsActivity) getActivity();

View File

@ -40,7 +40,7 @@ public class DownloadDetailsSettingsFragment extends PreferenceFragmentCompat {
if (prefs.getBoolean("no_global", true)) { if (prefs.getBoolean("no_global", true)) {
for (Map.Entry<String, ?> k : prefs.getAll().entrySet()) { for (Map.Entry<String, ?> k : prefs.getAll().entrySet()) {
if (prefs.getString(k.getKey(), "").equals("global")) { if (("global").equals(prefs.getString(k.getKey(), ""))) {
editor.putString(k.getKey(), "").apply(); editor.putString(k.getKey(), "").apply();
} }
} }

View File

@ -1,12 +1,16 @@
package org.meowcat.edxposed.manager; package org.meowcat.edxposed.manager;
import android.annotation.SuppressLint; import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.Context; import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageInfo; import android.content.pm.PackageInfo;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
import android.content.res.Resources; import android.content.res.Resources;
import android.net.Uri;
import android.os.Bundle; import android.os.Bundle;
import android.text.method.LinkMovementMethod; import android.text.method.LinkMovementMethod;
import android.util.Log;
import android.util.TypedValue; import android.util.TypedValue;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
@ -35,11 +39,11 @@ import org.meowcat.edxposed.manager.util.chrome.LinkTransformationMethod;
import org.meowcat.edxposed.manager.widget.DownloadView; import org.meowcat.edxposed.manager.widget.DownloadView;
import java.io.File; import java.io.File;
import java.io.FileInputStream;
import java.io.OutputStream;
import java.text.DateFormat; import java.text.DateFormat;
import java.util.Date; import java.util.Date;
import static org.meowcat.edxposed.manager.XposedApp.WRITE_EXTERNAL_PERMISSION;
public class DownloadDetailsVersionsFragment extends ListFragment { public class DownloadDetailsVersionsFragment extends ListFragment {
private static View rootView; private static View rootView;
private DownloadDetailsActivity mActivity; private DownloadDetailsActivity mActivity;
@ -93,13 +97,31 @@ public class DownloadDetailsVersionsFragment extends ListFragment {
} }
@Override @Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults); super.onActivityResult(requestCode, resultCode, data);
if (requestCode == WRITE_EXTERNAL_PERMISSION) { if (resultCode != Activity.RESULT_OK) {
if (grantResults.length == 1 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { return;
DownloadView.mClickedButton.performClick(); }
} else { if (requestCode == 42) {
Snackbar.make(rootView, R.string.permissionNotGranted, Snackbar.LENGTH_LONG).show(); if (data != null) {
Uri uri = data.getData();
if (uri != null) {
try {
OutputStream os = mActivity.getContentResolver().openOutputStream(uri);
if (os != null) {
FileInputStream in = new FileInputStream(new File(DownloadView.mInfo.localFilename));
byte[] buffer = new byte[1024];
int len;
while ((len = in.read(buffer)) > 0) {
os.write(buffer, 0, len);
}
os.close();
}
} catch (Exception e) {
e.printStackTrace();
//Snackbar.make(findViewById(R.id.snackbar), getResources().getString(R.string.logs_save_failed) + "\n" + e.getMessage(), Snackbar.LENGTH_LONG).show();
}
}
} }
} }
} }

View File

@ -83,10 +83,6 @@ public class EdDownloadActivity extends BaseActivity {
@SuppressLint("StaticFieldLeak") @SuppressLint("StaticFieldLeak")
private class JSONParser extends AsyncTask<Void, Void, String> { private class JSONParser extends AsyncTask<Void, Void, String> {
private String newApkVersion = null;
private String newApkLink = null;
private String newApkChangelog = null;
@Override @Override
protected String doInBackground(Void... params) { protected String doInBackground(Void... params) {
try { try {
@ -115,9 +111,9 @@ public class EdDownloadActivity extends BaseActivity {
tabsAdapter.notifyDataSetChanged(); tabsAdapter.notifyDataSetChanged();
} }
newApkVersion = xposedJson.apk.version; String newApkVersion = xposedJson.apk.version;
newApkLink = xposedJson.apk.link; String newApkLink = xposedJson.apk.link;
newApkChangelog = xposedJson.apk.changelog; String newApkChangelog = xposedJson.apk.changelog;
if (newApkVersion == null) { if (newApkVersion == null) {
return; return;

View File

@ -1,15 +1,11 @@
package org.meowcat.edxposed.manager; package org.meowcat.edxposed.manager;
import android.Manifest;
import android.annotation.SuppressLint; import android.annotation.SuppressLint;
import android.app.ProgressDialog; import android.app.ProgressDialog;
import android.content.Intent; import android.content.Intent;
import android.content.pm.PackageManager;
import android.net.Uri; import android.net.Uri;
import android.os.AsyncTask; import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.os.Environment;
import android.os.Handler; import android.os.Handler;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.Menu; import android.view.Menu;
@ -23,7 +19,6 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.appcompat.app.ActionBar; import androidx.appcompat.app.ActionBar;
import androidx.appcompat.widget.Toolbar; import androidx.appcompat.widget.Toolbar;
import androidx.core.app.ActivityCompat;
import androidx.core.content.FileProvider; import androidx.core.content.FileProvider;
import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.RecyclerView;
@ -36,6 +31,7 @@ import java.io.File;
import java.io.FileInputStream; import java.io.FileInputStream;
import java.io.FileOutputStream; import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Calendar; import java.util.Calendar;
@ -48,7 +44,6 @@ public class LogsActivity extends BaseActivity {
XposedApp.BASE_DIR + "log/error.log.old"); XposedApp.BASE_DIR + "log/error.log.old");
private File mFileAllLog = new File(XposedApp.BASE_DIR + "log/all.log"); private File mFileAllLog = new File(XposedApp.BASE_DIR + "log/all.log");
private File mFileAllLogOld = new File(XposedApp.BASE_DIR + "log/all.log.old"); private File mFileAllLogOld = new File(XposedApp.BASE_DIR + "log/all.log.old");
private MenuItem mClickedMenuItem = null;
private LogsAdapter mAdapter; private LogsAdapter mAdapter;
private RecyclerView mListView; private RecyclerView mListView;
private Handler handler = new Handler(); private Handler handler = new Handler();
@ -124,7 +119,6 @@ public class LogsActivity extends BaseActivity {
@Override @Override
public boolean onOptionsItemSelected(@NonNull MenuItem item) { public boolean onOptionsItemSelected(@NonNull MenuItem item) {
mClickedMenuItem = item;
switch (item.getItemId()) { switch (item.getItemId()) {
case R.id.menu_scroll_top: case R.id.menu_scroll_top:
scrollTop(); scrollTop();
@ -138,7 +132,8 @@ public class LogsActivity extends BaseActivity {
case R.id.menu_send: case R.id.menu_send:
try { try {
send(); send();
} catch (NullPointerException ignored) { } catch (Exception e) {
Snackbar.make(findViewById(R.id.snackbar), e.getLocalizedMessage(), Snackbar.LENGTH_LONG).show();
} }
return true; return true;
case R.id.menu_save: case R.id.menu_save:
@ -186,34 +181,8 @@ public class LogsActivity extends BaseActivity {
startActivity(Intent.createChooser(sendIntent, getResources().getString(R.string.menuSend))); startActivity(Intent.createChooser(sendIntent, getResources().getString(R.string.menuSend)));
} }
@Override
public void onRequestPermissionsResult(int requestCode,
@NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions,
grantResults);
if (requestCode == XposedApp.WRITE_EXTERNAL_PERMISSION) {
if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
if (mClickedMenuItem != null) {
new Handler().postDelayed(() -> onOptionsItemSelected(mClickedMenuItem), 500);
}
} else {
Snackbar.make(findViewById(R.id.snackbar), R.string.permissionNotGranted, Snackbar.LENGTH_LONG).show();
}
}
}
@SuppressLint("DefaultLocale") @SuppressLint("DefaultLocale")
private void save() { private void save() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && ActivityCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
requestPermissions(new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, XposedApp.WRITE_EXTERNAL_PERMISSION);
return;
}
if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
Snackbar.make(findViewById(R.id.snackbar), R.string.sdcard_not_writable, Snackbar.LENGTH_LONG).show();
return;
}
Calendar now = Calendar.getInstance(); Calendar now = Calendar.getInstance();
String filename = String.format( String filename = String.format(
"EdXposed_Verbose_%04d%02d%02d_%02d%02d%02d.log", "EdXposed_Verbose_%04d%02d%02d_%02d%02d%02d.log",
@ -221,24 +190,41 @@ public class LogsActivity extends BaseActivity {
now.get(Calendar.DAY_OF_MONTH), now.get(Calendar.HOUR_OF_DAY), now.get(Calendar.DAY_OF_MONTH), now.get(Calendar.HOUR_OF_DAY),
now.get(Calendar.MINUTE), now.get(Calendar.SECOND)); now.get(Calendar.MINUTE), now.get(Calendar.SECOND));
File targetFile = new File(XposedApp.createFolder(), filename); Intent exportIntent = new Intent(Intent.ACTION_CREATE_DOCUMENT);
exportIntent.addCategory(Intent.CATEGORY_OPENABLE);
exportIntent.setType("text/*");
exportIntent.putExtra(Intent.EXTRA_TITLE, filename);
startActivityForResult(exportIntent, 42);
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (resultCode != RESULT_OK) {
return;
}
if (requestCode == 42) {
if (data != null) {
Uri uri = data.getData();
if (uri != null) {
try { try {
OutputStream os = getContentResolver().openOutputStream(uri);
if (os != null) {
FileInputStream in = new FileInputStream(allLog ? mFileAllLog : mFileErrorLog); FileInputStream in = new FileInputStream(allLog ? mFileAllLog : mFileErrorLog);
FileOutputStream out = new FileOutputStream(targetFile);
byte[] buffer = new byte[1024]; byte[] buffer = new byte[1024];
int len; int len;
while ((len = in.read(buffer)) > 0) { while ((len = in.read(buffer)) > 0) {
out.write(buffer, 0, len); os.write(buffer, 0, len);
} }
in.close(); os.close();
out.close(); }
} catch (Exception e) {
Snackbar.make(findViewById(R.id.snackbar), targetFile.toString(), Snackbar.LENGTH_LONG).show();
} catch (IOException e) {
Snackbar.make(findViewById(R.id.snackbar), getResources().getString(R.string.logs_save_failed) + "\n" + e.getMessage(), Snackbar.LENGTH_LONG).show(); Snackbar.make(findViewById(R.id.snackbar), getResources().getString(R.string.logs_save_failed) + "\n" + e.getMessage(), Snackbar.LENGTH_LONG).show();
} }
} }
}
}
}
@SuppressLint("StaticFieldLeak") @SuppressLint("StaticFieldLeak")
private class LogsReader extends AsyncTask<File, Integer, ArrayList<String>> { private class LogsReader extends AsyncTask<File, Integer, ArrayList<String>> {

View File

@ -9,8 +9,6 @@ import android.widget.ImageView;
import android.widget.TextView; import android.widget.TextView;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.appcompat.view.menu.MenuBuilder;
import androidx.appcompat.view.menu.MenuPopupHelper;
import androidx.appcompat.widget.PopupMenu; import androidx.appcompat.widget.PopupMenu;
import com.google.android.material.card.MaterialCardView; import com.google.android.material.card.MaterialCardView;
@ -70,9 +68,7 @@ public class MainActivity extends BaseActivity implements RepoLoader.RepoListene
PopupMenu appMenu = new PopupMenu(MainActivity.this, menu); PopupMenu appMenu = new PopupMenu(MainActivity.this, menu);
appMenu.inflate(R.menu.menu_installer); appMenu.inflate(R.menu.menu_installer);
appMenu.setOnMenuItemClickListener(this::onOptionsItemSelected); appMenu.setOnMenuItemClickListener(this::onOptionsItemSelected);
MenuPopupHelper menuHelper = new MenuPopupHelper(MainActivity.this, (MenuBuilder) appMenu.getMenu(), menu); appMenu.show();
menuHelper.setForceShowIcon(true);
menuHelper.show();
}); });
String installedXposedVersion; String installedXposedVersion;
try { try {

View File

@ -1,15 +1,11 @@
package org.meowcat.edxposed.manager; package org.meowcat.edxposed.manager;
import android.content.ActivityNotFoundException;
import android.content.Intent; import android.content.Intent;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo; import android.content.pm.ResolveInfo;
import android.net.Uri; import android.net.Uri;
import android.os.Bundle; import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.text.TextUtils; import android.text.TextUtils;
import android.util.Log;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.Menu; import android.view.Menu;
import android.view.MenuItem; import android.view.MenuItem;
@ -24,7 +20,6 @@ import androidx.annotation.NonNull;
import androidx.appcompat.app.ActionBar; import androidx.appcompat.app.ActionBar;
import androidx.appcompat.widget.SearchView; import androidx.appcompat.widget.SearchView;
import androidx.appcompat.widget.Toolbar; import androidx.appcompat.widget.Toolbar;
import androidx.core.view.MenuItemCompat;
import androidx.recyclerview.widget.DividerItemDecoration; import androidx.recyclerview.widget.DividerItemDecoration;
import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.RecyclerView;
@ -43,15 +38,11 @@ import org.meowcat.edxposed.manager.util.NavUtil;
import org.meowcat.edxposed.manager.util.RepoLoader; import org.meowcat.edxposed.manager.util.RepoLoader;
import java.io.BufferedReader; import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File; import java.io.File;
import java.io.FileInputStream; import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.InputStreamReader; import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.PrintWriter; import java.io.PrintWriter;
import java.text.DateFormat; import java.text.DateFormat;
import java.text.SimpleDateFormat; import java.text.SimpleDateFormat;
@ -77,7 +68,6 @@ public class ModulesActivity extends BaseActivity implements ModuleUtil.ModuleLi
private DateFormat dateformat = new SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()); private DateFormat dateformat = new SimpleDateFormat("yyyy-MM-dd", Locale.getDefault());
private ModuleUtil mModuleUtil; private ModuleUtil mModuleUtil;
private ModuleAdapter mAdapter = null; private ModuleAdapter mAdapter = null;
private MenuItem mClickedMenuItem = null;
private RecyclerView mListView; private RecyclerView mListView;
private SwipeRefreshLayout mSwipeRefreshLayout; private SwipeRefreshLayout mSwipeRefreshLayout;
private Runnable reloadModules = new Runnable() { private Runnable reloadModules = new Runnable() {
@ -162,152 +152,140 @@ public class ModulesActivity extends BaseActivity implements ModuleUtil.ModuleLi
@Override @Override
public boolean onCreateOptionsMenu(Menu menu) { public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.menu_modules, menu); getMenuInflater().inflate(R.menu.menu_modules, menu);
mSearchView = (SearchView) MenuItemCompat.getActionView(menu.findItem(R.id.menu_search)); mSearchView = (SearchView) menu.findItem(R.id.menu_search).getActionView();
mSearchView.setOnQueryTextListener(mSearchListener); mSearchView.setOnQueryTextListener(mSearchListener);
return super.onCreateOptionsMenu(menu); return super.onCreateOptionsMenu(menu);
} }
@Override @Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onRequestPermissionsResult(requestCode, permissions, super.onActivityResult(requestCode, resultCode, data);
grantResults); if (resultCode != RESULT_OK) {
if (requestCode == XposedApp.WRITE_EXTERNAL_PERMISSION) { return;
if (grantResults[0] == PackageManager.PERMISSION_GRANTED) { }
if (mClickedMenuItem != null) { if (requestCode == 42) {
new Handler().postDelayed(() -> onOptionsItemSelected(mClickedMenuItem), 500); File listModules = new File(XposedApp.ENABLED_MODULES_LIST_FILE);
if (data != null) {
Uri uri = data.getData();
if (uri != null) {
try {
OutputStream os = getContentResolver().openOutputStream(uri);
if (os != null) {
FileInputStream in = new FileInputStream(listModules);
byte[] buffer = new byte[1024];
int len;
while ((len = in.read(buffer)) > 0) {
os.write(buffer, 0, len);
}
os.close();
}
} catch (Exception e) {
Snackbar.make(findViewById(R.id.snackbar), getResources().getString(R.string.logs_save_failed) + "\n" + e.getMessage(), Snackbar.LENGTH_LONG).show();
}
}
}
} else if (requestCode == 43) {
if (data != null) {
Uri uri = data.getData();
if (uri != null) {
try {
OutputStream os = getContentResolver().openOutputStream(uri);
if (os != null) {
PrintWriter fileOut = new PrintWriter(os);
Set<String> keys = ModuleUtil.getInstance().getModules().keySet();
for (String key1 : keys) {
fileOut.println(key1);
}
fileOut.close();
os.close();
}
} catch (Exception e) {
Snackbar.make(findViewById(R.id.snackbar), getResources().getString(R.string.logs_save_failed) + "\n" + e.getMessage(), Snackbar.LENGTH_LONG).show();
}
}
}
} else if (requestCode == 44) {
if (data != null) {
Uri uri = data.getData();
if (uri != null) {
try {
importModules(uri);
} catch (Exception e) {
e.printStackTrace();
}
} }
} else {
Snackbar.make(findViewById(R.id.snackbar), R.string.permissionNotGranted, Snackbar.LENGTH_LONG).show();
} }
} }
} }
@Override @Override
public boolean onOptionsItemSelected(@NonNull MenuItem item) { public boolean onOptionsItemSelected(@NonNull MenuItem item) {
File enabledModulesPath = new File(XposedApp.createFolder(), "enabled_modules.list"); Intent intent;
File installedModulesPath = new File(XposedApp.createFolder(), "installed_modules.list");
File listModules = new File(XposedApp.ENABLED_MODULES_LIST_FILE);
mClickedMenuItem = item;
if (checkPermissions())
return false;
switch (item.getItemId()) { switch (item.getItemId()) {
case R.id.export_enabled_modules: case R.id.export_enabled_modules:
if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
return false;
}
if (ModuleUtil.getInstance().getEnabledModules().isEmpty()) { if (ModuleUtil.getInstance().getEnabledModules().isEmpty()) {
Snackbar.make(findViewById(R.id.snackbar), R.string.no_enabled_modules, Snackbar.LENGTH_SHORT).show(); Snackbar.make(findViewById(R.id.snackbar), R.string.no_enabled_modules, Snackbar.LENGTH_SHORT).show();
return false; return false;
} }
intent = new Intent(Intent.ACTION_CREATE_DOCUMENT);
try { intent.addCategory(Intent.CATEGORY_OPENABLE);
XposedApp.createFolder(); intent.setType("text/*");
intent.putExtra(Intent.EXTRA_TITLE, "enabled_modules.list");
FileInputStream in = new FileInputStream(listModules); startActivityForResult(intent, 42);
FileOutputStream out = new FileOutputStream(enabledModulesPath);
byte[] buffer = new byte[1024];
int len;
while ((len = in.read(buffer)) > 0) {
out.write(buffer, 0, len);
}
in.close();
out.close();
} catch (IOException e) {
Snackbar.make(findViewById(R.id.snackbar), getResources().getString(R.string.logs_save_failed) + "\n" + e.getMessage(), Snackbar.LENGTH_LONG).show();
return false;
}
Snackbar.make(findViewById(R.id.snackbar), enabledModulesPath.toString(), Snackbar.LENGTH_LONG).show();
return true; return true;
case R.id.export_installed_modules: case R.id.export_installed_modules:
if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
Snackbar.make(findViewById(R.id.snackbar), R.string.sdcard_not_writable, Snackbar.LENGTH_LONG).show();
return false;
}
Map<String, ModuleUtil.InstalledModule> installedModules = ModuleUtil.getInstance().getModules(); Map<String, ModuleUtil.InstalledModule> installedModules = ModuleUtil.getInstance().getModules();
if (installedModules.isEmpty()) { if (installedModules.isEmpty()) {
Snackbar.make(findViewById(R.id.snackbar), R.string.no_installed_modules, Snackbar.LENGTH_SHORT).show(); Snackbar.make(findViewById(R.id.snackbar), R.string.no_installed_modules, Snackbar.LENGTH_SHORT).show();
return false; return false;
} }
intent = new Intent(Intent.ACTION_CREATE_DOCUMENT);
try { intent.addCategory(Intent.CATEGORY_OPENABLE);
XposedApp.createFolder(); intent.setType("text/*");
intent.putExtra(Intent.EXTRA_TITLE, "installed_modules.list");
FileWriter fw = new FileWriter(installedModulesPath); startActivityForResult(intent, 43);
BufferedWriter bw = new BufferedWriter(fw);
PrintWriter fileOut = new PrintWriter(bw);
Set<String> keys = installedModules.keySet();
for (String key1 : keys) {
fileOut.println(key1);
}
fileOut.close();
} catch (IOException e) {
Snackbar.make(findViewById(R.id.snackbar), getResources().getString(R.string.logs_save_failed) + "\n" + e.getMessage(), Snackbar.LENGTH_LONG).show();
return false;
}
Snackbar.make(findViewById(R.id.snackbar), installedModulesPath.toString(), Snackbar.LENGTH_LONG).show();
return true; return true;
case R.id.import_installed_modules: case R.id.import_installed_modules:
return importModules(installedModulesPath);
case R.id.import_enabled_modules: case R.id.import_enabled_modules:
return importModules(enabledModulesPath); intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
intent.addCategory(Intent.CATEGORY_OPENABLE);
intent.setType("*/*");
startActivityForResult(intent, 44);
return true;
} }
return super.onOptionsItemSelected(item); return super.onOptionsItemSelected(item);
} }
private boolean importModules(File path) { private void importModules(Uri uri) {
if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
Snackbar.make(findViewById(R.id.snackbar), R.string.sdcard_not_writable, Snackbar.LENGTH_LONG).show();
return false;
}
InputStream ips = null;
RepoLoader repoLoader = RepoLoader.getInstance(); RepoLoader repoLoader = RepoLoader.getInstance();
List<Module> list = new ArrayList<>(); List<Module> list = new ArrayList<>();
if (!path.exists()) {
Snackbar.make(findViewById(R.id.snackbar), R.string.no_backup_found, Snackbar.LENGTH_LONG).show();
return false;
}
try {
ips = new FileInputStream(path);
} catch (FileNotFoundException e) {
Log.e(XposedApp.TAG, "ModulesFragment -> " + e.getMessage());
}
if (path.length() == 0) {
Snackbar.make(findViewById(R.id.snackbar), R.string.file_is_empty, Snackbar.LENGTH_LONG).show();
return false;
}
try { try {
assert ips != null; InputStream inputStream = getContentResolver().openInputStream(uri);
InputStreamReader ipsr = new InputStreamReader(ips); InputStreamReader isr = new InputStreamReader(inputStream);
BufferedReader br = new BufferedReader(ipsr); BufferedReader br = new BufferedReader(isr);
String line; String line;
while ((line = br.readLine()) != null) { while ((line = br.readLine()) != null) {
Module m = repoLoader.getModule(line); Module m = repoLoader.getModule(line);
if (m == null) { if (m == null) {
Snackbar.make(findViewById(R.id.snackbar), getString(R.string.download_details_not_found, Snackbar.make(findViewById(R.id.snackbar), getString(R.string.download_details_not_found, line), Snackbar.LENGTH_SHORT).show();
line), Snackbar.LENGTH_SHORT).show();
} else { } else {
list.add(m); list.add(m);
} }
} }
br.close(); br.close();
} catch (ActivityNotFoundException | IOException e) { } catch (Exception e) {
Snackbar.make(findViewById(R.id.snackbar), e.toString(), Snackbar.LENGTH_SHORT).show(); e.printStackTrace();
Snackbar.make(findViewById(R.id.snackbar), e.getLocalizedMessage(), Snackbar.LENGTH_SHORT).show();
} }
for (final Module m : list) { for (final Module m : list) {
if (mModuleUtil.getModule(m.packageName) != null) {
continue;
}
ModuleVersion mv = null; ModuleVersion mv = null;
for (int i = 0; i < m.versions.size(); i++) { for (int i = 0; i < m.versions.size(); i++) {
ModuleVersion mvTemp = m.versions.get(i); ModuleVersion mvTemp = m.versions.get(i);
@ -319,13 +297,11 @@ public class ModulesActivity extends BaseActivity implements ModuleUtil.ModuleLi
} }
if (mv != null) { if (mv != null) {
DownloadsUtil.addModule(this, m.name, mv.downloadLink, false, (context, info) -> new InstallApkUtil(this, info).execute()); DownloadsUtil.addModule(this, m.name, mv.downloadLink, (context, info) -> new InstallApkUtil(this, info).execute());
} }
} }
ModuleUtil.getInstance().reloadInstalledModules(); ModuleUtil.getInstance().reloadInstalledModules();
return true;
} }
@Override @Override

View File

@ -3,13 +3,9 @@ package org.meowcat.edxposed.manager;
import android.annotation.SuppressLint; import android.annotation.SuppressLint;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.net.Uri; import android.net.Uri;
import android.os.Build; import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.os.FileUtils;
import android.text.Html; import android.text.Html;
import android.util.Log; import android.util.Log;
import android.view.LayoutInflater; import android.view.LayoutInflater;
@ -28,13 +24,10 @@ import java.io.File;
import java.io.FileReader; import java.io.FileReader;
import java.io.IOException; import java.io.IOException;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.util.List;
import java.util.Objects;
@SuppressLint("StaticFieldLeak") @SuppressLint("StaticFieldLeak")
public class StatusInstallerFragment extends Fragment { public class StatusInstallerFragment extends Fragment {
public static final File DISABLE_FILE = new File(XposedApp.BASE_DIR + "conf/disabled");
private static AppCompatActivity sActivity; private static AppCompatActivity sActivity;
private static String mUpdateLink; private static String mUpdateLink;
private static View mUpdateView; private static View mUpdateView;
@ -98,20 +91,6 @@ public class StatusInstallerFragment extends Fragment {
} }
} }
@SuppressWarnings("SameParameterValue")
@SuppressLint({"WorldReadableFiles", "WorldWriteableFiles"})
private static void setFilePermissionsFromMode(String name, int mode) {
int perms = FileUtils.S_IRUSR | FileUtils.S_IWUSR
| FileUtils.S_IRGRP | FileUtils.S_IWGRP;
if ((mode & Context.MODE_WORLD_READABLE) != 0) {
perms |= FileUtils.S_IROTH;
}
if ((mode & Context.MODE_WORLD_WRITEABLE) != 0) {
perms |= FileUtils.S_IWOTH;
}
FileUtils.setPermissions(name, perms, -1, -1);
}
@Override @Override
public void onCreate(@Nullable Bundle savedInstanceState) { public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
@ -119,7 +98,6 @@ public class StatusInstallerFragment extends Fragment {
} }
@SuppressLint("WorldReadableFiles") @SuppressLint("WorldReadableFiles")
@SuppressWarnings("ResultOfMethodCallIgnored")
@Override @Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.status_installer, container, false); View v = inflater.inflate(R.layout.status_installer, container, false);
@ -156,8 +134,6 @@ public class StatusInstallerFragment extends Fragment {
cpu.setText(getCompleteArch()); cpu.setText(getCompleteArch());
determineVerifiedBootState(v); determineVerifiedBootState(v);
refreshKnownIssue();
return v; return v;
} }
@ -191,6 +167,7 @@ public class StatusInstallerFragment extends Fragment {
} }
} }
/*
@SuppressWarnings("SameParameterValue") @SuppressWarnings("SameParameterValue")
private boolean checkAppInstalled(Context context, String pkgName) { private boolean checkAppInstalled(Context context, String pkgName) {
if (pkgName == null || pkgName.isEmpty()) { if (pkgName == null || pkgName.isEmpty()) {
@ -222,12 +199,12 @@ public class StatusInstallerFragment extends Fragment {
if (new File("/system/framework/core.jar.jex").exists()) { if (new File("/system/framework/core.jar.jex").exists()) {
issueName = "Aliyun OS"; issueName = "Aliyun OS";
issueLink = "https://forum.xda-developers.com/showpost.php?p=52289793&postcount=5"; issueLink = "https://forum.xda-developers.com/showpost.php?p=52289793&postcount=5";
// } else if (Build.VERSION.SDK_INT < 24 && (new File("/data/miui/DexspyInstaller.jar").exists() || checkClassExists("miui.dexspy.DexspyInstaller"))) { // } else if (Build.VERSION.SDK_INT < 24 && (new File("/data/miui/DexspyInstaller.jar").exists() || checkClassExists("miui.dexspy.DexspyInstaller"))) {
// issueName = "MIUI/Dexspy"; // issueName = "MIUI/Dexspy";
// issueLink = "https://forum.xda-developers.com/showpost.php?p=52291098&postcount=6"; // issueLink = "https://forum.xda-developers.com/showpost.php?p=52291098&postcount=6";
// } else if (Build.VERSION.SDK_INT < 24 && new File("/system/framework/twframework.jar").exists()) { // } else if (Build.VERSION.SDK_INT < 24 && new File("/system/framework/twframework.jar").exists()) {
// issueName = "Samsung TouchWiz ROM"; // issueName = "Samsung TouchWiz ROM";
// issueLink = "https://forum.xda-developers.com/showthread.php?t=3034811"; // issueLink = "https://forum.xda-developers.com/showthread.php?t=3034811";
} else if (!baseDirCanonical.equals(baseDirActualCanonical)) { } else if (!baseDirCanonical.equals(baseDirActualCanonical)) {
Log.e(XposedApp.TAG, "Base directory: " + getPathWithCanonicalPath(baseDir, baseDirCanonical)); Log.e(XposedApp.TAG, "Base directory: " + getPathWithCanonicalPath(baseDir, baseDirCanonical));
Log.e(XposedApp.TAG, "Expected: " + getPathWithCanonicalPath(baseDirActual, baseDirActualCanonical)); Log.e(XposedApp.TAG, "Expected: " + getPathWithCanonicalPath(baseDirActual, baseDirActualCanonical));
@ -241,7 +218,7 @@ public class StatusInstallerFragment extends Fragment {
} }
} }
*/
private String getAndroidVersion() { private String getAndroidVersion() {
switch (Build.VERSION.SDK_INT) { switch (Build.VERSION.SDK_INT) {
// case 16: // case 16:
@ -296,6 +273,7 @@ public class StatusInstallerFragment extends Fragment {
return manufacturer; return manufacturer;
} }
/*
private File getCanonicalFile(File file) { private File getCanonicalFile(File file) {
try { try {
return file.getCanonicalFile(); return file.getCanonicalFile();
@ -312,7 +290,7 @@ public class StatusInstallerFragment extends Fragment {
return file.getAbsolutePath() + " \u2192 " + canonical.getAbsolutePath(); return file.getAbsolutePath() + " \u2192 " + canonical.getAbsolutePath();
} }
} }
*/
private int extractIntPart(String str) { private int extractIntPart(String str) {
int result = 0, length = str.length(); int result = 0, length = str.length();
for (int offset = 0; offset < length; offset++) { for (int offset = 0; offset < length; offset++) {

View File

@ -105,6 +105,7 @@ public class XposedApp extends de.robv.android.xposed.installer.XposedApp implem
public void onCreate() { public void onCreate() {
super.onCreate(); super.onCreate();
if (!BuildConfig.DEBUG) {
try { try {
Thread.setDefaultUncaughtExceptionHandler((thread, throwable) -> { Thread.setDefaultUncaughtExceptionHandler((thread, throwable) -> {
@ -129,7 +130,10 @@ public class XposedApp extends de.robv.android.xposed.installer.XposedApp implem
System.exit(10); System.exit(10);
}); });
} catch (Throwable t) { } catch (Throwable t) {
t.printStackTrace();
} }
}
mInstance = this; mInstance = this;
mUiThread = Thread.currentThread(); mUiThread = Thread.currentThread();
mMainHandler = new Handler(); mMainHandler = new Handler();

View File

@ -4,26 +4,17 @@ import android.annotation.SuppressLint;
import android.app.DownloadManager; import android.app.DownloadManager;
import android.app.DownloadManager.Query; import android.app.DownloadManager.Query;
import android.app.DownloadManager.Request; import android.app.DownloadManager.Request;
import android.app.ProgressDialog;
import android.content.Context; import android.content.Context;
import android.content.DialogInterface;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.database.Cursor; import android.database.Cursor;
import android.net.Uri; import android.net.Uri;
import android.os.Environment;
import android.provider.MediaStore; import android.provider.MediaStore;
import android.util.Log;
import android.widget.Toast; import android.widget.Toast;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.core.content.ContextCompat;
import androidx.core.os.EnvironmentCompat;
import org.meowcat.edxposed.manager.R; import org.meowcat.edxposed.manager.R;
import org.meowcat.edxposed.manager.XposedApp; import org.meowcat.edxposed.manager.XposedApp;
import org.meowcat.edxposed.manager.repo.Module;
import org.meowcat.edxposed.manager.repo.ModuleVersion;
import org.meowcat.edxposed.manager.repo.ReleaseType;
import java.io.File; import java.io.File;
import java.io.FileOutputStream; import java.io.FileOutputStream;
@ -40,7 +31,7 @@ import java.util.Map;
import java.util.Objects; import java.util.Objects;
public class DownloadsUtil { public class DownloadsUtil {
static final String MIME_TYPE_APK = "application/vnd.android.package-archive"; public static final String MIME_TYPE_APK = "application/vnd.android.package-archive";
//private static final String MIME_TYPE_ZIP = "application/zip"; //private static final String MIME_TYPE_ZIP = "application/zip";
private static final Map<String, DownloadFinishedCallback> mCallbacks = new HashMap<>(); private static final Map<String, DownloadFinishedCallback> mCallbacks = new HashMap<>();
@SuppressLint("StaticFieldLeak") @SuppressLint("StaticFieldLeak")
@ -48,141 +39,44 @@ public class DownloadsUtil {
private static final SharedPreferences mPref = mApp private static final SharedPreferences mPref = mApp
.getSharedPreferences("download_cache", Context.MODE_PRIVATE); .getSharedPreferences("download_cache", Context.MODE_PRIVATE);
private static String DOWNLOAD_MODULES = "modules";
private static DownloadInfo add(Builder b) { private static DownloadInfo add(Builder b) {
Context context = b.mContext; Context context = b.mContext;
removeAllForUrl(context, b.mUrl); removeAllForUrl(context, b.mUrl);
if (!b.mDialog) {
synchronized (mCallbacks) { synchronized (mCallbacks) {
mCallbacks.put(b.mUrl, b.mCallback); mCallbacks.put(b.mUrl, b.mCallback);
} }
}
String savePath = "Download/EdXposedManager";
if (b.mModule) {
savePath += "/modules";
}
Request request = new Request(Uri.parse(b.mUrl)); Request request = new Request(Uri.parse(b.mUrl));
request.setTitle(b.mTitle); request.setTitle(b.mTitle);
request.setMimeType(b.mMimeType.toString()); request.setMimeType(b.mMimeType.toString());
if (b.mSave) { /*if (b.mSave) {
try { try {
request.setDestinationInExternalPublicDir(savePath, b.mTitle + b.mMimeType.getExtension()); request.setDestinationInExternalPublicDir(savePath, b.mTitle + b.mMimeType.getExtension());
} catch (IllegalStateException e) { } catch (IllegalStateException e) {
Toast.makeText(context, e.getMessage(), Toast.LENGTH_SHORT).show(); Toast.makeText(context, e.getMessage(), Toast.LENGTH_SHORT).show();
} }
} else if (b.mDestination != null) { } else */
//noinspection ResultOfMethodCallIgnored File destination = new File(context.getExternalCacheDir(), "/downloads/" + b.mTitle + b.mMimeType.getExtension());
b.mDestination.getParentFile().mkdirs(); request.setDestinationUri(Uri.fromFile(destination));
removeAllForLocalFile(context, b.mDestination);
request.setDestinationUri(Uri.fromFile(b.mDestination));
}
request.setNotificationVisibility(Request.VISIBILITY_VISIBLE); request.setNotificationVisibility(Request.VISIBILITY_VISIBLE);
DownloadManager dm = (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE); DownloadManager dm = (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE);
long id = dm.enqueue(request); long id = dm.enqueue(request);
if (b.mDialog) {
showDownloadDialog(b, id);
}
return getById(context, id); return getById(context, id);
} }
private static File[] getDownloadDirs(String subDir) { public static DownloadInfo addModule(Context context, String title, String url, DownloadFinishedCallback callback) {
Context context = XposedApp.getInstance();
ArrayList<File> dirs = new ArrayList<>(2);
for (File dir : ContextCompat.getExternalCacheDirs(context)) {
if (dir != null && EnvironmentCompat.getStorageState(dir).equals(Environment.MEDIA_MOUNTED)) {
dirs.add(new File(new File(dir, "downloads"), subDir));
}
}
dirs.add(new File(new File(context.getCacheDir(), "downloads"), subDir));
return dirs.toArray(new File[0]);
}
private static File getDownloadTarget(String subDir, String filename) {
return new File(getDownloadDirs(subDir)[0], filename);
}
private static File getDownloadTargetForUrl(String subDir, String url) {
return getDownloadTarget(subDir, Uri.parse(url).getLastPathSegment());
}
public static DownloadInfo addModule(Context context, String title, String url, boolean save, DownloadFinishedCallback callback) {
return new Builder(context) return new Builder(context)
.setTitle(title) .setTitle(title)
.setUrl(url) .setUrl(url)
.setDestinationFromUrl(DownloadsUtil.DOWNLOAD_MODULES)
.setCallback(callback) .setCallback(callback)
.setSave(save)
.setModule(true) .setModule(true)
.setMimeType(MIME_TYPES.APK) .setMimeType(MIME_TYPES.APK)
.download(); .download();
} }
private static void showDownloadDialog(final Builder b, final long id) { /*
final Context context = b.mContext;
final ProgressDialog dialog = new ProgressDialog(context);
dialog.setTitle(b.mTitle);
dialog.setMessage(context.getString(R.string.download_view_waiting));
dialog.setButton(DialogInterface.BUTTON_NEGATIVE, context.getString(R.string.download_view_cancel), (dialog1, which) -> dialog1.cancel());
dialog.setOnCancelListener(dialog12 -> removeById(context, id));
dialog.setProgress(0);
dialog.setCanceledOnTouchOutside(false);
dialog.setProgressNumberFormat(context.getString(R.string.download_progress));
dialog.show();
new Thread("DownloadDialog") {
@Override
public void run() {
while (true) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
return;
}
final DownloadInfo info = getById(context, id);
if (info == null) {
dialog.cancel();
return;
} else if (info.status == DownloadManager.STATUS_FAILED) {
dialog.cancel();
XposedApp.runOnUiThread(() -> Toast.makeText(context,
context.getString(R.string.download_view_failed, info.reason),
Toast.LENGTH_LONG).show());
return;
} else if (info.status == DownloadManager.STATUS_SUCCESSFUL) {
dialog.dismiss();
// Hack to reset stat information.
//noinspection ResultOfMethodCallIgnored
new File(info.localFilename).setExecutable(false);
if (b.mCallback != null) {
b.mCallback.onDownloadFinished(context, info);
}
return;
}
XposedApp.runOnUiThread(() -> {
if (info.totalSize <= 0 || info.status != DownloadManager.STATUS_RUNNING) {
dialog.setMessage(context.getString(R.string.download_view_waiting));
} else {
dialog.setMessage(context.getString(R.string.download_running));
dialog.setProgress(info.bytesDownloaded / 1024);
dialog.setMax(info.totalSize / 1024);
}
});
}
}
}.start();
}
public static ModuleVersion getStableVersion(Module m) { public static ModuleVersion getStableVersion(Module m) {
for (int i = 0; i < m.versions.size(); i++) { for (int i = 0; i < m.versions.size(); i++) {
ModuleVersion mvTemp = m.versions.get(i); ModuleVersion mvTemp = m.versions.get(i);
@ -193,7 +87,7 @@ public class DownloadsUtil {
} }
return null; return null;
} }
*/
public static DownloadInfo getById(Context context, long id) { public static DownloadInfo getById(Context context, long id) {
DownloadManager dm = (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE); DownloadManager dm = (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE);
Cursor c = dm.query(new Query().setFilterById(id)); Cursor c = dm.query(new Query().setFilterById(id));
@ -317,7 +211,7 @@ public class DownloadsUtil {
dm.remove(ids); dm.remove(ids);
} }
/*
private static void removeAllForLocalFile(Context context, File file) { private static void removeAllForLocalFile(Context context, File file) {
//noinspection ResultOfMethodCallIgnored //noinspection ResultOfMethodCallIgnored
file.delete(); file.delete();
@ -367,7 +261,7 @@ public class DownloadsUtil {
ids[i] = idsList.get(i); ids[i] = idsList.get(i);
dm.remove(ids); dm.remove(ids);
} }*/
// public static void removeOutdated(Context context, long cutoff) { // public static void removeOutdated(Context context, long cutoff) {
// DownloadManager dm = (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE); // DownloadManager dm = (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE);
@ -563,9 +457,6 @@ public class DownloadsUtil {
private String mUrl = null; private String mUrl = null;
private DownloadFinishedCallback mCallback = null; private DownloadFinishedCallback mCallback = null;
private MIME_TYPES mMimeType = MIME_TYPES.APK; private MIME_TYPES mMimeType = MIME_TYPES.APK;
private File mDestination = null;
private boolean mDialog = false;
private boolean mSave = false;
public Builder(Context context) { public Builder(Context context) {
mContext = context; mContext = context;
@ -591,33 +482,11 @@ public class DownloadsUtil {
return this; return this;
} }
Builder setDestination(File file) {
mDestination = file;
return this;
}
Builder setDestinationFromUrl(String subDir) {
if (mUrl == null) {
throw new IllegalStateException("URL must be set first");
}
return setDestination(getDownloadTargetForUrl(subDir, mUrl));
}
public Builder setSave(boolean save) {
this.mSave = save;
return this;
}
public Builder setModule(boolean module) { public Builder setModule(boolean module) {
this.mModule = module; this.mModule = module;
return this; return this;
} }
public Builder setDialog(boolean dialog) {
mDialog = dialog;
return this;
}
public DownloadInfo download() { public DownloadInfo download() {
return add(this); return add(this);
} }

View File

@ -1,10 +1,8 @@
package org.meowcat.edxposed.manager.widget; package org.meowcat.edxposed.manager.widget;
import android.Manifest;
import android.annotation.SuppressLint;
import android.app.DownloadManager; import android.app.DownloadManager;
import android.content.Context; import android.content.Context;
import android.content.pm.PackageManager; import android.content.Intent;
import android.util.AttributeSet; import android.util.AttributeSet;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
@ -12,9 +10,7 @@ import android.widget.Button;
import android.widget.LinearLayout; import android.widget.LinearLayout;
import android.widget.ProgressBar; import android.widget.ProgressBar;
import android.widget.TextView; import android.widget.TextView;
import android.widget.Toast;
import androidx.core.app.ActivityCompat;
import androidx.fragment.app.Fragment; import androidx.fragment.app.Fragment;
import org.meowcat.edxposed.manager.R; import org.meowcat.edxposed.manager.R;
@ -23,20 +19,15 @@ import org.meowcat.edxposed.manager.util.DownloadsUtil.DownloadFinishedCallback;
import java.util.Objects; import java.util.Objects;
import static org.meowcat.edxposed.manager.XposedApp.WRITE_EXTERNAL_PERMISSION;
public class DownloadView extends LinearLayout { public class DownloadView extends LinearLayout {
@SuppressLint("StaticFieldLeak") public static DownloadsUtil.DownloadInfo mInfo = null;
public static Button mClickedButton;
private final Button btnDownload; private final Button btnDownload;
private final Button btnDownloadCancel; private final Button btnDownloadCancel;
private final Button btnInstall; private final Button btnInstall;
private final Button btnRemove;
private final Button btnSave; private final Button btnSave;
private final ProgressBar progressBar; private final ProgressBar progressBar;
private final TextView txtInfo; private final TextView txtInfo;
public Fragment fragment; public Fragment fragment;
private DownloadsUtil.DownloadInfo mInfo = null;
private String mUrl = null; private String mUrl = null;
private final Runnable refreshViewRunnable = new Runnable() { private final Runnable refreshViewRunnable = new Runnable() {
@Override @Override
@ -45,7 +36,6 @@ public class DownloadView extends LinearLayout {
btnDownload.setVisibility(View.GONE); btnDownload.setVisibility(View.GONE);
btnSave.setVisibility(View.GONE); btnSave.setVisibility(View.GONE);
btnDownloadCancel.setVisibility(View.GONE); btnDownloadCancel.setVisibility(View.GONE);
btnRemove.setVisibility(View.GONE);
btnInstall.setVisibility(View.GONE); btnInstall.setVisibility(View.GONE);
progressBar.setVisibility(View.GONE); progressBar.setVisibility(View.GONE);
txtInfo.setVisibility(View.VISIBLE); txtInfo.setVisibility(View.VISIBLE);
@ -54,7 +44,6 @@ public class DownloadView extends LinearLayout {
btnDownload.setVisibility(View.VISIBLE); btnDownload.setVisibility(View.VISIBLE);
btnSave.setVisibility(View.VISIBLE); btnSave.setVisibility(View.VISIBLE);
btnDownloadCancel.setVisibility(View.GONE); btnDownloadCancel.setVisibility(View.GONE);
btnRemove.setVisibility(View.GONE);
btnInstall.setVisibility(View.GONE); btnInstall.setVisibility(View.GONE);
progressBar.setVisibility(View.GONE); progressBar.setVisibility(View.GONE);
txtInfo.setVisibility(View.GONE); txtInfo.setVisibility(View.GONE);
@ -66,7 +55,6 @@ public class DownloadView extends LinearLayout {
btnDownload.setVisibility(View.GONE); btnDownload.setVisibility(View.GONE);
btnSave.setVisibility(View.GONE); btnSave.setVisibility(View.GONE);
btnDownloadCancel.setVisibility(View.VISIBLE); btnDownloadCancel.setVisibility(View.VISIBLE);
btnRemove.setVisibility(View.GONE);
btnInstall.setVisibility(View.GONE); btnInstall.setVisibility(View.GONE);
progressBar.setVisibility(View.VISIBLE); progressBar.setVisibility(View.VISIBLE);
txtInfo.setVisibility(View.VISIBLE); txtInfo.setVisibility(View.VISIBLE);
@ -88,7 +76,6 @@ public class DownloadView extends LinearLayout {
btnDownload.setVisibility(View.VISIBLE); btnDownload.setVisibility(View.VISIBLE);
btnSave.setVisibility(View.VISIBLE); btnSave.setVisibility(View.VISIBLE);
btnDownloadCancel.setVisibility(View.GONE); btnDownloadCancel.setVisibility(View.GONE);
btnRemove.setVisibility(View.GONE);
btnInstall.setVisibility(View.GONE); btnInstall.setVisibility(View.GONE);
progressBar.setVisibility(View.GONE); progressBar.setVisibility(View.GONE);
txtInfo.setVisibility(View.VISIBLE); txtInfo.setVisibility(View.VISIBLE);
@ -98,9 +85,8 @@ public class DownloadView extends LinearLayout {
case DownloadManager.STATUS_SUCCESSFUL: case DownloadManager.STATUS_SUCCESSFUL:
btnDownload.setVisibility(View.GONE); btnDownload.setVisibility(View.GONE);
btnSave.setVisibility(View.GONE); btnSave.setVisibility(View.VISIBLE);
btnDownloadCancel.setVisibility(View.GONE); btnDownloadCancel.setVisibility(View.GONE);
btnRemove.setVisibility(View.VISIBLE);
btnInstall.setVisibility(View.VISIBLE); btnInstall.setVisibility(View.VISIBLE);
progressBar.setVisibility(View.GONE); progressBar.setVisibility(View.GONE);
txtInfo.setVisibility(View.VISIBLE); txtInfo.setVisibility(View.VISIBLE);
@ -123,27 +109,27 @@ public class DownloadView extends LinearLayout {
btnDownload = findViewById(R.id.btnDownload); btnDownload = findViewById(R.id.btnDownload);
btnDownloadCancel = findViewById(R.id.btnDownloadCancel); btnDownloadCancel = findViewById(R.id.btnDownloadCancel);
btnRemove = findViewById(R.id.btnRemove);
btnInstall = findViewById(R.id.btnInstall); btnInstall = findViewById(R.id.btnInstall);
btnSave = findViewById(R.id.save); btnSave = findViewById(R.id.save);
btnDownload.setOnClickListener(v -> { btnDownload.setOnClickListener(v -> {
mClickedButton = btnDownload; mInfo = DownloadsUtil.addModule(getContext(), mTitle, mUrl, mCallback);
mInfo = DownloadsUtil.addModule(getContext(), mTitle, mUrl, false, mCallback);
refreshViewFromUiThread(); refreshViewFromUiThread();
if (mInfo != null) if (mInfo != null)
new DownloadMonitor().start(); new DownloadMonitor().start();
}); });
btnSave.setOnClickListener(v -> { btnSave.setOnClickListener(v -> {
mClickedButton = btnSave; mInfo = DownloadsUtil.addModule(getContext(), mTitle, mUrl, (context1, info) -> {
Intent exportIntent = new Intent(Intent.ACTION_CREATE_DOCUMENT);
if (checkPermissions()) exportIntent.addCategory(Intent.CATEGORY_OPENABLE);
return; exportIntent.setType(DownloadsUtil.MIME_TYPE_APK);
exportIntent.putExtra(Intent.EXTRA_TITLE, mTitle + ".apk");
mInfo = DownloadsUtil.addModule(getContext(), mTitle, mUrl, true, (context1, info) -> Toast.makeText(context1, context1.getString(R.string.module_saved, info.localFilename), Toast.LENGTH_SHORT).show()); fragment.startActivityForResult(exportIntent, 42);
});
refreshViewFromUiThread();
if (mInfo != null)
new DownloadMonitor().start();
}); });
btnDownloadCancel.setOnClickListener(v -> { btnDownloadCancel.setOnClickListener(v -> {
@ -154,14 +140,6 @@ public class DownloadView extends LinearLayout {
// UI update will happen automatically by the DownloadMonitor // UI update will happen automatically by the DownloadMonitor
}); });
btnRemove.setOnClickListener(v -> {
if (mInfo == null)
return;
DownloadsUtil.removeById(getContext(), mInfo.id);
// UI update will happen automatically by the DownloadMonitor
});
btnInstall.setOnClickListener(v -> { btnInstall.setOnClickListener(v -> {
if (mCallback == null) if (mCallback == null)
return; return;
@ -175,15 +153,6 @@ public class DownloadView extends LinearLayout {
refreshViewFromUiThread(); refreshViewFromUiThread();
} }
private boolean checkPermissions() {
if (ActivityCompat.checkSelfPermission(this.getContext(),
Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
fragment.requestPermissions(new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, WRITE_EXTERNAL_PERMISSION);
return true;
}
return false;
}
private void refreshViewFromUiThread() { private void refreshViewFromUiThread() {
refreshViewRunnable.run(); refreshViewRunnable.run();
} }

View File

@ -42,16 +42,6 @@
android:visibility="gone" android:visibility="gone"
tools:ignore="ButtonOrder" /> tools:ignore="ButtonOrder" />
<com.google.android.material.button.MaterialButton
android:id="@+id/btnRemove"
style="@style/Widget.MaterialComponents.Button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="4dp"
android:text="@string/download_view_remove"
android:theme="@style/Widget.AppCompat.Button.Colored"
android:visibility="gone" />
<com.google.android.material.button.MaterialButton <com.google.android.material.button.MaterialButton
android:id="@+id/btnInstall" android:id="@+id/btnInstall"
style="@style/Widget.MaterialComponents.Button" style="@style/Widget.MaterialComponents.Button"

View File

@ -126,7 +126,6 @@
<!-- Logs tab --> <!-- Logs tab -->
<string name="logs_save_failed">无法将日志写入 SD 卡: </string> <string name="logs_save_failed">无法将日志写入 SD 卡: </string>
<string name="sdcard_not_writable">无法找到 SD 卡或不可写入</string>
<string name="menuClearLog">立即清理日志</string> <string name="menuClearLog">立即清理日志</string>
<string name="logs_cleared">日志清理成功</string> <string name="logs_cleared">日志清理成功</string>
<string name="logs_clear_failed">无法清理日志: </string> <string name="logs_clear_failed">无法清理日志: </string>
@ -156,7 +155,6 @@
<string name="download_could_not_read_file">无法读取下载的文件: %s</string> <string name="download_could_not_read_file">无法读取下载的文件: %s</string>
<string name="download_no_valid_apk">下载的文件并非有效的 APK (或存在兼容性问题)</string> <string name="download_no_valid_apk">下载的文件并非有效的 APK (或存在兼容性问题)</string>
<string name="download_incorrect_package_name">文件包名不正确(下载: %1$s, 预期: %2$s)</string> <string name="download_incorrect_package_name">文件包名不正确(下载: %1$s, 预期: %2$s)</string>
<string name="download_running">正在下载</string>
<!-- RepoLoader --> <!-- RepoLoader -->
<string name="repo_download_failed_http">下载 %1$s 失败: %2$d(%3$s)</string> <string name="repo_download_failed_http">下载 %1$s 失败: %2$d(%3$s)</string>
@ -173,7 +171,6 @@
<string name="no_installed_modules">没有任何已安装的模块</string> <string name="no_installed_modules">没有任何已安装的模块</string>
<string name="enable_heads_up">启用浮动通知</string> <string name="enable_heads_up">启用浮动通知</string>
<string name="enable_heads_up_summary">对新安装或已更新的模块启用浮动通知</string> <string name="enable_heads_up_summary">对新安装或已更新的模块启用浮动通知</string>
<string name="no_backup_found">找不到备份</string>
<string name="no_enabled_modules">没有任何已启用的模块</string> <string name="no_enabled_modules">没有任何已启用的模块</string>
<string name="share">分享</string> <string name="share">分享</string>
@ -195,7 +192,6 @@
<string name="changelog">更新日志</string> <string name="changelog">更新日志</string>
<string name="scroll_top">滚动到顶部</string> <string name="scroll_top">滚动到顶部</string>
<string name="file_is_empty">文件为空</string>
<string name="install_with_su">静默安装(root)</string> <string name="install_with_su">静默安装(root)</string>
<string name="install_with_su_summ">勾选后在 EdXposed Manager 内安装模块时将不再显示安装提示</string> <string name="install_with_su_summ">勾选后在 EdXposed Manager 内安装模块时将不再显示安装提示</string>
<string name="module_saved">模块已保存至 %1$s</string> <string name="module_saved">模块已保存至 %1$s</string>

View File

@ -126,7 +126,6 @@
<!-- Logs tab --> <!-- Logs tab -->
<string name="logs_save_failed">無法將日誌寫入 SD 卡: </string> <string name="logs_save_failed">無法將日誌寫入 SD 卡: </string>
<string name="sdcard_not_writable">無法找到 SD 卡或不可寫入</string>
<string name="menuClearLog">立即清理日誌</string> <string name="menuClearLog">立即清理日誌</string>
<string name="logs_cleared">日誌清理成功</string> <string name="logs_cleared">日誌清理成功</string>
<string name="logs_clear_failed">無法清理日誌: </string> <string name="logs_clear_failed">無法清理日誌: </string>
@ -156,7 +155,6 @@
<string name="download_could_not_read_file">無法讀取下載的文件: %s</string> <string name="download_could_not_read_file">無法讀取下載的文件: %s</string>
<string name="download_no_valid_apk">下載的文件並非有效的 APK (或存在兼容性問題)</string> <string name="download_no_valid_apk">下載的文件並非有效的 APK (或存在兼容性問題)</string>
<string name="download_incorrect_package_name">文件包名不正確(下載: %1$s, 預期: %2$s)</string> <string name="download_incorrect_package_name">文件包名不正確(下載: %1$s, 預期: %2$s)</string>
<string name="download_running">正在下載</string>
<!-- RepoLoader --> <!-- RepoLoader -->
<string name="repo_download_failed_http">下載 %1$s 失敗: %2$d(%3$s)</string> <string name="repo_download_failed_http">下載 %1$s 失敗: %2$d(%3$s)</string>
@ -173,7 +171,6 @@
<string name="no_installed_modules">沒有任何已安裝的模塊</string> <string name="no_installed_modules">沒有任何已安裝的模塊</string>
<string name="enable_heads_up">啟用浮動通知</string> <string name="enable_heads_up">啟用浮動通知</string>
<string name="enable_heads_up_summary">對新安裝或已更新的模塊啟用浮動通知</string> <string name="enable_heads_up_summary">對新安裝或已更新的模塊啟用浮動通知</string>
<string name="no_backup_found">找不到備份</string>
<string name="no_enabled_modules">沒有任何已啟用的模塊</string> <string name="no_enabled_modules">沒有任何已啟用的模塊</string>
<string name="share">分享</string> <string name="share">分享</string>
@ -195,7 +192,6 @@
<string name="changelog">更新日誌</string> <string name="changelog">更新日誌</string>
<string name="scroll_top">滾動到頂部</string> <string name="scroll_top">滾動到頂部</string>
<string name="file_is_empty">文件為空</string>
<string name="install_with_su">靜默安裝(root)</string> <string name="install_with_su">靜默安裝(root)</string>
<string name="install_with_su_summ">勾選後在 EdXposed Manager 內安裝模塊時將不再顯示安裝提示</string> <string name="install_with_su_summ">勾選後在 EdXposed Manager 內安裝模塊時將不再顯示安裝提示</string>
<string name="module_saved">模塊已保存至 %1$s</string> <string name="module_saved">模塊已保存至 %1$s</string>

View File

@ -126,7 +126,6 @@
<!-- Logs tab --> <!-- Logs tab -->
<string name="logs_save_failed">無法將日誌寫入 SD 卡: </string> <string name="logs_save_failed">無法將日誌寫入 SD 卡: </string>
<string name="sdcard_not_writable">無法找到 SD 卡或不可寫入</string>
<string name="menuClearLog">立即清理日誌</string> <string name="menuClearLog">立即清理日誌</string>
<string name="logs_cleared">日誌清理成功</string> <string name="logs_cleared">日誌清理成功</string>
<string name="logs_clear_failed">無法清理日誌: </string> <string name="logs_clear_failed">無法清理日誌: </string>
@ -156,7 +155,6 @@
<string name="download_could_not_read_file">無法讀取下載的檔案: %s</string> <string name="download_could_not_read_file">無法讀取下載的檔案: %s</string>
<string name="download_no_valid_apk">下載的檔案並非有效的 APK (或存在相容性問題)</string> <string name="download_no_valid_apk">下載的檔案並非有效的 APK (或存在相容性問題)</string>
<string name="download_incorrect_package_name">檔案包名不正確(下載: %1$s, 預期: %2$s)</string> <string name="download_incorrect_package_name">檔案包名不正確(下載: %1$s, 預期: %2$s)</string>
<string name="download_running">正在下載</string>
<!-- RepoLoader --> <!-- RepoLoader -->
<string name="repo_download_failed_http">下載 %1$s 失敗: %2$d(%3$s)</string> <string name="repo_download_failed_http">下載 %1$s 失敗: %2$d(%3$s)</string>
@ -173,7 +171,6 @@
<string name="no_installed_modules">沒有任何已安裝的模組</string> <string name="no_installed_modules">沒有任何已安裝的模組</string>
<string name="enable_heads_up">啟用浮動通知</string> <string name="enable_heads_up">啟用浮動通知</string>
<string name="enable_heads_up_summary">對新安裝或已更新的模組啟用浮動通知</string> <string name="enable_heads_up_summary">對新安裝或已更新的模組啟用浮動通知</string>
<string name="no_backup_found">找不到備份</string>
<string name="no_enabled_modules">沒有任何已啟用的模組</string> <string name="no_enabled_modules">沒有任何已啟用的模組</string>
<string name="share">分享</string> <string name="share">分享</string>
@ -195,7 +192,6 @@
<string name="changelog">更新日誌</string> <string name="changelog">更新日誌</string>
<string name="scroll_top">滾動到頂部</string> <string name="scroll_top">滾動到頂部</string>
<string name="file_is_empty">檔案為空</string>
<string name="install_with_su">靜默安裝(root)</string> <string name="install_with_su">靜默安裝(root)</string>
<string name="install_with_su_summ">勾選後在 EdXposed Manager 內安裝模組時將不再顯示安裝提示</string> <string name="install_with_su_summ">勾選後在 EdXposed Manager 內安裝模組時將不再顯示安裝提示</string>
<string name="module_saved">模組已儲存至 %1$s</string> <string name="module_saved">模組已儲存至 %1$s</string>

View File

@ -128,7 +128,6 @@
<!-- Logs tab --> <!-- Logs tab -->
<string name="logs_save_failed">Could not write log to SD card:</string> <string name="logs_save_failed">Could not write log to SD card:</string>
<string name="sdcard_not_writable">SD card not found or not writable</string>
<string name="menuClearLog">Clear log now</string> <string name="menuClearLog">Clear log now</string>
<string name="logs_cleared">Log successfully cleared.</string> <string name="logs_cleared">Log successfully cleared.</string>
<string name="logs_clear_failed">Could not clear the log:</string> <string name="logs_clear_failed">Could not clear the log:</string>
@ -158,8 +157,6 @@
<string name="download_could_not_read_file">Could not read downloaded file: %s</string> <string name="download_could_not_read_file">Could not read downloaded file: %s</string>
<string name="download_no_valid_apk">Downloaded file is not a valid APK (or incompatible)</string> <string name="download_no_valid_apk">Downloaded file is not a valid APK (or incompatible)</string>
<string name="download_incorrect_package_name">Package name is incorrect (downloaded: %1$s, expected: %2$s)</string> <string name="download_incorrect_package_name">Package name is incorrect (downloaded: %1$s, expected: %2$s)</string>
<string name="download_running">Download is running</string>
<string name="download_progress" translatable="false">%1$,d / %2$,d kB</string>
<!-- RepoLoader --> <!-- RepoLoader -->
<string name="repo_download_failed_http">Downloading %1$s failed: %2$d (%3$s)</string> <string name="repo_download_failed_http">Downloading %1$s failed: %2$d (%3$s)</string>
@ -176,7 +173,6 @@
<string name="no_installed_modules">There are no installed modules!</string> <string name="no_installed_modules">There are no installed modules!</string>
<string name="enable_heads_up">Enable Heads-Up notification</string> <string name="enable_heads_up">Enable Heads-Up notification</string>
<string name="enable_heads_up_summary">This option enables heads up notification on new/updated module</string> <string name="enable_heads_up_summary">This option enables heads up notification on new/updated module</string>
<string name="no_backup_found">No backup found</string>
<string name="no_enabled_modules">There are no modules enabled</string> <string name="no_enabled_modules">There are no modules enabled</string>
<string name="share">Share with…</string> <string name="share">Share with…</string>
@ -198,7 +194,6 @@
<string name="changelog">Changelog</string> <string name="changelog">Changelog</string>
<string name="scroll_top">Scroll to top</string> <string name="scroll_top">Scroll to top</string>
<string name="file_is_empty">File is empty</string>
<string name="install_with_su">Silent installation (root)</string> <string name="install_with_su">Silent installation (root)</string>
<string name="install_with_su_summ">If checked EdXposed Manager will install modules without a prompt</string> <string name="install_with_su_summ">If checked EdXposed Manager will install modules without a prompt</string>
<string name="module_saved">Module saved at %1$s</string> <string name="module_saved">Module saved at %1$s</string>

View File

@ -2,10 +2,7 @@
<paths> <paths>
<external-cache-path <external-cache-path
name="downloads" name="downloads"
path="downloads" /> path="/downloads" />
<external-path
name="download"
path="Download" />
<!--suppress AndroidElementNotAllowed --> <!--suppress AndroidElementNotAllowed -->
<root-path <root-path
name="log_legacy" name="log_legacy"