Fix possible crash & add crash report activity

This commit is contained in:
NekoInverter 2020-02-06 17:24:23 +08:00
parent 2e6f000cd6
commit fac904009d
15 changed files with 417 additions and 189 deletions

View File

@ -21,6 +21,9 @@
android:supportsRtl="true"
android:theme="@style/AppTheme"
tools:ignore="AllowBackup,GoogleAppIndexingWarning">
<activity
android:name=".CrashReportActivity"
android:process=":error_activity" />
<activity
android:name=".CompatListActivity"
android:label="@string/nav_title_compat_list" />

View File

@ -13,13 +13,11 @@ import android.os.Looper;
import android.text.TextUtils;
import android.view.MenuItem;
import android.view.View;
import android.widget.TextView;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.StyleRes;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.app.AppCompatDelegate;
import androidx.core.app.ActivityCompat;
@ -119,8 +117,10 @@ public class BaseActivity extends AppCompatActivity {
}
void softReboot() {
if (startShell())
if (!Shell.rootAccess()) {
showAlert(getString(R.string.root_failed));
return;
}
List<String> messages = new LinkedList<>();
Shell.Result result = Shell.su("setprop ctl.restart surfaceflinger; setprop ctl.restart zygote").exec();
@ -132,29 +132,16 @@ public class BaseActivity extends AppCompatActivity {
}
}
private boolean startShell() {
if (Shell.rootAccess())
return false;
showAlert(getString(R.string.root_failed));
return true;
}
void showAlert(final String result) {
if (Looper.myLooper() != Looper.getMainLooper()) {
runOnUiThread(() -> showAlert(result));
return;
}
AlertDialog dialog = new MaterialAlertDialogBuilder(this).setMessage(result).setPositiveButton(android.R.string.ok, null).create();
dialog.show();
TextView txtMessage = dialog
.findViewById(android.R.id.message);
try {
txtMessage.setTextSize(14);
} catch (NullPointerException ignored) {
}
new MaterialAlertDialogBuilder(this)
.setMessage(result)
.setPositiveButton(android.R.string.ok, null)
.show();
}
public boolean checkPermissions() {
@ -168,8 +155,10 @@ public class BaseActivity extends AppCompatActivity {
}
void reboot(String mode) {
if (startShell())
if (!Shell.rootAccess()) {
showAlert(getString(R.string.root_failed));
return;
}
List<String> messages = new LinkedList<>();

View File

@ -0,0 +1,120 @@
package org.meowcat.edxposed.manager;
import android.content.ClipData;
import android.content.ClipboardManager;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.os.Build;
import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import com.google.android.material.snackbar.Snackbar;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
public class CrashReportActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_crash_report);
findViewById(R.id.copyLogs).setOnClickListener(v -> {
ClipboardManager clipboard = (ClipboardManager) getSystemService(CLIPBOARD_SERVICE);
//Are there any devices without clipboard...?
if (clipboard != null) {
ClipData clip = ClipData.newPlainText("edcrash", getAllErrorDetailsFromIntent(getIntent()));
clipboard.setPrimaryClip(clip);
Snackbar.make(findViewById(R.id.snackbar), R.string.copy_toast_msg, Snackbar.LENGTH_SHORT).show();
}
});
}
public String getAllErrorDetailsFromIntent(@NonNull Intent intent) {
Date currentDate = new Date();
DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.US);
String buildDateAsString = getBuildDateAsString(dateFormat);
String versionName = getVersionName();
String errorDetails = "";
errorDetails += "Build version: " + versionName + " \n";
if (buildDateAsString != null) {
errorDetails += "Build date: " + buildDateAsString + " \n";
}
errorDetails += "Current date: " + dateFormat.format(currentDate) + " \n";
errorDetails += "Device: " + getDeviceModelName() + " \n \n";
errorDetails += "Stack trace: \n";
errorDetails += getStackTraceFromIntent(intent);
return errorDetails;
}
private String getBuildDateAsString(@NonNull DateFormat dateFormat) {
long buildDate;
try {
ApplicationInfo ai = getPackageManager().getApplicationInfo(getPackageName(), 0);
ZipFile zf = new ZipFile(ai.sourceDir);
ZipEntry ze = zf.getEntry("classes.dex");
buildDate = ze.getTime();
zf.close();
} catch (Exception e) {
buildDate = 0;
}
if (buildDate > 312764400000L) {
return dateFormat.format(new Date(buildDate));
} else {
return null;
}
}
private String getVersionName() {
try {
PackageInfo packageInfo = getPackageManager().getPackageInfo(getPackageName(), 0);
return packageInfo.versionName;
} catch (Exception e) {
return "Unknown";
}
}
private String getDeviceModelName() {
String manufacturer = Build.MANUFACTURER;
String model = Build.MODEL;
if (model.startsWith(manufacturer)) {
return capitalize(model);
} else {
return capitalize(manufacturer) + " " + model;
}
}
private String capitalize(@Nullable String s) {
if (s == null || s.length() == 0) {
return "";
}
char first = s.charAt(0);
if (Character.isUpperCase(first)) {
return s;
} else {
return Character.toUpperCase(first) + s.substring(1);
}
}
public String getStackTraceFromIntent(@NonNull Intent intent) {
return intent.getStringExtra(BuildConfig.APPLICATION_ID + ".EXTRA_STACK_TRACE");
}
}

