").append(
context.getString(
R.string.home_root_description_sui,
"Sui",
"Sui"
)
)
}
binding.text1.text = sb.toHtml(HtmlCompat.FROM_HTML_OPTION_TRIM_WHITESPACE)
}
override fun onRecycle() {
super.onRecycle()
alertDialog = null
}
}
================================================
FILE: manager/src/main/java/moe/shizuku/manager/home/StartWirelessAdbViewHolder.kt
================================================
package moe.shizuku.manager.home
import android.content.Context
import android.content.Intent
import android.os.Build
import android.os.SystemProperties
import android.text.method.LinkMovementMethod
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.annotation.RequiresApi
import androidx.core.view.isVisible
import androidx.fragment.app.FragmentActivity
import moe.shizuku.manager.Helps
import moe.shizuku.manager.R
import moe.shizuku.manager.adb.AdbPairingTutorialActivity
import moe.shizuku.manager.databinding.HomeItemContainerBinding
import moe.shizuku.manager.databinding.HomeStartWirelessAdbBinding
import moe.shizuku.manager.ktx.toHtml
import moe.shizuku.manager.starter.StarterActivity
import moe.shizuku.manager.utils.CustomTabsHelper
import moe.shizuku.manager.utils.EnvironmentUtils
import rikka.core.content.asActivity
import rikka.html.text.HtmlCompat
import rikka.recyclerview.BaseViewHolder
import rikka.recyclerview.BaseViewHolder.Creator
import java.net.Inet4Address
class StartWirelessAdbViewHolder(binding: HomeStartWirelessAdbBinding, root: View) :
BaseViewHolder
* Each entry must have a corresponding index in
* {@link #setEntryValues(int[])}.
*
* @param entries The entries.
* @see #setEntryValues(int[])
*/
public void setEntries(CharSequence[] entries) {
mEntries = entries;
mPopupWindow.requestMeasure();
}
/**
* @param entriesResId The entries array as a resource.
* @see #setEntries(CharSequence[])
*/
public void setEntries(@ArrayRes int entriesResId) {
setEntries(getContext().getResources().getTextArray(entriesResId));
}
/**
* The list of entries to be shown in the list in subsequent dialogs.
*
* @return The list as an array.
*/
public CharSequence[] getEntries() {
return mEntries;
}
/**
* The array to find the value to save for a preference when an entry from
* entries is selected. If a user clicks on the second item in entries, the
* second item in this array will be saved to the preference.
*
* @param entryValues The array to be used as values to save for the preference.
*/
public void setEntryValues(int[] entryValues) {
mEntryValues = entryValues;
}
/**
* @param entryValuesResId The entry values array as a resource.
* @see #setEntryValues(int[])
*/
public void setEntryValues(@ArrayRes int entryValuesResId) {
setEntryValues(getContext().getResources().getIntArray(entryValuesResId));
}
/**
* Returns the array of values to be saved for the preference.
*
* @return The array of values.
*/
public int[] getEntryValues() {
return mEntryValues;
}
/**
* Sets the value of the key. This should be one of the entries in
* {@link #getEntryValues()}.
*
* @param value The value to set for the key.
*/
public void setValue(int value) {
// Always persist/notify the first time.
final boolean changed = mValue != value;
if (changed || !mValueSet) {
mValue = value;
mValueSet = true;
persistInt(value);
if (changed) {
notifyChanged();
}
}
}
/**
* Returns the summary of this ListPreference. If the summary
* has a {@linkplain java.lang.String#format String formatting}
* marker in it (i.e. "%s" or "%1$s"), then the current entry
* value will be substituted in its place.
*
* @return the summary with appropriate string substitution
*/
@Override
public CharSequence getSummary() {
final CharSequence entry = getEntry();
if (mSummary == null) {
return super.getSummary();
} else {
return String.format(mSummary, entry == null ? "" : entry);
}
}
/**
* Sets the summary for this Preference with a CharSequence.
* If the summary has a
* {@linkplain java.lang.String#format String formatting}
* marker in it (i.e. "%s" or "%1$s"), then the current entry
* value will be substituted in its place when it's retrieved.
*
* @param summary The summary for the preference.
*/
@Override
public void setSummary(CharSequence summary) {
super.setSummary(summary);
if (summary == null && mSummary != null) {
mSummary = null;
} else if (summary != null && !summary.equals(mSummary)) {
mSummary = summary.toString();
}
}
/**
* Sets the value to the given index from the entry values.
*
* @param index The index of the value to set.
*/
public void setValueIndex(int index) {
if (mEntryValues != null) {
setValue(mEntryValues[index]);
}
}
/**
* Returns the value of the key. This should be one of the entries in
* {@link #getEntryValues()}.
*
* @return The value of the key.
*/
public int getValue() {
return mValue;
}
/**
* Returns the entry corresponding to the current value.
*
* @return The entry corresponding to the current value, or null.
*/
public CharSequence getEntry() {
int index = getValueIndex();
return index >= 0 && mEntries != null ? mEntries[index] : null;
}
/**
* Returns the index of the given value (in the entry values array).
*
* @param value The value whose index should be returned.
* @return The index of the value, or -1 if not found.
*/
public int findIndexOfValue(int value) {
if (mEntryValues != null) {
for (int i = mEntryValues.length - 1; i >= 0; i--) {
int entryValue = mEntryValues[i];
if (entryValue == value) {
return i;
}
}
}
return -1;
}
private int getValueIndex() {
return findIndexOfValue(mValue);
}
@Override
protected Object onGetDefaultValue(TypedArray a, int index) {
return a.getInt(index, 1);
}
@Override
protected void onSetInitialValue(Object defaultValue) {
if (defaultValue == null) {
defaultValue = 0;
}
setValue(getPersistedInt((Integer) defaultValue));
}
@Override
protected Parcelable onSaveInstanceState() {
final Parcelable superState = super.onSaveInstanceState();
if (isPersistent()) {
// No need to save instance state since it's persistent
return superState;
}
final SavedState myState = new SavedState(superState);
myState.value = getValue();
return myState;
}
@Override
protected void onRestoreInstanceState(Parcelable state) {
if (state == null || !state.getClass().equals(SavedState.class)) {
// Didn't save state for us in onSaveInstanceState
super.onRestoreInstanceState(state);
return;
}
SavedState myState = (SavedState) state;
super.onRestoreInstanceState(myState.getSuperState());
setValue(myState.value);
}
private static class SavedState extends BaseSavedState {
int value;
public SavedState(Parcel source) {
super(source);
value = source.readInt();
}
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
super.writeToParcel(dest, flags);
dest.writeInt(value);
}
public SavedState(Parcelable superState) {
super(superState);
}
public static final Parcelable.Creator Legacy Shizuku has been deprecated since March 2019.]]> Shizuku قدیمی از مارس ۲۰۱۹ منسوخ شده است.]]> Az örökölt Shizuku 2019 márciusa óta elavult.]]> 旧式 Shizuku 已于 2019 年 3 月被弃用。]]>
$localizedLocaleName".toHtml()
} else {
localizedLocaleName
}
)
}
languagePreference.entries = localizedLocales.toTypedArray()
languagePreference.summary = when {
TextUtils.isEmpty(currentLocaleTag) || "SYSTEM" == currentLocaleTag -> {
getString(R.string.follow_system)
}
currentLocaleIndex != -1 -> {
val localizedLocale = localizedLocales[currentLocaleIndex]
val newLineIndex = localizedLocale.indexOf('\n')
if (newLineIndex == -1) {
localizedLocale.toString()
} else {
localizedLocale.subSequence(0, newLineIndex).toString()
}
}
else -> {
""
}
}
}
}
================================================
FILE: manager/src/main/java/moe/shizuku/manager/shell/Shell.java
================================================
package moe.shizuku.manager.shell;
import android.content.pm.PackageManager;
import android.os.Handler;
import android.os.IBinder;
import rikka.rish.Rish;
import rikka.rish.RishConfig;
import rikka.shizuku.Shizuku;
import rikka.shizuku.ShizukuApiConstants;
public class Shell extends Rish {
@Override
public void requestPermission(Runnable onGrantedRunnable) {
if (Shizuku.checkSelfPermission() == PackageManager.PERMISSION_GRANTED) {
onGrantedRunnable.run();
} else if (Shizuku.shouldShowRequestPermissionRationale()) {
System.err.println("Permission denied");
System.err.flush();
System.exit(1);
} else {
Shizuku.addRequestPermissionResultListener(new Shizuku.OnRequestPermissionResultListener() {
@Override
public void onRequestPermissionResult(int requestCode, int grantResult) {
Shizuku.removeRequestPermissionResultListener(this);
if (grantResult == PackageManager.PERMISSION_GRANTED) {
onGrantedRunnable.run();
} else {
System.err.println("Permission denied");
System.err.flush();
System.exit(1);
}
}
});
Shizuku.requestPermission(0);
}
}
public static void main(String[] args, String packageName, IBinder binder, Handler handler) {
RishConfig.init(binder, ShizukuApiConstants.BINDER_DESCRIPTOR, 30000);
Shizuku.onBinderReceived(binder, packageName);
Shizuku.addBinderReceivedListenerSticky(() -> {
int version = Shizuku.getVersion();
if (version < 12) {
System.err.println("Rish requires server 12 (running " + version + ")");
System.err.flush();
System.exit(1);
}
new Shell().start(args);
});
}
}
================================================
FILE: manager/src/main/java/moe/shizuku/manager/shell/ShellBinderRequestHandler.kt
================================================
package moe.shizuku.manager.shell
import android.content.Context
import android.content.Intent
import android.os.IBinder
import android.os.Parcel
import moe.shizuku.manager.utils.Logger.LOGGER
import rikka.shizuku.Shizuku
object ShellBinderRequestHandler {
fun handleRequest(context: Context, intent: Intent): Boolean {
if (intent.action != "rikka.shizuku.intent.action.REQUEST_BINDER") {
return false
}
val binder = intent.getBundleExtra("data")?.getBinder("binder") ?: return false
val shizukuBinder = Shizuku.getBinder()
if (shizukuBinder == null) {
LOGGER.w("Binder not received or Shizuku service not running")
}
val data = Parcel.obtain()
return try {
data.writeStrongBinder(shizukuBinder)
data.writeString(context.applicationInfo.sourceDir)
binder.transact(1, data, null, IBinder.FLAG_ONEWAY)
true
} catch (e: Throwable) {
e.printStackTrace()
false
} finally {
data.recycle()
}
}
}
================================================
FILE: manager/src/main/java/moe/shizuku/manager/shell/ShellTutorialActivity.kt
================================================
package moe.shizuku.manager.shell
import android.net.Uri
import android.os.Bundle
import android.provider.DocumentsContract
import android.view.View
import androidx.activity.result.contract.ActivityResultContracts
import moe.shizuku.manager.Helps
import moe.shizuku.manager.R
import moe.shizuku.manager.app.AppBarActivity
import moe.shizuku.manager.databinding.TerminalTutorialActivityBinding
import moe.shizuku.manager.ktx.toHtml
import moe.shizuku.manager.utils.CustomTabsHelper
import rikka.html.text.HtmlCompat
import rikka.insets.*
import kotlin.math.roundToInt
class ShellTutorialActivity : AppBarActivity() {
companion object {
private val SH_NAME = "rish"
private val DEX_NAME = "rish_shizuku.dex"
}
private val openDocumentsTree =
registerForActivityResult(ActivityResultContracts.OpenDocumentTree()) { tree: Uri? ->
if (tree == null) return@registerForActivityResult
val cr = contentResolver
val doc = DocumentsContract.buildDocumentUriUsingTree(tree, DocumentsContract.getTreeDocumentId(tree))
val child =
DocumentsContract.buildChildDocumentsUriUsingTree(tree, DocumentsContract.getTreeDocumentId(tree))
cr.query(
child,
arrayOf(DocumentsContract.Document.COLUMN_DOCUMENT_ID, DocumentsContract.Document.COLUMN_DISPLAY_NAME),
null,
null,
null
)?.use {
while (it.moveToNext()) {
val id = it.getString(0)
val name = it.getString(1)
if (name == SH_NAME || name == DEX_NAME) {
DocumentsContract.deleteDocument(cr, DocumentsContract.buildDocumentUriUsingTree(tree, id))
}
}
}
fun writeToDocument(name: String) {
DocumentsContract.createDocument(contentResolver, doc, "application/octet-stream", name)?.runCatching {
cr.openOutputStream(this)?.let { assets.open(name).copyTo(it) }
}
}
writeToDocument(SH_NAME)
writeToDocument(DEX_NAME)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val binding = TerminalTutorialActivityBinding.inflate(layoutInflater)
setContentView(binding.root)
binding.content.apply {
setInitialPadding(
initialPaddingLeft,
initialPaddingTop + (resources.displayMetrics.density * 8).roundToInt(),
initialPaddingRight,
initialPaddingBottom
)
}
supportActionBar?.setDisplayHomeAsUpEnabled(true)
binding.apply {
val shName = "$SH_NAME"
val dexName = "$DEX_NAME"
summary.text =
getString(R.string.rish_description, shName).toHtml(HtmlCompat.FROM_HTML_OPTION_TRIM_WHITESPACE)
text1.text = getString(R.string.terminal_tutorial_1, shName, dexName).toHtml()
text2.text = getString(R.string.terminal_tutorial_2, shName).toHtml()
summary2.text = getString(
R.string.terminal_tutorial_2_description,
"Termux",
"PKG",
"com.termux",
"com.termux",
).toHtml()
text3.text = getString(
R.string.terminal_tutorial_3,
"sh $SH_NAME",
).toHtml()
summary3.text = getString(
R.string.terminal_tutorial_3_description,
shName, "PATH"
).toHtml()
button1.setOnClickListener { openDocumentsTree.launch(null) }
button2.setOnClickListener { v: View -> CustomTabsHelper.launchUrlOrCopy(v.context, Helps.RISH.get()) }
}
}
}
================================================
FILE: manager/src/main/java/moe/shizuku/manager/starter/Starter.kt
================================================
package moe.shizuku.manager.starter
import moe.shizuku.manager.application
import java.io.File
object Starter {
private val starterFile = File(application.applicationInfo.nativeLibraryDir, "libshizuku.so")
val userCommand: String = starterFile.absolutePath
val adbCommand = "adb shell $userCommand"
val internalCommand = "$userCommand --apk=${application.applicationInfo.sourceDir}"
}
================================================
FILE: manager/src/main/java/moe/shizuku/manager/starter/StarterActivity.kt
================================================
package moe.shizuku.manager.starter
import android.content.Context
import android.os.Bundle
import android.util.Log
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.topjohnwu.superuser.CallbackList
import com.topjohnwu.superuser.Shell
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import moe.shizuku.manager.AppConstants.EXTRA
import moe.shizuku.manager.R
import moe.shizuku.manager.ShizukuSettings
import moe.shizuku.manager.adb.AdbClient
import moe.shizuku.manager.adb.AdbKey
import moe.shizuku.manager.adb.AdbKeyException
import moe.shizuku.manager.adb.PreferenceAdbKeyStore
import moe.shizuku.manager.app.AppBarActivity
import moe.shizuku.manager.databinding.StarterActivityBinding
import rikka.lifecycle.Resource
import rikka.lifecycle.Status
import rikka.lifecycle.viewModels
import rikka.shizuku.Shizuku
import java.net.ConnectException
import javax.net.ssl.SSLProtocolException
private class NotRootedException : Exception()
class StarterActivity : AppBarActivity() {
private val viewModel by viewModels {
ViewModel(
this,
intent.getBooleanExtra(EXTRA_IS_ROOT, true),
intent.getStringExtra(EXTRA_HOST),
intent.getIntExtra(EXTRA_PORT, 0)
)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
supportActionBar?.setDisplayHomeAsUpEnabled(true)
supportActionBar?.setHomeAsUpIndicator(R.drawable.ic_close_24)
val binding = StarterActivityBinding.inflate(layoutInflater)
setContentView(binding.root)
viewModel.output.observe(this) {
val output = it.data!!.trim()
if (output.endsWith("info: shizuku_starter exit with 0")) {
viewModel.appendOutput("")
viewModel.appendOutput("Waiting for service...")
Shizuku.addBinderReceivedListener(object : Shizuku.OnBinderReceivedListener {
override fun onBinderReceived() {
Shizuku.removeBinderReceivedListener(this)
viewModel.appendOutput("Service started, this window will be automatically closed in 3 seconds")
window?.decorView?.postDelayed({
if (!isFinishing) finish()
}, 3000)
}
})
} else if (it.status == Status.ERROR) {
var message = 0
when (it.error) {
is AdbKeyException -> {
message = R.string.adb_error_key_store
}
is NotRootedException -> {
message = R.string.start_with_root_failed
}
is ConnectException -> {
message = R.string.cannot_connect_port
}
is SSLProtocolException -> {
message = R.string.adb_pair_required
}
}
if (message != 0) {
MaterialAlertDialogBuilder(this)
.setMessage(message)
.setPositiveButton(android.R.string.ok, null)
.show()
}
}
binding.text1.text = output
}
}
companion object {
const val EXTRA_IS_ROOT = "$EXTRA.IS_ROOT"
const val EXTRA_HOST = "$EXTRA.HOST"
const val EXTRA_PORT = "$EXTRA.PORT"
}
}
private class ViewModel(context: Context, root: Boolean, host: String?, port: Int) : androidx.lifecycle.ViewModel() {
private val sb = StringBuilder()
private val _output = MutableLiveData
* There are some other considerations, please confirm that you have read the help first.]]>
* ملاحظات دیگری نیز وجود دارد، لطفاً تأیید کنید که ابتدا راهنما را خوانده اید.]]>
* Є деякі інші міркування, будь ласка, підтвердьте, що ви прочитали довідку спочатку.]]>
* 还有一些其他注意事项,请确认你已阅读帮助。]]>