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">
<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.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
@ -98,7 +96,7 @@
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="org.meowcat.edxposed.manager.fileprovider"
android:authorities="${applicationId}.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data

View File

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

View File

@ -1,13 +1,10 @@
package org.meowcat.edxposed.manager;
import android.Manifest;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.DialogInterface;
import android.content.pm.PackageManager;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.os.Build;
import android.os.Bundle;
import android.os.Looper;
import android.text.TextUtils;
@ -20,7 +17,6 @@ import androidx.annotation.Nullable;
import androidx.annotation.StyleRes;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.app.AppCompatDelegate;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import androidx.core.view.ViewCompat;
@ -152,16 +148,6 @@ public class BaseActivity extends AppCompatActivity {
.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) {
if (!Shell.rootAccess()) {
showAlert(getString(R.string.root_failed));

View File

@ -1,13 +1,9 @@
package org.meowcat.edxposed.manager;
import android.Manifest;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.text.Html;
import android.view.LayoutInflater;
import android.view.View;
@ -20,11 +16,9 @@ import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.fragment.app.Fragment;
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.json.XposedTab;
@ -33,8 +27,6 @@ import org.meowcat.edxposed.manager.util.json.XposedZip;
import java.util.List;
import java.util.Objects;
import static org.meowcat.edxposed.manager.XposedApp.WRITE_EXTERNAL_PERMISSION;
public class BaseAdvancedInstaller extends Fragment {
private View mClickedButton;
@ -88,16 +80,6 @@ public class BaseAdvancedInstaller extends Fragment {
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
public void onDestroy() {
super.onDestroy();
@ -143,31 +125,21 @@ public class BaseAdvancedInstaller extends Fragment {
.setMessage(s).setPositiveButton(android.R.string.ok, null).show();
});
btnInstall.setOnClickListener(v -> {
mClickedButton = v;
if (checkPermissions()) return;
btnInstall.setOnClickListener(v -> areYouSure(R.string.warningArchitecture,
(dialog, which) -> {
XposedZip selectedInstaller = (XposedZip) chooserInstallers.getSelectedItem();
Uri uri = Uri.parse(selectedInstaller.link);
Intent intent = new Intent(Intent.ACTION_VIEW, uri);
startActivity(intent);
}));
areYouSure(R.string.warningArchitecture,
(dialog, which) -> {
XposedZip selectedInstaller = (XposedZip) chooserInstallers.getSelectedItem();
Uri uri = Uri.parse(selectedInstaller.link);
Intent intent = new Intent(Intent.ACTION_VIEW, uri);
startActivity(intent);
});
});
btnUninstall.setOnClickListener(v -> {
mClickedButton = v;
if (checkPermissions()) return;
areYouSure(R.string.warningArchitecture,
(dialog, which) -> {
XposedZip selectedUninstaller = (XposedZip) chooserUninstallers.getSelectedItem();
Uri uri = Uri.parse(selectedUninstaller.link);
Intent intent = new Intent(Intent.ACTION_VIEW, uri);
startActivity(intent);
});
});
btnUninstall.setOnClickListener(v -> areYouSure(R.string.warningArchitecture,
(dialog, which) -> {
XposedZip selectedUninstaller = (XposedZip) chooserUninstallers.getSelectedItem();
Uri uri = Uri.parse(selectedUninstaller.link);
Intent intent = new Intent(Intent.ACTION_VIEW, uri);
startActivity(intent);
}));
noticeTv.setText(Html.fromHtml(notice()));
author.setText(getString(R.string.download_author, author()));
@ -198,20 +170,6 @@ public class BaseAdvancedInstaller extends Fragment {
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")
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.widget.SearchView;
import androidx.appcompat.widget.Toolbar;
import androidx.core.view.MenuItemCompat;
import androidx.recyclerview.widget.DividerItemDecoration;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
@ -73,7 +72,7 @@ public class BlackListActivity extends BaseActivity implements AppAdapter.Callba
@Override
public boolean onCreateOptionsMenu(@NonNull Menu 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);
return super.onCreateOptionsMenu(menu);
}
@ -126,11 +125,6 @@ public class BlackListActivity extends BaseActivity implements AppAdapter.Callba
AppHelper.showMenu(this, getSupportFragmentManager(), v, info);
}
@Override
public void onPointerCaptureChanged(boolean hasCapture) {
}
@Override
public void onBackPressed() {
if (mSearchView.isIconified()) {

View File

@ -10,7 +10,6 @@ import androidx.annotation.Nullable;
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.widget.SearchView;
import androidx.appcompat.widget.Toolbar;
import androidx.core.view.MenuItemCompat;
import androidx.recyclerview.widget.DividerItemDecoration;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
@ -68,15 +67,10 @@ public class CompatListActivity extends BaseActivity implements AppAdapter.Callb
};
}
@Override
public void onResume() {
super.onResume();
}
@Override
public boolean onCreateOptionsMenu(@NonNull Menu 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);
return super.onCreateOptionsMenu(menu);
}

View File

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

View File

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

View File

@ -40,7 +40,7 @@ public class DownloadDetailsSettingsFragment extends PreferenceFragmentCompat {
if (prefs.getBoolean("no_global", true)) {
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();
}
}

View File

@ -1,12 +1,16 @@
package org.meowcat.edxposed.manager;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.res.Resources;
import android.net.Uri;
import android.os.Bundle;
import android.text.method.LinkMovementMethod;
import android.util.Log;
import android.util.TypedValue;
import android.view.LayoutInflater;
import android.view.View;
@ -35,11 +39,11 @@ import org.meowcat.edxposed.manager.util.chrome.LinkTransformationMethod;
import org.meowcat.edxposed.manager.widget.DownloadView;
import java.io.File;
import java.io.FileInputStream;
import java.io.OutputStream;
import java.text.DateFormat;
import java.util.Date;
import static org.meowcat.edxposed.manager.XposedApp.WRITE_EXTERNAL_PERMISSION;
public class DownloadDetailsVersionsFragment extends ListFragment {
private static View rootView;
private DownloadDetailsActivity mActivity;
@ -93,13 +97,31 @@ public class DownloadDetailsVersionsFragment extends ListFragment {
}
@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) {
DownloadView.mClickedButton.performClick();
} else {
Snackbar.make(rootView, R.string.permissionNotGranted, Snackbar.LENGTH_LONG).show();
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (resultCode != Activity.RESULT_OK) {
return;
}
if (requestCode == 42) {
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")
private class JSONParser extends AsyncTask<Void, Void, String> {
private String newApkVersion = null;
private String newApkLink = null;
private String newApkChangelog = null;
@Override
protected String doInBackground(Void... params) {
try {
@ -115,9 +111,9 @@ public class EdDownloadActivity extends BaseActivity {
tabsAdapter.notifyDataSetChanged();
}
newApkVersion = xposedJson.apk.version;
newApkLink = xposedJson.apk.link;
newApkChangelog = xposedJson.apk.changelog;
String newApkVersion = xposedJson.apk.version;
String newApkLink = xposedJson.apk.link;
String newApkChangelog = xposedJson.apk.changelog;
if (newApkVersion == null) {
return;

View File

@ -1,15 +1,11 @@
package org.meowcat.edxposed.manager;
import android.Manifest;
import android.annotation.SuppressLint;
import android.app.ProgressDialog;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.view.LayoutInflater;
import android.view.Menu;
@ -23,7 +19,6 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.widget.Toolbar;
import androidx.core.app.ActivityCompat;
import androidx.core.content.FileProvider;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
@ -36,6 +31,7 @@ import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
@ -48,7 +44,6 @@ public class LogsActivity extends BaseActivity {
XposedApp.BASE_DIR + "log/error.log.old");
private File mFileAllLog = new File(XposedApp.BASE_DIR + "log/all.log");
private File mFileAllLogOld = new File(XposedApp.BASE_DIR + "log/all.log.old");
private MenuItem mClickedMenuItem = null;
private LogsAdapter mAdapter;
private RecyclerView mListView;
private Handler handler = new Handler();
@ -124,7 +119,6 @@ public class LogsActivity extends BaseActivity {
@Override
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
mClickedMenuItem = item;
switch (item.getItemId()) {
case R.id.menu_scroll_top:
scrollTop();
@ -138,7 +132,8 @@ public class LogsActivity extends BaseActivity {
case R.id.menu_send:
try {
send();
} catch (NullPointerException ignored) {
} catch (Exception e) {
Snackbar.make(findViewById(R.id.snackbar), e.getLocalizedMessage(), Snackbar.LENGTH_LONG).show();
}
return true;
case R.id.menu_save:
@ -186,34 +181,8 @@ public class LogsActivity extends BaseActivity {
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")
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();
String filename = String.format(
"EdXposed_Verbose_%04d%02d%02d_%02d%02d%02d.log",
@ -221,22 +190,39 @@ public class LogsActivity extends BaseActivity {
now.get(Calendar.DAY_OF_MONTH), now.get(Calendar.HOUR_OF_DAY),
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);
}
try {
FileInputStream in = new FileInputStream(allLog ? mFileAllLog : mFileErrorLog);
FileOutputStream out = new FileOutputStream(targetFile);
byte[] buffer = new byte[1024];
int len;
while ((len = in.read(buffer)) > 0) {
out.write(buffer, 0, len);
@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 {
OutputStream os = getContentResolver().openOutputStream(uri);
if (os != null) {
FileInputStream in = new FileInputStream(allLog ? mFileAllLog : mFileErrorLog);
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();
}
}
}
in.close();
out.close();
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();
}
}

View File

@ -9,8 +9,6 @@ import android.widget.ImageView;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.appcompat.view.menu.MenuBuilder;
import androidx.appcompat.view.menu.MenuPopupHelper;
import androidx.appcompat.widget.PopupMenu;
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);
appMenu.inflate(R.menu.menu_installer);
appMenu.setOnMenuItemClickListener(this::onOptionsItemSelected);
MenuPopupHelper menuHelper = new MenuPopupHelper(MainActivity.this, (MenuBuilder) appMenu.getMenu(), menu);
menuHelper.setForceShowIcon(true);
menuHelper.show();
appMenu.show();
});
String installedXposedVersion;
try {

View File

@ -1,15 +1,11 @@
package org.meowcat.edxposed.manager;
import android.content.ActivityNotFoundException;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.net.Uri;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.text.TextUtils;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
@ -24,7 +20,6 @@ import androidx.annotation.NonNull;
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.widget.SearchView;
import androidx.appcompat.widget.Toolbar;
import androidx.core.view.MenuItemCompat;
import androidx.recyclerview.widget.DividerItemDecoration;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
@ -43,15 +38,11 @@ import org.meowcat.edxposed.manager.util.NavUtil;
import org.meowcat.edxposed.manager.util.RepoLoader;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
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.InputStreamReader;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.text.DateFormat;
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 ModuleUtil mModuleUtil;
private ModuleAdapter mAdapter = null;
private MenuItem mClickedMenuItem = null;
private RecyclerView mListView;
private SwipeRefreshLayout mSwipeRefreshLayout;
private Runnable reloadModules = new Runnable() {
@ -162,152 +152,140 @@ public class ModulesActivity extends BaseActivity implements ModuleUtil.ModuleLi
@Override
public boolean onCreateOptionsMenu(Menu 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);
return super.onCreateOptionsMenu(menu);
}
@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);
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (resultCode != RESULT_OK) {
return;
}
if (requestCode == 42) {
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
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
File enabledModulesPath = new File(XposedApp.createFolder(), "enabled_modules.list");
File installedModulesPath = new File(XposedApp.createFolder(), "installed_modules.list");
File listModules = new File(XposedApp.ENABLED_MODULES_LIST_FILE);
mClickedMenuItem = item;
if (checkPermissions())
return false;
Intent intent;
switch (item.getItemId()) {
case R.id.export_enabled_modules:
if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
return false;
}
if (ModuleUtil.getInstance().getEnabledModules().isEmpty()) {
Snackbar.make(findViewById(R.id.snackbar), R.string.no_enabled_modules, Snackbar.LENGTH_SHORT).show();
return false;
}
try {
XposedApp.createFolder();
FileInputStream in = new FileInputStream(listModules);
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();
intent = new Intent(Intent.ACTION_CREATE_DOCUMENT);
intent.addCategory(Intent.CATEGORY_OPENABLE);
intent.setType("text/*");
intent.putExtra(Intent.EXTRA_TITLE, "enabled_modules.list");
startActivityForResult(intent, 42);
return true;
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();
if (installedModules.isEmpty()) {
Snackbar.make(findViewById(R.id.snackbar), R.string.no_installed_modules, Snackbar.LENGTH_SHORT).show();
return false;
}
try {
XposedApp.createFolder();
FileWriter fw = new FileWriter(installedModulesPath);
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();
intent = new Intent(Intent.ACTION_CREATE_DOCUMENT);
intent.addCategory(Intent.CATEGORY_OPENABLE);
intent.setType("text/*");
intent.putExtra(Intent.EXTRA_TITLE, "installed_modules.list");
startActivityForResult(intent, 43);
return true;
case R.id.import_installed_modules:
return importModules(installedModulesPath);
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);
}
private boolean importModules(File path) {
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;
private void importModules(Uri uri) {
RepoLoader repoLoader = RepoLoader.getInstance();
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 {
assert ips != null;
InputStreamReader ipsr = new InputStreamReader(ips);
BufferedReader br = new BufferedReader(ipsr);
InputStream inputStream = getContentResolver().openInputStream(uri);
InputStreamReader isr = new InputStreamReader(inputStream);
BufferedReader br = new BufferedReader(isr);
String line;
while ((line = br.readLine()) != null) {
Module m = repoLoader.getModule(line);
if (m == null) {
Snackbar.make(findViewById(R.id.snackbar), getString(R.string.download_details_not_found,
line), Snackbar.LENGTH_SHORT).show();
Snackbar.make(findViewById(R.id.snackbar), getString(R.string.download_details_not_found, line), Snackbar.LENGTH_SHORT).show();
} else {
list.add(m);
}
}
br.close();
} catch (ActivityNotFoundException | IOException e) {
Snackbar.make(findViewById(R.id.snackbar), e.toString(), Snackbar.LENGTH_SHORT).show();
} catch (Exception e) {
e.printStackTrace();
Snackbar.make(findViewById(R.id.snackbar), e.getLocalizedMessage(), Snackbar.LENGTH_SHORT).show();
}
for (final Module m : list) {
if (mModuleUtil.getModule(m.packageName) != null) {
continue;
}
ModuleVersion mv = null;
for (int i = 0; i < m.versions.size(); i++) {
ModuleVersion mvTemp = m.versions.get(i);
@ -319,13 +297,11 @@ public class ModulesActivity extends BaseActivity implements ModuleUtil.ModuleLi
}
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();
return true;
}
@Override

View File

@ -3,13 +3,9 @@ package org.meowcat.edxposed.manager;
import android.annotation.SuppressLint;
import android.content.Context;
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.os.Build;
import android.os.Bundle;
import android.os.FileUtils;
import android.text.Html;
import android.util.Log;
import android.view.LayoutInflater;
@ -28,13 +24,10 @@ import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.lang.reflect.Method;
import java.util.List;
import java.util.Objects;
@SuppressLint("StaticFieldLeak")
public class StatusInstallerFragment extends Fragment {
public static final File DISABLE_FILE = new File(XposedApp.BASE_DIR + "conf/disabled");
private static AppCompatActivity sActivity;
private static String mUpdateLink;
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
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@ -119,7 +98,6 @@ public class StatusInstallerFragment extends Fragment {
}
@SuppressLint("WorldReadableFiles")
@SuppressWarnings("ResultOfMethodCallIgnored")
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.status_installer, container, false);
@ -156,8 +134,6 @@ public class StatusInstallerFragment extends Fragment {
cpu.setText(getCompleteArch());
determineVerifiedBootState(v);
refreshKnownIssue();
return v;
}
@ -191,57 +167,58 @@ public class StatusInstallerFragment extends Fragment {
}
}
@SuppressWarnings("SameParameterValue")
private boolean checkAppInstalled(Context context, String pkgName) {
if (pkgName == null || pkgName.isEmpty()) {
return false;
}
final PackageManager packageManager = context.getPackageManager();
List<PackageInfo> info = packageManager.getInstalledPackages(0);
if (info == null || info.isEmpty()) {
return false;
}
for (int i = 0; i < info.size(); i++) {
if (pkgName.equals(info.get(i).packageName)) {
return true;
/*
@SuppressWarnings("SameParameterValue")
private boolean checkAppInstalled(Context context, String pkgName) {
if (pkgName == null || pkgName.isEmpty()) {
return false;
}
}
return false;
}
@SuppressLint("StringFormatInvalid")
private void refreshKnownIssue() {
String issueName = null;
String issueLink = null;
final ApplicationInfo appInfo = Objects.requireNonNull(getActivity()).getApplicationInfo();
final File baseDir = new File(XposedApp.BASE_DIR);
final File baseDirCanonical = getCanonicalFile(baseDir);
final File baseDirActual = new File(Build.VERSION.SDK_INT >= 24 ? appInfo.deviceProtectedDataDir : appInfo.dataDir);
final File baseDirActualCanonical = getCanonicalFile(baseDirActual);
if (new File("/system/framework/core.jar.jex").exists()) {
issueName = "Aliyun OS";
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"))) {
// issueName = "MIUI/Dexspy";
// 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()) {
// issueName = "Samsung TouchWiz ROM";
// issueLink = "https://forum.xda-developers.com/showthread.php?t=3034811";
} else if (!baseDirCanonical.equals(baseDirActualCanonical)) {
Log.e(XposedApp.TAG, "Base directory: " + getPathWithCanonicalPath(baseDir, baseDirCanonical));
Log.e(XposedApp.TAG, "Expected: " + getPathWithCanonicalPath(baseDirActual, baseDirActualCanonical));
issueName = getString(R.string.known_issue_wrong_base_directory, getPathWithCanonicalPath(baseDirActual, baseDirActualCanonical));
} else if (!baseDir.exists()) {
issueName = getString(R.string.known_issue_missing_base_directory);
issueLink = "https://github.com/rovo89/XposedInstaller/issues/393";
} else if (checkAppInstalled(getContext(), "com.solohsu.android.edxp.manager")) {
issueName = getString(R.string.edxp_installer_installed);
issueLink = getString(R.string.about_support);
final PackageManager packageManager = context.getPackageManager();
List<PackageInfo> info = packageManager.getInstalledPackages(0);
if (info == null || info.isEmpty()) {
return false;
}
for (int i = 0; i < info.size(); i++) {
if (pkgName.equals(info.get(i).packageName)) {
return true;
}
}
return false;
}
}
@SuppressLint("StringFormatInvalid")
private void refreshKnownIssue() {
String issueName = null;
String issueLink = null;
final ApplicationInfo appInfo = Objects.requireNonNull(getActivity()).getApplicationInfo();
final File baseDir = new File(XposedApp.BASE_DIR);
final File baseDirCanonical = getCanonicalFile(baseDir);
final File baseDirActual = new File(Build.VERSION.SDK_INT >= 24 ? appInfo.deviceProtectedDataDir : appInfo.dataDir);
final File baseDirActualCanonical = getCanonicalFile(baseDirActual);
if (new File("/system/framework/core.jar.jex").exists()) {
issueName = "Aliyun OS";
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"))) {
// issueName = "MIUI/Dexspy";
// 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()) {
// issueName = "Samsung TouchWiz ROM";
// issueLink = "https://forum.xda-developers.com/showthread.php?t=3034811";
} else if (!baseDirCanonical.equals(baseDirActualCanonical)) {
Log.e(XposedApp.TAG, "Base directory: " + getPathWithCanonicalPath(baseDir, baseDirCanonical));
Log.e(XposedApp.TAG, "Expected: " + getPathWithCanonicalPath(baseDirActual, baseDirActualCanonical));
issueName = getString(R.string.known_issue_wrong_base_directory, getPathWithCanonicalPath(baseDirActual, baseDirActualCanonical));
} else if (!baseDir.exists()) {
issueName = getString(R.string.known_issue_missing_base_directory);
issueLink = "https://github.com/rovo89/XposedInstaller/issues/393";
} else if (checkAppInstalled(getContext(), "com.solohsu.android.edxp.manager")) {
issueName = getString(R.string.edxp_installer_installed);
issueLink = getString(R.string.about_support);
}
}
*/
private String getAndroidVersion() {
switch (Build.VERSION.SDK_INT) {
// case 16:
@ -296,23 +273,24 @@ public class StatusInstallerFragment extends Fragment {
return manufacturer;
}
private File getCanonicalFile(File file) {
try {
return file.getCanonicalFile();
} catch (IOException e) {
Log.e(XposedApp.TAG, "Failed to get canonical file for " + file.getAbsolutePath(), e);
return file;
/*
private File getCanonicalFile(File file) {
try {
return file.getCanonicalFile();
} catch (IOException e) {
Log.e(XposedApp.TAG, "Failed to get canonical file for " + file.getAbsolutePath(), e);
return file;
}
}
}
private String getPathWithCanonicalPath(File file, File canonical) {
if (file.equals(canonical)) {
return file.getAbsolutePath();
} else {
return file.getAbsolutePath() + " \u2192 " + canonical.getAbsolutePath();
private String getPathWithCanonicalPath(File file, File canonical) {
if (file.equals(canonical)) {
return file.getAbsolutePath();
} else {
return file.getAbsolutePath() + " \u2192 " + canonical.getAbsolutePath();
}
}
}
*/
private int extractIntPart(String str) {
int result = 0, length = str.length();
for (int offset = 0; offset < length; offset++) {

View File

@ -105,31 +105,35 @@ public class XposedApp extends de.robv.android.xposed.installer.XposedApp implem
public void onCreate() {
super.onCreate();
try {
Thread.setDefaultUncaughtExceptionHandler((thread, throwable) -> {
if (!BuildConfig.DEBUG) {
try {
Thread.setDefaultUncaughtExceptionHandler((thread, throwable) -> {
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
throwable.printStackTrace(pw);
String stackTraceString = sw.toString();
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
throwable.printStackTrace(pw);
String stackTraceString = sw.toString();
//Reduce data to 128KB so we don't get a TransactionTooLargeException when sending the intent.
//The limit is 1MB on Android but some devices seem to have it lower.
//See: http://developer.android.com/reference/android/os/TransactionTooLargeException.html
//And: http://stackoverflow.com/questions/11451393/what-to-do-on-transactiontoolargeexception#comment46697371_12809171
if (stackTraceString.length() > 131071) {
String disclaimer = " [stack trace too large]";
stackTraceString = stackTraceString.substring(0, 131071 - disclaimer.length()) + disclaimer;
}
Intent intent = new Intent(XposedApp.this, CrashReportActivity.class);
intent.putExtra(BuildConfig.APPLICATION_ID + ".EXTRA_STACK_TRACE", stackTraceString);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
XposedApp.this.startActivity(intent);
android.os.Process.killProcess(android.os.Process.myPid());
System.exit(10);
});
} catch (Throwable t) {
//Reduce data to 128KB so we don't get a TransactionTooLargeException when sending the intent.
//The limit is 1MB on Android but some devices seem to have it lower.
//See: http://developer.android.com/reference/android/os/TransactionTooLargeException.html
//And: http://stackoverflow.com/questions/11451393/what-to-do-on-transactiontoolargeexception#comment46697371_12809171
if (stackTraceString.length() > 131071) {
String disclaimer = " [stack trace too large]";
stackTraceString = stackTraceString.substring(0, 131071 - disclaimer.length()) + disclaimer;
}
Intent intent = new Intent(XposedApp.this, CrashReportActivity.class);
intent.putExtra(BuildConfig.APPLICATION_ID + ".EXTRA_STACK_TRACE", stackTraceString);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
XposedApp.this.startActivity(intent);
android.os.Process.killProcess(android.os.Process.myPid());
System.exit(10);
});
} catch (Throwable t) {
t.printStackTrace();
}
}
mInstance = this;
mUiThread = Thread.currentThread();
mMainHandler = new Handler();

View File

@ -4,26 +4,17 @@ import android.annotation.SuppressLint;
import android.app.DownloadManager;
import android.app.DownloadManager.Query;
import android.app.DownloadManager.Request;
import android.app.ProgressDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.SharedPreferences;
import android.database.Cursor;
import android.net.Uri;
import android.os.Environment;
import android.provider.MediaStore;
import android.util.Log;
import android.widget.Toast;
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.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.FileOutputStream;
@ -40,7 +31,7 @@ import java.util.Map;
import java.util.Objects;
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 Map<String, DownloadFinishedCallback> mCallbacks = new HashMap<>();
@SuppressLint("StaticFieldLeak")
@ -48,152 +39,55 @@ public class DownloadsUtil {
private static final SharedPreferences mPref = mApp
.getSharedPreferences("download_cache", Context.MODE_PRIVATE);
private static String DOWNLOAD_MODULES = "modules";
private static DownloadInfo add(Builder b) {
Context context = b.mContext;
removeAllForUrl(context, b.mUrl);
if (!b.mDialog) {
synchronized (mCallbacks) {
mCallbacks.put(b.mUrl, b.mCallback);
}
}
String savePath = "Download/EdXposedManager";
if (b.mModule) {
savePath += "/modules";
synchronized (mCallbacks) {
mCallbacks.put(b.mUrl, b.mCallback);
}
Request request = new Request(Uri.parse(b.mUrl));
request.setTitle(b.mTitle);
request.setMimeType(b.mMimeType.toString());
if (b.mSave) {
/*if (b.mSave) {
try {
request.setDestinationInExternalPublicDir(savePath, b.mTitle + b.mMimeType.getExtension());
} catch (IllegalStateException e) {
Toast.makeText(context, e.getMessage(), Toast.LENGTH_SHORT).show();
}
} else if (b.mDestination != null) {
//noinspection ResultOfMethodCallIgnored
b.mDestination.getParentFile().mkdirs();
removeAllForLocalFile(context, b.mDestination);
request.setDestinationUri(Uri.fromFile(b.mDestination));
}
} else */
File destination = new File(context.getExternalCacheDir(), "/downloads/" + b.mTitle + b.mMimeType.getExtension());
request.setDestinationUri(Uri.fromFile(destination));
request.setNotificationVisibility(Request.VISIBILITY_VISIBLE);
DownloadManager dm = (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE);
long id = dm.enqueue(request);
if (b.mDialog) {
showDownloadDialog(b, id);
}
return getById(context, id);
}
private static File[] getDownloadDirs(String subDir) {
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) {
public static DownloadInfo addModule(Context context, String title, String url, DownloadFinishedCallback callback) {
return new Builder(context)
.setTitle(title)
.setUrl(url)
.setDestinationFromUrl(DownloadsUtil.DOWNLOAD_MODULES)
.setCallback(callback)
.setSave(save)
.setModule(true)
.setMimeType(MIME_TYPES.APK)
.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));
/*
public static ModuleVersion getStableVersion(Module m) {
for (int i = 0; i < m.versions.size(); i++) {
ModuleVersion mvTemp = m.versions.get(i);
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);
}
});
if (mvTemp.relType == ReleaseType.STABLE) {
return mvTemp;
}
}
}.start();
}
public static ModuleVersion getStableVersion(Module m) {
for (int i = 0; i < m.versions.size(); i++) {
ModuleVersion mvTemp = m.versions.get(i);
if (mvTemp.relType == ReleaseType.STABLE) {
return mvTemp;
}
return null;
}
return null;
}
*/
public static DownloadInfo getById(Context context, long id) {
DownloadManager dm = (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE);
Cursor c = dm.query(new Query().setFilterById(id));
@ -317,7 +211,7 @@ public class DownloadsUtil {
dm.remove(ids);
}
/*
private static void removeAllForLocalFile(Context context, File file) {
//noinspection ResultOfMethodCallIgnored
file.delete();
@ -367,7 +261,7 @@ public class DownloadsUtil {
ids[i] = idsList.get(i);
dm.remove(ids);
}
}*/
// public static void removeOutdated(Context context, long cutoff) {
// DownloadManager dm = (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE);
@ -563,9 +457,6 @@ public class DownloadsUtil {
private String mUrl = null;
private DownloadFinishedCallback mCallback = null;
private MIME_TYPES mMimeType = MIME_TYPES.APK;
private File mDestination = null;
private boolean mDialog = false;
private boolean mSave = false;
public Builder(Context context) {
mContext = context;
@ -591,33 +482,11 @@ public class DownloadsUtil {
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) {
this.mModule = module;
return this;
}
public Builder setDialog(boolean dialog) {
mDialog = dialog;
return this;
}
public DownloadInfo download() {
return add(this);
}

View File

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

View File

@ -42,16 +42,6 @@
android:visibility="gone"
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
android:id="@+id/btnInstall"
style="@style/Widget.MaterialComponents.Button"

View File

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

View File

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

View File

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

View File

@ -128,7 +128,6 @@
<!-- Logs tab -->
<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="logs_cleared">Log successfully cleared.</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_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_running">Download is running</string>
<string name="download_progress" translatable="false">%1$,d / %2$,d kB</string>
<!-- RepoLoader -->
<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="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="no_backup_found">No backup found</string>
<string name="no_enabled_modules">There are no modules enabled</string>
<string name="share">Share with…</string>
@ -198,7 +194,6 @@
<string name="changelog">Changelog</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_summ">If checked EdXposed Manager will install modules without a prompt</string>
<string name="module_saved">Module saved at %1$s</string>

View File

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