View File

@ -102,7 +102,15 @@ public class DownloadActivity extends BaseActivity implements RepoLoader.RepoLis
mListView.setAdapter(mAdapter);
mListView.setLayoutManager(new LinearLayoutManager(this));
mListView.addItemDecoration(new StickyRecyclerHeadersDecoration(mAdapter));
StickyRecyclerHeadersDecoration headersDecor = new StickyRecyclerHeadersDecoration(mAdapter);
mListView.addItemDecoration(headersDecor);
mAdapter.registerAdapterDataObserver(new RecyclerView.AdapterDataObserver() {
@Override
public void onChanged() {
headersDecor.invalidateHeaders();
}
});
DividerItemDecoration dividerItemDecoration = new DividerItemDecoration(mListView.getContext(),
DividerItemDecoration.VERTICAL);
mListView.addItemDecoration(dividerItemDecoration);
@ -171,7 +179,6 @@ public class DownloadActivity extends BaseActivity implements RepoLoader.RepoLis
private void reloadItems() {
mAdapter.swapCursor(RepoDb.queryModuleOverview(mSortingOrder, mFilterText));
mAdapter.notifyDataSetChanged();
//mAdapter.getFilter().filter(mFilterText);
}
@Override

View File

@ -1,7 +1,6 @@
package org.meowcat.edxposed.manager;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
@ -41,8 +40,8 @@ import java.util.Date;
import static org.meowcat.edxposed.manager.XposedApp.WRITE_EXTERNAL_PERMISSION;
public class DownloadDetailsVersionsFragment extends ListFragment {
private DownloadDetailsActivity mActivity;
private static View rootView;
private DownloadDetailsActivity mActivity;
@Override
public void onActivityCreated(Bundle savedInstanceState) {

View File

@ -1,15 +1,11 @@
package org.meowcat.edxposed.manager;
import android.annotation.SuppressLint;
import android.content.ActivityNotFoundException;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.graphics.Color;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
@ -27,9 +23,6 @@ import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.view.menu.MenuBuilder;
import androidx.appcompat.view.menu.MenuPopupHelper;
import androidx.appcompat.widget.PopupMenu;
import androidx.appcompat.widget.SearchView;
import androidx.appcompat.widget.Toolbar;
import androidx.recyclerview.widget.DividerItemDecoration;
@ -108,6 +101,7 @@ public class ModulesActivity extends BaseActivity implements ModuleUtil.ModuleLi
mSwipeRefreshLayout.setRefreshing(false);
}
};
private String selectedPackageName;
private void filter(String constraint) {
filter.filter(constraint);
@ -129,15 +123,12 @@ public class ModulesActivity extends BaseActivity implements ModuleUtil.ModuleLi
mModuleUtil = ModuleUtil.getInstance();
mPm = getPackageManager();
installedXposedVersion = XposedApp.getXposedVersion();
if (Build.VERSION.SDK_INT >= 21) {
if (installedXposedVersion <= 0) {
addHeader();
}
} else {
//if (StatusInstallerFragment.DISABLE_FILE.exists()) installedXposedVersion = -1;
if (installedXposedVersion <= 0) {
addHeader();
}
if (installedXposedVersion <= 0) {
Snackbar.make(findViewById(R.id.snackbar), R.string.xposed_not_active, Snackbar.LENGTH_LONG).setAction(R.string.Settings, v -> {
Intent intent = new Intent();
intent.setClass(ModulesActivity.this, SettingsActivity.class);
startActivity(intent);
}).show();
}
mAdapter = new ModuleAdapter();
mModuleUtil.addListener(this);
@ -166,12 +157,6 @@ public class ModulesActivity extends BaseActivity implements ModuleUtil.ModuleLi
}
private void addHeader() {
//View notActiveNote = getLayoutInflater().inflate(R.layout.xposed_not_active_note, mListView, false);
//notActiveNote.setTag(NOT_ACTIVE_NOTE_TAG);
//mListView.addHeaderView(notActiveNote);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.menu_modules, menu);
@ -277,7 +262,6 @@ public class ModulesActivity extends BaseActivity implements ModuleUtil.ModuleLi
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();
@ -362,78 +346,56 @@ public class ModulesActivity extends BaseActivity implements ModuleUtil.ModuleLi
runOnUiThread(reloadModules);
}
@SuppressLint("RestrictedApi")
private void showMenu(@NonNull Context context,
@NonNull View anchor,
@NonNull ApplicationInfo info) {
PopupMenu appMenu = new PopupMenu(context, anchor);
appMenu.inflate(R.menu.context_menu_modules);
ModuleUtil.InstalledModule installedModule = ModuleUtil.getInstance().getModule(info.packageName);
if (installedModule == null) {
return;
@Override
public boolean onContextItemSelected(MenuItem item) {
ModuleUtil.InstalledModule module = ModuleUtil.getInstance().getModule(selectedPackageName);
if (module == null) {
return false;
}
try {
String support = RepoDb
.getModuleSupport(installedModule.packageName);
if (NavUtil.parseURL(support) == null)
appMenu.getMenu().removeItem(R.id.menu_support);
} catch (RepoDb.RowNotFoundException e) {
appMenu.getMenu().removeItem(R.id.menu_download_updates);
appMenu.getMenu().removeItem(R.id.menu_support);
switch (item.getItemId()) {
case R.id.menu_launch:
String packageName = module.packageName;
if (packageName == null) {
return false;
}
Intent launchIntent = getSettingsIntent(packageName);
if (launchIntent != null) {
startActivity(launchIntent);
} else {
Snackbar.make(findViewById(R.id.snackbar), R.string.module_no_ui, Snackbar.LENGTH_LONG).show();
}
return true;
case R.id.menu_download_updates:
Intent detailsIntent = new Intent(this, DownloadDetailsActivity.class);
detailsIntent.setData(Uri.fromParts("package", module.packageName, null));
startActivity(detailsIntent);
return true;
case R.id.menu_support:
NavUtil.startURL(this, Uri.parse(RepoDb.getModuleSupport(module.packageName)));
return true;
case R.id.menu_app_store:
Uri uri = Uri.parse("market://details?id=" + module.packageName);
Intent intent = new Intent(Intent.ACTION_VIEW, uri);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
try {
startActivity(intent);
} catch (Exception ex) {
ex.printStackTrace();
}
return true;
case R.id.menu_app_info:
startActivity(new Intent(ACTION_APPLICATION_DETAILS_SETTINGS, Uri.fromParts("package", module.packageName, null)));
return true;
case R.id.menu_uninstall:
startActivity(new Intent(Intent.ACTION_UNINSTALL_PACKAGE, Uri.fromParts("package", module.packageName, null)));
return true;
}
appMenu.setOnMenuItemClickListener(menuItem -> {
ModuleUtil.InstalledModule module = ModuleUtil.getInstance().getModule(info.packageName);
if (module == null) {
return false;
}
switch (menuItem.getItemId()) {
case R.id.menu_launch:
String packageName = module.packageName;
if (packageName == null) {
return false;
}
Intent launchIntent = getSettingsIntent(packageName);
if (launchIntent != null) {
startActivity(launchIntent);
} else {
Snackbar.make(findViewById(R.id.snackbar), R.string.module_no_ui, Snackbar.LENGTH_LONG).show();
}
return true;
case R.id.menu_download_updates:
Intent detailsIntent = new Intent(this, DownloadDetailsActivity.class);
detailsIntent.setData(Uri.fromParts("package", module.packageName, null));
startActivity(detailsIntent);
return true;
case R.id.menu_support:
NavUtil.startURL(this, Uri.parse(RepoDb.getModuleSupport(module.packageName)));
return true;
case R.id.menu_app_store:
Uri uri = Uri.parse("market://details?id=" + module.packageName);
Intent intent = new Intent(Intent.ACTION_VIEW, uri);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
try {
startActivity(intent);
} catch (Exception ex) {
ex.printStackTrace();
}
return true;
case R.id.menu_app_info:
startActivity(new Intent(ACTION_APPLICATION_DETAILS_SETTINGS, Uri.fromParts("package", module.packageName, null)));
return true;
case R.id.menu_uninstall:
startActivity(new Intent(Intent.ACTION_UNINSTALL_PACKAGE, Uri.fromParts("package", module.packageName, null)));
return true;
}
return true;
});
MenuPopupHelper menuHelper = new MenuPopupHelper(context, (MenuBuilder) appMenu.getMenu(), anchor);
menuHelper.setForceShowIcon(true);
menuHelper.show();
return super.onContextItemSelected(item);
}
private Intent getSettingsIntent(String packageName) {
@ -458,37 +420,6 @@ public class ModulesActivity extends BaseActivity implements ModuleUtil.ModuleLi
return intent;
}
public void onItemClick(View view) {
if (getFragmentManager() != null) {
try {
showMenu(this, view, Objects.requireNonNull(this).getPackageManager().getApplicationInfo((String) view.getTag(), 0));
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
String packageName = (String) view.getTag();
if (packageName == null)
return;
Intent launchIntent = getSettingsIntent(packageName);
if (launchIntent != null) {
startActivity(launchIntent);
} else {
Snackbar.make(findViewById(R.id.snackbar), R.string.module_no_ui, Snackbar.LENGTH_LONG).show();
}
}
} else {
String packageName = (String) view.getTag();
if (packageName == null) {
return;
}
Intent launchIntent = getSettingsIntent(packageName);
if (launchIntent != null) {
startActivity(launchIntent);
} else {
Snackbar.make(findViewById(R.id.snackbar), R.string.module_no_ui, Snackbar.LENGTH_LONG).show();
}
}
}
private boolean lowercaseContains(String s, CharSequence filter) {
return !TextUtils.isEmpty(s) && s.toLowerCase().contains(filter);
}
@ -514,11 +445,41 @@ public class ModulesActivity extends BaseActivity implements ModuleUtil.ModuleLi
@Override
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
//View view = holder.itemView;
ModuleUtil.InstalledModule item = (ModuleUtil.InstalledModule) items.toArray()[position];
holder.itemView.setOnClickListener(v -> ModulesActivity.this.onItemClick(holder.itemView));
holder.itemView.setTag(item.packageName);
holder.itemView.setOnClickListener(v -> {
String packageName = item.packageName;
if (packageName == null) {
return;
}
Intent launchIntent = getSettingsIntent(packageName);
if (launchIntent != null) {
startActivity(launchIntent);
} else {
Snackbar.make(findViewById(R.id.snackbar), R.string.module_no_ui, Snackbar.LENGTH_LONG).show();
}
});
holder.itemView.setOnLongClickListener(v -> {
selectedPackageName = item.packageName;
return false;
});
holder.itemView.setOnCreateContextMenuListener((menu, v, menuInfo) -> {
getMenuInflater().inflate(R.menu.context_menu_modules, menu);
ModuleUtil.InstalledModule installedModule = ModuleUtil.getInstance().getModule(item.packageName);
if (installedModule == null) {
return;
}
try {
String support = RepoDb.getModuleSupport(installedModule.packageName);
if (NavUtil.parseURL(support) == null) {
menu.removeItem(R.id.menu_support);
}
} catch (RepoDb.RowNotFoundException e) {
menu.removeItem(R.id.menu_download_updates);
menu.removeItem(R.id.menu_support);
}
});
holder.appName.setText(item.getAppName());
TextView version = holder.appVersion;

View File

@ -27,6 +27,8 @@ import org.meowcat.edxposed.manager.util.NotificationUtil;
import org.meowcat.edxposed.manager.util.RepoLoader;
import java.io.File;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.lang.reflect.Method;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
@ -102,6 +104,31 @@ public class XposedApp extends de.robv.android.xposed.installer.XposedApp implem
public void onCreate() {
super.onCreate();
try {
Thread.setDefaultUncaughtExceptionHandler((thread, throwable) -> {
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) {
}
mInstance = this;
mUiThread = Thread.currentThread();
mMainHandler = new Handler();

View File

@ -0,0 +1,70 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/snackbar"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipChildren="false"
android:clipToPadding="false">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_centerInParent="true">
<com.google.android.material.card.MaterialCardView
android:id="@+id/warning_crash"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="16dp"
android:orientation="horizontal"
app:cardBackgroundColor="@color/amber_500">
<RelativeLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
tools:ignore="UseCompoundDrawables">
<ImageView
android:id="@+id/warning"
android:layout_width="128dp"
android:layout_height="128dp"
android:layout_centerHorizontal="true"
android:layout_margin="8dp"
android:src="@drawable/ic_warning"
tools:ignore="ContentDescription" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@+id/warning"
android:layout_centerHorizontal="true"
android:layout_marginBottom="16dp"
android:text="Manager crashed"
android:textAlignment="center"
android:textAllCaps="true"
android:textSize="36sp"
android:textStyle="bold" />
</RelativeLayout>
</com.google.android.material.card.MaterialCardView>
<com.google.android.material.button.MaterialButton
android:id="@+id/copyLogs"
style="@style/Widget.MaterialComponents.Button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_marginTop="4dp"
android:text="Copy logs"
android:theme="@style/Widget.AppCompat.Button.Colored" />
</LinearLayout>
</RelativeLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View File

@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/snackbar"
android:layout_width="match_parent"
android:layout_height="match_parent"
@ -12,10 +13,10 @@
android:id="@+id/swipeRefreshLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="?attr/actionBarSize"
android:clipChildren="false"
android:clipToPadding="false"
android:orientation="vertical">
android:orientation="vertical"
app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerView"

View File

@ -7,5 +7,49 @@
android:title="@string/menuSearch"
app:actionViewClass="androidx.appcompat.widget.SearchView"
app:showAsAction="always" />
<item
android:id="@+id/dexopt_all"
android:title="@string/dexopt_now"
app:showAsAction="never" />
<item
android:id="@+id/speed_all"
android:title="@string/speed_now"
app:showAsAction="never" />
<item
android:title="@string/reboot"
app:showAsAction="never">
<menu>
<item
android:id="@+id/soft_reboot"
android:title="@string/soft_reboot"
app:showAsAction="never" />
<item
android:id="@+id/reboot"
android:title="@string/reboot_system"
app:showAsAction="never" />
<item
android:id="@+id/reboot_recovery"
android:title="@string/reboot_recovery"
app:showAsAction="never" />
<item
android:id="@+id/reboot_bootloader"
android:title="@string/reboot_bootloader"
app:showAsAction="never" />
<item
android:id="@+id/reboot_download"
android:title="@string/reboot_download"
app:showAsAction="never" />
<item
android:id="@+id/reboot_edl"
android:title="@string/reboot_edl"
app:showAsAction="never" />
</menu>
</item>
</menu>

View File

@ -2,48 +2,51 @@
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<group>
<item
android:id="@+id/dexopt_all"
android:title="@string/dexopt_now"
app:showAsAction="never" />
<item
android:id="@+id/dexopt_all"
android:title="@string/dexopt_now"
app:showAsAction="never" />
<item
android:id="@+id/speed_all"
android:title="@string/speed_now"
app:showAsAction="never" />
<item
android:id="@+id/speed_all"
android:title="@string/speed_now"
app:showAsAction="never" />
<item
android:title="@string/reboot"
app:showAsAction="never">
<menu>
<item
android:id="@+id/soft_reboot"
android:title="@string/soft_reboot"
app:showAsAction="never" />
<item
android:id="@+id/soft_reboot"
android:title="@string/soft_reboot"
app:showAsAction="never" />
<item
android:id="@+id/reboot"
android:title="@string/reboot_system"
app:showAsAction="never" />
<item
android:id="@+id/reboot"
android:title="@string/reboot_system"
app:showAsAction="never" />
<item
android:id="@+id/reboot_recovery"
android:title="@string/reboot_recovery"
app:showAsAction="never" />
<item
android:id="@+id/reboot_recovery"
android:title="@string/reboot_recovery"
app:showAsAction="never" />
<item
android:id="@+id/reboot_bootloader"
android:title="@string/reboot_bootloader"
app:showAsAction="never" />
<item
android:id="@+id/reboot_bootloader"
android:title="@string/reboot_bootloader"
app:showAsAction="never" />
<item
android:id="@+id/reboot_download"
android:title="@string/reboot_download"
app:showAsAction="never" />
<item
android:id="@+id/reboot_download"
android:title="@string/reboot_download"
app:showAsAction="never" />
<item
android:id="@+id/reboot_edl"
android:title="@string/reboot_edl"
app:showAsAction="never" />
</menu>
</item>
<item
android:id="@+id/reboot_edl"
android:title="@string/reboot_edl"
app:showAsAction="never" />
</group>
</menu>

View File

@ -296,4 +296,5 @@
<string name="install_timestamps">安装于 %1$s \u00b7 更新于 %2$s</string>
<string name="pref_compat_mode_summary">兼容模式应用列表</string>
<string name="pref_compat_mode_title">兼容模式</string>
<string name="xposed_not_active">EdXposed 框架未安装或未激活, 您可在设置中关闭状态检查</string>
</resources>

View File

@ -296,4 +296,5 @@
<string name="install_timestamps">安裝於 %1$s \u00b7 更新於 %2$s</string>
<string name="pref_compat_mode_summary">兼容模式應用列表</string>
<string name="pref_compat_mode_title">兼容模式</string>
<string name="xposed_not_active">EdXposed 框架未安裝或未啟用, 您可在設定中關閉狀態檢查</string>
</resources>

View File

@ -296,4 +296,5 @@
<string name="install_timestamps">安裝於 %1$s \u00b7 更新於 %2$s</string>
<string name="pref_compat_mode_summary">相容模式應用列表</string>
<string name="pref_compat_mode_title">相容模式</string>
<string name="xposed_not_active">EdXposed 框架未安裝或未啟用, 您可在設定中關閉狀態檢查</string>
</resources>

View File

@ -330,4 +330,5 @@
<string name="follow_system">Follow system</string>
<string name="pref_compat_mode_title">Compat List</string>
<string name="pref_compat_mode_summary">Compat mode app list</string>
<string name="xposed_not_active">EdXposed Framework is not currently installed or active\nYou can close status check in settings</string>
</resources>