feat(android): add libshell and list devices
This commit is contained in:
parent
5105b5ea82
commit
d531112075
|
@ -37,6 +37,7 @@ dependencies {
|
||||||
annotationProcessor 'com.jakewharton:butterknife-compiler:10.0.0'
|
annotationProcessor 'com.jakewharton:butterknife-compiler:10.0.0'
|
||||||
|
|
||||||
implementation project(':libmynteye')
|
implementation project(':libmynteye')
|
||||||
|
implementation project(':libshell')
|
||||||
|
|
||||||
testImplementation 'junit:junit:4.12'
|
testImplementation 'junit:junit:4.12'
|
||||||
androidTestImplementation 'androidx.test:runner:1.1.1'
|
androidTestImplementation 'androidx.test:runner:1.1.1'
|
||||||
|
|
|
@ -3,6 +3,10 @@
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
package="com.slightech.mynteye.demo">
|
package="com.slightech.mynteye.demo">
|
||||||
|
|
||||||
|
<uses-permission android:name="android.permission.CAMERA" />
|
||||||
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
|
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||||
|
|
||||||
<application
|
<application
|
||||||
android:allowBackup="false"
|
android:allowBackup="false"
|
||||||
android:label="@string/app_name"
|
android:label="@string/app_name"
|
||||||
|
@ -10,7 +14,7 @@
|
||||||
android:supportsRtl="true"
|
android:supportsRtl="true"
|
||||||
android:theme="@style/AppTheme"
|
android:theme="@style/AppTheme"
|
||||||
tools:ignore="GoogleAppIndexingWarning">
|
tools:ignore="GoogleAppIndexingWarning">
|
||||||
<activity android:name=".MainActivity">
|
<activity android:name=".ui.MainActivity">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.MAIN"/>
|
<action android:name="android.intent.action.MAIN"/>
|
||||||
<category android:name="android.intent.category.LAUNCHER"/>
|
<category android:name="android.intent.category.LAUNCHER"/>
|
||||||
|
|
|
@ -1,20 +0,0 @@
|
||||||
package com.slightech.mynteye.demo;
|
|
||||||
|
|
||||||
import androidx.appcompat.app.AppCompatActivity;
|
|
||||||
import android.os.Bundle;
|
|
||||||
import com.slightech.mynteye.Device;
|
|
||||||
import com.slightech.mynteye.DeviceUsbInfo;
|
|
||||||
import timber.log.Timber;
|
|
||||||
|
|
||||||
public class MainActivity extends AppCompatActivity {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
|
||||||
super.onCreate(savedInstanceState);
|
|
||||||
setContentView(R.layout.activity_main);
|
|
||||||
|
|
||||||
for (DeviceUsbInfo info : Device.query()) {
|
|
||||||
Timber.i(info.toString());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,63 @@
|
||||||
|
package com.slightech.mynteye.demo.ui;
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint;
|
||||||
|
import android.content.pm.PackageManager;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.widget.Toast;
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.appcompat.app.AppCompatActivity;
|
||||||
|
import androidx.core.app.ActivityCompat;
|
||||||
|
import androidx.core.content.ContextCompat;
|
||||||
|
|
||||||
|
import static android.Manifest.permission.CAMERA;
|
||||||
|
import static android.Manifest.permission.WRITE_EXTERNAL_STORAGE;
|
||||||
|
|
||||||
|
@SuppressLint("Registered")
|
||||||
|
public class BaseActivity extends AppCompatActivity {
|
||||||
|
|
||||||
|
private final int REQ_PERMISSIONS = 1;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
requestPermissions();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void requestPermissions() {
|
||||||
|
final String[] permissions = new String[]{WRITE_EXTERNAL_STORAGE, CAMERA};
|
||||||
|
|
||||||
|
boolean granted = true;
|
||||||
|
for (String permission : permissions) {
|
||||||
|
if (ContextCompat.checkSelfPermission(this, permission)
|
||||||
|
!= PackageManager.PERMISSION_GRANTED) {
|
||||||
|
granted = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (granted) return;
|
||||||
|
|
||||||
|
ActivityCompat.requestPermissions(this, permissions, REQ_PERMISSIONS);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
|
||||||
|
@NonNull int[] grantResults) {
|
||||||
|
if (requestCode == REQ_PERMISSIONS) {
|
||||||
|
boolean granted = true;
|
||||||
|
if (grantResults.length < 1) {
|
||||||
|
granted = false;
|
||||||
|
} else {
|
||||||
|
for (int result : grantResults) {
|
||||||
|
if (result != PackageManager.PERMISSION_GRANTED) {
|
||||||
|
granted = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!granted) {
|
||||||
|
Toast.makeText(this, "Permission denied :(", Toast.LENGTH_LONG).show();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,105 @@
|
||||||
|
package com.slightech.mynteye.demo.ui;
|
||||||
|
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.view.Menu;
|
||||||
|
import android.view.MenuItem;
|
||||||
|
import android.widget.ArrayAdapter;
|
||||||
|
import android.widget.ListView;
|
||||||
|
import android.widget.Toast;
|
||||||
|
import androidx.appcompat.app.AlertDialog;
|
||||||
|
import butterknife.ButterKnife;
|
||||||
|
import com.slightech.mynteye.Device;
|
||||||
|
import com.slightech.mynteye.DeviceUsbInfo;
|
||||||
|
import com.slightech.mynteye.demo.R;
|
||||||
|
import com.slightech.mynteye.demo.util.RootUtils;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
|
public class MainActivity extends BaseActivity {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
setContentView(R.layout.activity_main);
|
||||||
|
ButterKnife.bind(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onCreateOptionsMenu(Menu menu) {
|
||||||
|
getMenuInflater().inflate(R.menu.menu_main, menu);
|
||||||
|
MenuItem item = menu.findItem(R.id.action_open);
|
||||||
|
if (item != null) {
|
||||||
|
item.setEnabled(false);
|
||||||
|
actionOpen(() -> item.setEnabled(true));
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onOptionsItemSelected(MenuItem item) {
|
||||||
|
switch (item.getItemId()) {
|
||||||
|
case R.id.action_open:
|
||||||
|
item.setEnabled(false);
|
||||||
|
actionOpen(() -> item.setEnabled(true));
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
|
return super.onOptionsItemSelected(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void actionOpen(final Runnable completeEvent) {
|
||||||
|
if (!RootUtils.isRooted()) {
|
||||||
|
alert("Warning", "Root denied :(");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
RootUtils.requestAccessible(ok -> {
|
||||||
|
if (completeEvent != null) completeEvent.run();
|
||||||
|
if (ok) {
|
||||||
|
toast("Root granted :)");
|
||||||
|
showDevices();
|
||||||
|
} else {
|
||||||
|
alert("Warning", "There are no devices accessible.");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void showDevices() {
|
||||||
|
ArrayList<DeviceUsbInfo> infos = Device.query();
|
||||||
|
if (infos.isEmpty()) {
|
||||||
|
alert("Warning", "There are no devices :(");
|
||||||
|
} else {
|
||||||
|
ArrayList<String> items = new ArrayList<>();
|
||||||
|
for (DeviceUsbInfo info : infos) {
|
||||||
|
items.add(String.format(Locale.getDefault(), "%d, %s, SN: %s",
|
||||||
|
info.getIndex(), info.getName(), info.getSn()));
|
||||||
|
}
|
||||||
|
|
||||||
|
AlertDialog dialog = new AlertDialog.Builder(this)
|
||||||
|
.setTitle("Devices")
|
||||||
|
.create();
|
||||||
|
ListView listView = new ListView(this);
|
||||||
|
listView.setAdapter(new ArrayAdapter<>(this, android.R.layout.simple_list_item_1, items));
|
||||||
|
listView.setOnItemClickListener((parent, view, position, id) -> {
|
||||||
|
dialog.dismiss();
|
||||||
|
openDevice(infos.get(position));
|
||||||
|
});
|
||||||
|
dialog.setView(listView);
|
||||||
|
dialog.show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void openDevice(DeviceUsbInfo info) {
|
||||||
|
}
|
||||||
|
|
||||||
|
private void toast(CharSequence text) {
|
||||||
|
Toast.makeText(this, text, Toast.LENGTH_LONG).show();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void alert(CharSequence title, CharSequence message) {
|
||||||
|
new AlertDialog.Builder(this)
|
||||||
|
.setTitle(title)
|
||||||
|
.setMessage(message)
|
||||||
|
.setPositiveButton(android.R.string.ok, null)
|
||||||
|
.show();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,76 @@
|
||||||
|
package com.slightech.mynteye.demo.util;
|
||||||
|
|
||||||
|
import com.stericson.RootShell.RootShell;
|
||||||
|
import com.stericson.RootShell.exceptions.RootDeniedException;
|
||||||
|
import com.stericson.RootShell.execution.Command;
|
||||||
|
import com.stericson.RootShell.execution.Shell;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.concurrent.TimeoutException;
|
||||||
|
import timber.log.Timber;
|
||||||
|
|
||||||
|
public final class RootUtils {
|
||||||
|
|
||||||
|
public interface OnRequestAccessibleListener {
|
||||||
|
void onRequestAccessible(boolean ok);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isRooted() {
|
||||||
|
if (!RootShell.isRootAvailable()) {
|
||||||
|
Timber.e("Root not found");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
RootShell.getShell(true);
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
return false;
|
||||||
|
} catch (TimeoutException e) {
|
||||||
|
Timber.e("TIMEOUT EXCEPTION!");
|
||||||
|
e.printStackTrace();
|
||||||
|
return false;
|
||||||
|
} catch (RootDeniedException e) {
|
||||||
|
Timber.e("ROOT DENIED EXCEPTION!");
|
||||||
|
e.printStackTrace();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (!RootShell.isAccessGiven()) {
|
||||||
|
Timber.e("ERROR: No root access to this device.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
Timber.e("ERROR: could not determine root access to this device.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void requestAccessible(OnRequestAccessibleListener l) {
|
||||||
|
try {
|
||||||
|
Shell sh = RootShell.getShell(true);
|
||||||
|
sh.add(new Command(1, "chmod 666 /dev/video*") {
|
||||||
|
@Override
|
||||||
|
public void commandOutput(int id, String line) {
|
||||||
|
Timber.d("commandOutput: %s", line);
|
||||||
|
super.commandOutput(id, line);
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public void commandTerminated(int id, String reason) {
|
||||||
|
Timber.d("commandTerminated: %s", reason);
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public void commandCompleted(int id, int exitcode) {
|
||||||
|
Timber.d("commandCompleted: %s", ((exitcode == 0) ? "ok" : "fail"));
|
||||||
|
if (l != null) l.onRequestAccessible(exitcode == 0);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
sh.close();
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -5,10 +5,11 @@
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
tools:context=".MainActivity"
|
tools:context=".ui.MainActivity"
|
||||||
>
|
>
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
|
android:id="@+id/text"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:text="Hello World!"
|
android:text="Hello World!"
|
||||||
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||||
|
<item
|
||||||
|
android:id="@+id/action_open"
|
||||||
|
android:orderInCategory="0"
|
||||||
|
android:title="@string/open"
|
||||||
|
app:showAsAction="ifRoom|withText" />
|
||||||
|
</menu>
|
|
@ -1,3 +1,5 @@
|
||||||
<resources>
|
<resources>
|
||||||
<string name="app_name">mynteye</string>
|
<string name="app_name">mynteye</string>
|
||||||
|
|
||||||
|
<string name="open">Open</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
#include "mynteye/device/context.h"
|
#include "mynteye/device/context.h"
|
||||||
#include "mynteye/device/device.h"
|
#include "mynteye/device/device.h"
|
||||||
|
#include "mynteye/logger.h"
|
||||||
|
|
||||||
#include "device_usb_info.hpp"
|
#include "device_usb_info.hpp"
|
||||||
#include "stream_request.hpp"
|
#include "stream_request.hpp"
|
||||||
|
@ -12,6 +13,7 @@ MYNTEYE_USE_NAMESPACE
|
||||||
namespace mynteye_jni {
|
namespace mynteye_jni {
|
||||||
|
|
||||||
std::vector<DeviceUsbInfo> Device::Query() {
|
std::vector<DeviceUsbInfo> Device::Query() {
|
||||||
|
VLOG(2) << __func__;
|
||||||
std::vector<DeviceUsbInfo> infos;
|
std::vector<DeviceUsbInfo> infos;
|
||||||
|
|
||||||
Context context;
|
Context context;
|
||||||
|
@ -27,6 +29,7 @@ std::vector<DeviceUsbInfo> Device::Query() {
|
||||||
}
|
}
|
||||||
|
|
||||||
std::shared_ptr<Device> Device::Create(const DeviceUsbInfo & info) {
|
std::shared_ptr<Device> Device::Create(const DeviceUsbInfo & info) {
|
||||||
|
VLOG(2) << __func__;
|
||||||
Context context;
|
Context context;
|
||||||
int32_t i = 0;
|
int32_t i = 0;
|
||||||
for (auto&& d : context.devices()) {
|
for (auto&& d : context.devices()) {
|
||||||
|
@ -39,12 +42,15 @@ std::shared_ptr<Device> Device::Create(const DeviceUsbInfo & info) {
|
||||||
}
|
}
|
||||||
|
|
||||||
DeviceImpl::DeviceImpl(const device_t & device) : Device(), device_(device) {
|
DeviceImpl::DeviceImpl(const device_t & device) : Device(), device_(device) {
|
||||||
|
VLOG(2) << __func__;
|
||||||
}
|
}
|
||||||
|
|
||||||
DeviceImpl::~DeviceImpl() {
|
DeviceImpl::~DeviceImpl() {
|
||||||
|
VLOG(2) << __func__;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<StreamRequest> DeviceImpl::GetStreamRequests() {
|
std::vector<StreamRequest> DeviceImpl::GetStreamRequests() {
|
||||||
|
VLOG(2) << __func__;
|
||||||
std::vector<StreamRequest> requests;
|
std::vector<StreamRequest> requests;
|
||||||
|
|
||||||
int32_t i = 0;
|
int32_t i = 0;
|
||||||
|
@ -58,6 +64,7 @@ std::vector<StreamRequest> DeviceImpl::GetStreamRequests() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void DeviceImpl::ConfigStreamRequest(const StreamRequest & request) {
|
void DeviceImpl::ConfigStreamRequest(const StreamRequest & request) {
|
||||||
|
VLOG(2) << __func__;
|
||||||
int32_t i = 0;
|
int32_t i = 0;
|
||||||
for (auto&& req : device_->GetStreamRequests()) {
|
for (auto&& req : device_->GetStreamRequests()) {
|
||||||
if (i == request.index) {
|
if (i == request.index) {
|
||||||
|
@ -69,9 +76,11 @@ void DeviceImpl::ConfigStreamRequest(const StreamRequest & request) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void DeviceImpl::Start() {
|
void DeviceImpl::Start() {
|
||||||
|
VLOG(2) << __func__;
|
||||||
}
|
}
|
||||||
|
|
||||||
void DeviceImpl::Stop() {
|
void DeviceImpl::Stop() {
|
||||||
|
VLOG(2) << __func__;
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace mynteye_jni
|
} // namespace mynteye_jni
|
||||||
|
|
1
wrappers/android/mynteye/libshell/.gitignore
vendored
Normal file
1
wrappers/android/mynteye/libshell/.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
/build
|
2
wrappers/android/mynteye/libshell/README.md
Normal file
2
wrappers/android/mynteye/libshell/README.md
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
|
||||||
|
* [RootShell](https://github.com/Stericson/RootShell)
|
29
wrappers/android/mynteye/libshell/build.gradle
Normal file
29
wrappers/android/mynteye/libshell/build.gradle
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
apply plugin: 'com.android.library'
|
||||||
|
|
||||||
|
android {
|
||||||
|
compileSdkVersion xversions.compileSdk
|
||||||
|
|
||||||
|
defaultConfig {
|
||||||
|
minSdkVersion xversions.minSdk
|
||||||
|
targetSdkVersion xversions.targetSdk
|
||||||
|
versionCode 1
|
||||||
|
versionName "1.0"
|
||||||
|
|
||||||
|
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
|
||||||
|
}
|
||||||
|
|
||||||
|
buildTypes {
|
||||||
|
release {
|
||||||
|
minifyEnabled false
|
||||||
|
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
||||||
|
|
||||||
|
testImplementation 'junit:junit:4.12'
|
||||||
|
androidTestImplementation 'androidx.test:runner:1.1.1'
|
||||||
|
androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1'
|
||||||
|
}
|
21
wrappers/android/mynteye/libshell/proguard-rules.pro
vendored
Normal file
21
wrappers/android/mynteye/libshell/proguard-rules.pro
vendored
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
# Add project specific ProGuard rules here.
|
||||||
|
# You can control the set of applied configuration files using the
|
||||||
|
# proguardFiles setting in build.gradle.
|
||||||
|
#
|
||||||
|
# For more details, see
|
||||||
|
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||||
|
|
||||||
|
# If your project uses WebView with JS, uncomment the following
|
||||||
|
# and specify the fully qualified class name to the JavaScript interface
|
||||||
|
# class:
|
||||||
|
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||||
|
# public *;
|
||||||
|
#}
|
||||||
|
|
||||||
|
# Uncomment this to preserve the line number information for
|
||||||
|
# debugging stack traces.
|
||||||
|
#-keepattributes SourceFile,LineNumberTable
|
||||||
|
|
||||||
|
# If you keep the line number information, uncomment this to
|
||||||
|
# hide the original source file name.
|
||||||
|
#-renamesourcefileattribute SourceFile
|
|
@ -0,0 +1,2 @@
|
||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
package="com.stericson.RootShell"/>
|
|
@ -0,0 +1,610 @@
|
||||||
|
/*
|
||||||
|
* This file is part of the RootShell Project: http://code.google.com/p/RootShell/
|
||||||
|
*
|
||||||
|
* Copyright (c) 2014 Stephen Erickson, Chris Ravenscroft
|
||||||
|
*
|
||||||
|
* This code is dual-licensed under the terms of the Apache License Version 2.0 and
|
||||||
|
* the terms of the General Public License (GPL) Version 2.
|
||||||
|
* You may use this code according to either of these licenses as is most appropriate
|
||||||
|
* for your project on a case-by-case basis.
|
||||||
|
*
|
||||||
|
* The terms of each license can be found in the root directory of this project's repository as well as at:
|
||||||
|
*
|
||||||
|
* * http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
* * http://www.gnu.org/licenses/gpl-2.0.txt
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under these Licenses is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See each License for the specific language governing permissions and
|
||||||
|
* limitations under that License.
|
||||||
|
*/
|
||||||
|
package com.stericson.RootShell;
|
||||||
|
|
||||||
|
|
||||||
|
import com.stericson.RootShell.exceptions.RootDeniedException;
|
||||||
|
import com.stericson.RootShell.execution.Command;
|
||||||
|
import com.stericson.RootShell.execution.Shell;
|
||||||
|
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.concurrent.TimeoutException;
|
||||||
|
|
||||||
|
public class RootShell {
|
||||||
|
|
||||||
|
// --------------------
|
||||||
|
// # Public Variables #
|
||||||
|
// --------------------
|
||||||
|
|
||||||
|
public static boolean debugMode = false;
|
||||||
|
|
||||||
|
public static final String version = "RootShell v1.6";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Setting this to false will disable the handler that is used
|
||||||
|
* by default for the 3 callback methods for Command.
|
||||||
|
* <p/>
|
||||||
|
* By disabling this all callbacks will be called from a thread other than
|
||||||
|
* the main UI thread.
|
||||||
|
*/
|
||||||
|
public static boolean handlerEnabled = true;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Setting this will change the default command timeout.
|
||||||
|
* <p/>
|
||||||
|
* The default is 20000ms
|
||||||
|
*/
|
||||||
|
public static int defaultCommandTimeout = 20000;
|
||||||
|
|
||||||
|
public static enum LogLevel {
|
||||||
|
VERBOSE,
|
||||||
|
ERROR,
|
||||||
|
DEBUG,
|
||||||
|
WARN
|
||||||
|
}
|
||||||
|
// --------------------
|
||||||
|
// # Public Methods #
|
||||||
|
// --------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This will close all open shells.
|
||||||
|
*/
|
||||||
|
public static void closeAllShells() throws IOException {
|
||||||
|
Shell.closeAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This will close the custom shell that you opened.
|
||||||
|
*/
|
||||||
|
public static void closeCustomShell() throws IOException {
|
||||||
|
Shell.closeCustomShell();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This will close either the root shell or the standard shell depending on what you specify.
|
||||||
|
*
|
||||||
|
* @param root a <code>boolean</code> to specify whether to close the root shell or the standard shell.
|
||||||
|
*/
|
||||||
|
public static void closeShell(boolean root) throws IOException {
|
||||||
|
if (root) {
|
||||||
|
Shell.closeRootShell();
|
||||||
|
} else {
|
||||||
|
Shell.closeShell();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use this to check whether or not a file exists on the filesystem.
|
||||||
|
*
|
||||||
|
* @param file String that represent the file, including the full path to the
|
||||||
|
* file and its name.
|
||||||
|
* @return a boolean that will indicate whether or not the file exists.
|
||||||
|
*/
|
||||||
|
public static boolean exists(final String file) {
|
||||||
|
return exists(file, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use this to check whether or not a file OR directory exists on the filesystem.
|
||||||
|
*
|
||||||
|
* @param file String that represent the file OR the directory, including the full path to the
|
||||||
|
* file and its name.
|
||||||
|
* @param isDir boolean that represent whether or not we are looking for a directory
|
||||||
|
* @return a boolean that will indicate whether or not the file exists.
|
||||||
|
*/
|
||||||
|
public static boolean exists(final String file, boolean isDir) {
|
||||||
|
final List<String> result = new ArrayList<String>();
|
||||||
|
|
||||||
|
String cmdToExecute = "ls " + (isDir ? "-d " : " ");
|
||||||
|
|
||||||
|
Command command = new Command(0, false, cmdToExecute + file) {
|
||||||
|
@Override
|
||||||
|
public void commandOutput(int id, String line) {
|
||||||
|
RootShell.log(line);
|
||||||
|
result.add(line);
|
||||||
|
|
||||||
|
super.commandOutput(id, line);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
//Try without root...
|
||||||
|
RootShell.getShell(false).add(command);
|
||||||
|
commandWait(RootShell.getShell(false), command);
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
RootShell.log("Exception: " + e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (String line : result) {
|
||||||
|
if (line.trim().equals(file)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
result.clear();
|
||||||
|
|
||||||
|
command = new Command(0, false, cmdToExecute + file) {
|
||||||
|
@Override
|
||||||
|
public void commandOutput(int id, String line) {
|
||||||
|
RootShell.log(line);
|
||||||
|
result.add(line);
|
||||||
|
|
||||||
|
super.commandOutput(id, line);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
RootShell.getShell(true).add(command);
|
||||||
|
commandWait(RootShell.getShell(true), command);
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
RootShell.log("Exception: " + e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
//Avoid concurrent modification...
|
||||||
|
List<String> final_result = new ArrayList<String>();
|
||||||
|
final_result.addAll(result);
|
||||||
|
|
||||||
|
for (String line : final_result) {
|
||||||
|
if (line.trim().equals(file)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param binaryName String that represent the binary to find.
|
||||||
|
* @param singlePath boolean that represents whether to return a single path or multiple.
|
||||||
|
*
|
||||||
|
* @return <code>List<String></code> containing the locations the binary was found at.
|
||||||
|
*/
|
||||||
|
public static List<String> findBinary(String binaryName, boolean singlePath) {
|
||||||
|
return findBinary(binaryName, null, singlePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param binaryName <code>String</code> that represent the binary to find.
|
||||||
|
* @param searchPaths <code>List<String></code> which contains the paths to search for this binary in.
|
||||||
|
* @param singlePath boolean that represents whether to return a single path or multiple.
|
||||||
|
*
|
||||||
|
* @return <code>List<String></code> containing the locations the binary was found at.
|
||||||
|
*/
|
||||||
|
public static List<String> findBinary(final String binaryName, List<String> searchPaths, boolean singlePath) {
|
||||||
|
|
||||||
|
final List<String> foundPaths = new ArrayList<String>();
|
||||||
|
|
||||||
|
boolean found = false;
|
||||||
|
|
||||||
|
if(searchPaths == null)
|
||||||
|
{
|
||||||
|
searchPaths = RootShell.getPath();
|
||||||
|
}
|
||||||
|
|
||||||
|
RootShell.log("Checking for " + binaryName);
|
||||||
|
|
||||||
|
//Try to use stat first
|
||||||
|
try {
|
||||||
|
for (String path : searchPaths) {
|
||||||
|
|
||||||
|
if(!path.endsWith("/"))
|
||||||
|
{
|
||||||
|
path += "/";
|
||||||
|
}
|
||||||
|
|
||||||
|
final String currentPath = path;
|
||||||
|
|
||||||
|
Command cc = new Command(0, false, "stat " + path + binaryName) {
|
||||||
|
@Override
|
||||||
|
public void commandOutput(int id, String line) {
|
||||||
|
if (line.contains("File: ") && line.contains(binaryName)) {
|
||||||
|
foundPaths.add(currentPath);
|
||||||
|
|
||||||
|
RootShell.log(binaryName + " was found here: " + currentPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
RootShell.log(line);
|
||||||
|
|
||||||
|
super.commandOutput(id, line);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
cc = RootShell.getShell(false).add(cc);
|
||||||
|
commandWait(RootShell.getShell(false), cc);
|
||||||
|
|
||||||
|
if(foundPaths.size() > 0 && singlePath) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
found = !foundPaths.isEmpty();
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
RootShell.log(binaryName + " was not found, more information MAY be available with Debugging on.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!found) {
|
||||||
|
RootShell.log("Trying second method");
|
||||||
|
|
||||||
|
for (String path : searchPaths) {
|
||||||
|
|
||||||
|
if(!path.endsWith("/"))
|
||||||
|
{
|
||||||
|
path += "/";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (RootShell.exists(path + binaryName)) {
|
||||||
|
RootShell.log(binaryName + " was found here: " + path);
|
||||||
|
foundPaths.add(path);
|
||||||
|
|
||||||
|
if(foundPaths.size() > 0 && singlePath) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
RootShell.log(binaryName + " was NOT found here: " + path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Collections.reverse(foundPaths);
|
||||||
|
|
||||||
|
return foundPaths;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This will open or return, if one is already open, a custom shell, you are responsible for managing the shell, reading the output
|
||||||
|
* and for closing the shell when you are done using it.
|
||||||
|
*
|
||||||
|
* @param shellPath a <code>String</code> to Indicate the path to the shell that you want to open.
|
||||||
|
* @param timeout an <code>int</code> to Indicate the length of time before giving up on opening a shell.
|
||||||
|
* @throws TimeoutException
|
||||||
|
* @throws com.stericson.RootShell.exceptions.RootDeniedException
|
||||||
|
* @throws IOException
|
||||||
|
*/
|
||||||
|
public static Shell getCustomShell(String shellPath, int timeout) throws IOException, TimeoutException, RootDeniedException
|
||||||
|
{
|
||||||
|
return Shell.startCustomShell(shellPath, timeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This will return the environment variable PATH
|
||||||
|
*
|
||||||
|
* @return <code>List<String></code> A List of Strings representing the environment variable $PATH
|
||||||
|
*/
|
||||||
|
public static List<String> getPath() {
|
||||||
|
return Arrays.asList(System.getenv("PATH").split(":"));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This will open or return, if one is already open, a shell, you are responsible for managing the shell, reading the output
|
||||||
|
* and for closing the shell when you are done using it.
|
||||||
|
*
|
||||||
|
* @param root a <code>boolean</code> to Indicate whether or not you want to open a root shell or a standard shell
|
||||||
|
* @param timeout an <code>int</code> to Indicate the length of time to wait before giving up on opening a shell.
|
||||||
|
* @param shellContext the context to execute the shell with
|
||||||
|
* @param retry a <code>int</code> to indicate how many times the ROOT shell should try to open with root priviliges...
|
||||||
|
*/
|
||||||
|
public static Shell getShell(boolean root, int timeout, Shell.ShellContext shellContext, int retry) throws IOException, TimeoutException, RootDeniedException {
|
||||||
|
if (root) {
|
||||||
|
return Shell.startRootShell(timeout, shellContext, retry);
|
||||||
|
} else {
|
||||||
|
return Shell.startShell(timeout);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This will open or return, if one is already open, a shell, you are responsible for managing the shell, reading the output
|
||||||
|
* and for closing the shell when you are done using it.
|
||||||
|
*
|
||||||
|
* @param root a <code>boolean</code> to Indicate whether or not you want to open a root shell or a standard shell
|
||||||
|
* @param timeout an <code>int</code> to Indicate the length of time to wait before giving up on opening a shell.
|
||||||
|
* @param shellContext the context to execute the shell with
|
||||||
|
*/
|
||||||
|
public static Shell getShell(boolean root, int timeout, Shell.ShellContext shellContext) throws IOException, TimeoutException, RootDeniedException {
|
||||||
|
return getShell(root, timeout, shellContext, 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This will open or return, if one is already open, a shell, you are responsible for managing the shell, reading the output
|
||||||
|
* and for closing the shell when you are done using it.
|
||||||
|
*
|
||||||
|
* @param root a <code>boolean</code> to Indicate whether or not you want to open a root shell or a standard shell
|
||||||
|
* @param shellContext the context to execute the shell with
|
||||||
|
*/
|
||||||
|
public static Shell getShell(boolean root, Shell.ShellContext shellContext) throws IOException, TimeoutException, RootDeniedException {
|
||||||
|
return getShell(root, 0, shellContext, 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This will open or return, if one is already open, a shell, you are responsible for managing the shell, reading the output
|
||||||
|
* and for closing the shell when you are done using it.
|
||||||
|
*
|
||||||
|
* @param root a <code>boolean</code> to Indicate whether or not you want to open a root shell or a standard shell
|
||||||
|
* @param timeout an <code>int</code> to Indicate the length of time to wait before giving up on opening a shell.
|
||||||
|
*/
|
||||||
|
public static Shell getShell(boolean root, int timeout) throws IOException, TimeoutException, RootDeniedException {
|
||||||
|
return getShell(root, timeout, Shell.defaultContext, 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This will open or return, if one is already open, a shell, you are responsible for managing the shell, reading the output
|
||||||
|
* and for closing the shell when you are done using it.
|
||||||
|
*
|
||||||
|
* @param root a <code>boolean</code> to Indicate whether or not you want to open a root shell or a standard shell
|
||||||
|
*/
|
||||||
|
public static Shell getShell(boolean root) throws IOException, TimeoutException, RootDeniedException {
|
||||||
|
return RootShell.getShell(root, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return <code>true</code> if your app has been given root access.
|
||||||
|
* @throws TimeoutException if this operation times out. (cannot determine if access is given)
|
||||||
|
*/
|
||||||
|
public static boolean isAccessGiven() {
|
||||||
|
return isAccessGiven(0, 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Control how many time of retries should request
|
||||||
|
*
|
||||||
|
* @param timeout The timeout
|
||||||
|
* @param retries The number of retries
|
||||||
|
*
|
||||||
|
* @return <code>true</code> if your app has been given root access.
|
||||||
|
* @throws TimeoutException if this operation times out. (cannot determine if access is given)
|
||||||
|
*/
|
||||||
|
public static boolean isAccessGiven(int timeout, int retries) {
|
||||||
|
final Set<String> ID = new HashSet<String>();
|
||||||
|
final int IAG = 158;
|
||||||
|
|
||||||
|
try {
|
||||||
|
RootShell.log("Checking for Root access");
|
||||||
|
|
||||||
|
Command command = new Command(IAG, false, "id") {
|
||||||
|
@Override
|
||||||
|
public void commandOutput(int id, String line) {
|
||||||
|
if (id == IAG) {
|
||||||
|
ID.addAll(Arrays.asList(line.split(" ")));
|
||||||
|
}
|
||||||
|
|
||||||
|
super.commandOutput(id, line);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Shell shell = Shell.startRootShell(timeout, retries);
|
||||||
|
shell.add(command);
|
||||||
|
commandWait(shell, command);
|
||||||
|
|
||||||
|
//parse the userid
|
||||||
|
for (String userid : ID) {
|
||||||
|
RootShell.log(userid);
|
||||||
|
|
||||||
|
if (userid.toLowerCase().contains("uid=0")) {
|
||||||
|
RootShell.log("Access Given");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return <code>true</code> if BusyBox was found.
|
||||||
|
*/
|
||||||
|
public static boolean isBusyboxAvailable()
|
||||||
|
{
|
||||||
|
return isBusyboxAvailable(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return <code>true</code> if BusyBox or Toybox was found.
|
||||||
|
*/
|
||||||
|
public static boolean isBusyboxAvailable(boolean includeToybox)
|
||||||
|
{
|
||||||
|
if(includeToybox) {
|
||||||
|
return (findBinary("busybox", true)).size() > 0 || (findBinary("toybox", true)).size() > 0;
|
||||||
|
} else {
|
||||||
|
return (findBinary("busybox", true)).size() > 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return <code>true</code> if su was found.
|
||||||
|
*/
|
||||||
|
public static boolean isRootAvailable() {
|
||||||
|
return (findBinary("su", true)).size() > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method allows you to output debug messages only when debugging is on. This will allow
|
||||||
|
* you to add a debug option to your app, which by default can be left off for performance.
|
||||||
|
* However, when you need debugging information, a simple switch can enable it and provide you
|
||||||
|
* with detailed logging.
|
||||||
|
* <p/>
|
||||||
|
* This method handles whether or not to log the information you pass it depending whether or
|
||||||
|
* not RootShell.debugMode is on. So you can use this and not have to worry about handling it
|
||||||
|
* yourself.
|
||||||
|
*
|
||||||
|
* @param msg The message to output.
|
||||||
|
*/
|
||||||
|
public static void log(String msg) {
|
||||||
|
log(null, msg, LogLevel.DEBUG, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method allows you to output debug messages only when debugging is on. This will allow
|
||||||
|
* you to add a debug option to your app, which by default can be left off for performance.
|
||||||
|
* However, when you need debugging information, a simple switch can enable it and provide you
|
||||||
|
* with detailed logging.
|
||||||
|
* <p/>
|
||||||
|
* This method handles whether or not to log the information you pass it depending whether or
|
||||||
|
* not RootShell.debugMode is on. So you can use this and not have to worry about handling it
|
||||||
|
* yourself.
|
||||||
|
*
|
||||||
|
* @param TAG Optional parameter to define the tag that the Log will use.
|
||||||
|
* @param msg The message to output.
|
||||||
|
*/
|
||||||
|
public static void log(String TAG, String msg) {
|
||||||
|
log(TAG, msg, LogLevel.DEBUG, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method allows you to output debug messages only when debugging is on. This will allow
|
||||||
|
* you to add a debug option to your app, which by default can be left off for performance.
|
||||||
|
* However, when you need debugging information, a simple switch can enable it and provide you
|
||||||
|
* with detailed logging.
|
||||||
|
* <p/>
|
||||||
|
* This method handles whether or not to log the information you pass it depending whether or
|
||||||
|
* not RootShell.debugMode is on. So you can use this and not have to worry about handling it
|
||||||
|
* yourself.
|
||||||
|
*
|
||||||
|
* @param msg The message to output.
|
||||||
|
* @param type The type of log, 1 for verbose, 2 for error, 3 for debug, 4 for warn
|
||||||
|
* @param e The exception that was thrown (Needed for errors)
|
||||||
|
*/
|
||||||
|
public static void log(String msg, LogLevel type, Exception e) {
|
||||||
|
log(null, msg, type, e);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method allows you to check whether logging is enabled.
|
||||||
|
* Yes, it has a goofy name, but that's to keep it as short as possible.
|
||||||
|
* After all writing logging calls should be painless.
|
||||||
|
* This method exists to save Android going through the various Java layers
|
||||||
|
* that are traversed any time a string is created (i.e. what you are logging)
|
||||||
|
* <p/>
|
||||||
|
* Example usage:
|
||||||
|
* if(islog) {
|
||||||
|
* StrinbBuilder sb = new StringBuilder();
|
||||||
|
* // ...
|
||||||
|
* // build string
|
||||||
|
* // ...
|
||||||
|
* log(sb.toString());
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* @return true if logging is enabled
|
||||||
|
*/
|
||||||
|
public static boolean islog() {
|
||||||
|
return debugMode;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method allows you to output debug messages only when debugging is on. This will allow
|
||||||
|
* you to add a debug option to your app, which by default can be left off for performance.
|
||||||
|
* However, when you need debugging information, a simple switch can enable it and provide you
|
||||||
|
* with detailed logging.
|
||||||
|
* <p/>
|
||||||
|
* This method handles whether or not to log the information you pass it depending whether or
|
||||||
|
* not RootShell.debugMode is on. So you can use this and not have to worry about handling it
|
||||||
|
* yourself.
|
||||||
|
*
|
||||||
|
* @param TAG Optional parameter to define the tag that the Log will use.
|
||||||
|
* @param msg The message to output.
|
||||||
|
* @param type The type of log, 1 for verbose, 2 for error, 3 for debug
|
||||||
|
* @param e The exception that was thrown (Needed for errors)
|
||||||
|
*/
|
||||||
|
public static void log(String TAG, String msg, LogLevel type, Exception e) {
|
||||||
|
if (msg != null && !msg.equals("")) {
|
||||||
|
if (debugMode) {
|
||||||
|
if (TAG == null) {
|
||||||
|
TAG = version;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (type) {
|
||||||
|
case VERBOSE:
|
||||||
|
Log.v(TAG, msg);
|
||||||
|
break;
|
||||||
|
case ERROR:
|
||||||
|
Log.e(TAG, msg, e);
|
||||||
|
break;
|
||||||
|
case DEBUG:
|
||||||
|
Log.d(TAG, msg);
|
||||||
|
break;
|
||||||
|
case WARN:
|
||||||
|
Log.w(TAG, msg);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --------------------
|
||||||
|
// # Public Methods #
|
||||||
|
// --------------------
|
||||||
|
|
||||||
|
private static void commandWait(Shell shell, Command cmd) throws Exception {
|
||||||
|
while (!cmd.isFinished()) {
|
||||||
|
|
||||||
|
RootShell.log(version, shell.getCommandQueuePositionString(cmd));
|
||||||
|
RootShell.log(version, "Processed " + cmd.totalOutputProcessed + " of " + cmd.totalOutput + " output from command.");
|
||||||
|
|
||||||
|
synchronized (cmd) {
|
||||||
|
try {
|
||||||
|
if (!cmd.isFinished()) {
|
||||||
|
cmd.wait(2000);
|
||||||
|
}
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!cmd.isExecuting() && !cmd.isFinished()) {
|
||||||
|
if (!shell.isExecuting && !shell.isReading) {
|
||||||
|
RootShell.log(version, "Waiting for a command to be executed in a shell that is not executing and not reading! \n\n Command: " + cmd.getCommand());
|
||||||
|
Exception e = new Exception();
|
||||||
|
e.setStackTrace(Thread.currentThread().getStackTrace());
|
||||||
|
e.printStackTrace();
|
||||||
|
} else if (shell.isExecuting && !shell.isReading) {
|
||||||
|
RootShell.log(version, "Waiting for a command to be executed in a shell that is executing but not reading! \n\n Command: " + cmd.getCommand());
|
||||||
|
Exception e = new Exception();
|
||||||
|
e.setStackTrace(Thread.currentThread().getStackTrace());
|
||||||
|
e.printStackTrace();
|
||||||
|
} else {
|
||||||
|
RootShell.log(version, "Waiting for a command to be executed in a shell that is not reading! \n\n Command: " + cmd.getCommand());
|
||||||
|
Exception e = new Exception();
|
||||||
|
e.setStackTrace(Thread.currentThread().getStackTrace());
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,348 @@
|
||||||
|
package com.stericson.RootShell.containers;
|
||||||
|
|
||||||
|
import java.io.BufferedReader;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileFilter;
|
||||||
|
import java.io.FileNotFoundException;
|
||||||
|
import java.io.FileReader;
|
||||||
|
import java.io.FilenameFilter;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.lang.reflect.Constructor;
|
||||||
|
import java.lang.reflect.InvocationTargetException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
/* #ANNOTATIONS @SupportedAnnotationTypes("com.stericson.RootShell.containers.RootClass.Candidate") */
|
||||||
|
/* #ANNOTATIONS @SupportedSourceVersion(SourceVersion.RELEASE_6) */
|
||||||
|
public class RootClass /* #ANNOTATIONS extends AbstractProcessor */ {
|
||||||
|
|
||||||
|
/* #ANNOTATIONS
|
||||||
|
@Override
|
||||||
|
public boolean process(Set<? extends TypeElement> typeElements, RoundEnvironment roundEnvironment) {
|
||||||
|
processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE, "I was invoked!!!");
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
static String PATH_TO_DX = "/Users/Chris/Projects/android-sdk-macosx/build-tools/18.0.1/dx";
|
||||||
|
|
||||||
|
enum READ_STATE {
|
||||||
|
STARTING, FOUND_ANNOTATION;
|
||||||
|
}
|
||||||
|
|
||||||
|
public RootClass(String[] args) throws ClassNotFoundException, NoSuchMethodException,
|
||||||
|
IllegalAccessException, InvocationTargetException, InstantiationException {
|
||||||
|
|
||||||
|
// Note: rather than calling System.load("/system/lib/libandroid_runtime.so");
|
||||||
|
// which would leave a bunch of unresolved JNI references,
|
||||||
|
// we are using the 'withFramework' class as a preloader.
|
||||||
|
// So, yeah, russian dolls: withFramework > RootClass > actual method
|
||||||
|
|
||||||
|
String className = args[0];
|
||||||
|
RootArgs actualArgs = new RootArgs();
|
||||||
|
actualArgs.args = new String[args.length - 1];
|
||||||
|
System.arraycopy(args, 1, actualArgs.args, 0, args.length - 1);
|
||||||
|
Class<?> classHandler = Class.forName(className);
|
||||||
|
Constructor<?> classConstructor = classHandler.getConstructor(RootArgs.class);
|
||||||
|
classConstructor.newInstance(actualArgs);
|
||||||
|
}
|
||||||
|
|
||||||
|
public @interface Candidate {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
;
|
||||||
|
|
||||||
|
public class RootArgs {
|
||||||
|
|
||||||
|
public String args[];
|
||||||
|
}
|
||||||
|
|
||||||
|
static void displayError(Exception e) {
|
||||||
|
// Not using system.err to make it easier to capture from
|
||||||
|
// calling library.
|
||||||
|
System.out.println("##ERR##" + e.getMessage() + "##");
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
|
||||||
|
// I reckon it would be better to investigate classes using getAttribute()
|
||||||
|
// however this method allows the developer to simply select "Run" on RootClass
|
||||||
|
// and immediately re-generate the necessary jar file.
|
||||||
|
static public class AnnotationsFinder {
|
||||||
|
|
||||||
|
private final String AVOIDDIRPATH = "stericson" + File.separator + "RootShell" + File.separator;
|
||||||
|
|
||||||
|
private List<File> classFiles;
|
||||||
|
|
||||||
|
public AnnotationsFinder() throws IOException {
|
||||||
|
System.out.println("Discovering root class annotations...");
|
||||||
|
classFiles = new ArrayList<File>();
|
||||||
|
lookup(new File("src"), classFiles);
|
||||||
|
System.out.println("Done discovering annotations. Building jar file.");
|
||||||
|
File builtPath = getBuiltPath();
|
||||||
|
if (null != builtPath) {
|
||||||
|
// Android! Y U no have com.google.common.base.Joiner class?
|
||||||
|
String rc1 = "com" + File.separator
|
||||||
|
+ "stericson" + File.separator
|
||||||
|
+ "RootShell" + File.separator
|
||||||
|
+ "containers" + File.separator
|
||||||
|
+ "RootClass.class";
|
||||||
|
String rc2 = "com" + File.separator
|
||||||
|
+ "stericson" + File.separator
|
||||||
|
+ "RootShell" + File.separator
|
||||||
|
+ "containers" + File.separator
|
||||||
|
+ "RootClass$RootArgs.class";
|
||||||
|
String rc3 = "com" + File.separator
|
||||||
|
+ "stericson" + File.separator
|
||||||
|
+ "RootShell" + File.separator
|
||||||
|
+ "containers" + File.separator
|
||||||
|
+ "RootClass$AnnotationsFinder.class";
|
||||||
|
String rc4 = "com" + File.separator
|
||||||
|
+ "stericson" + File.separator
|
||||||
|
+ "RootShell" + File.separator
|
||||||
|
+ "containers" + File.separator
|
||||||
|
+ "RootClass$AnnotationsFinder$1.class";
|
||||||
|
String rc5 = "com" + File.separator
|
||||||
|
+ "stericson" + File.separator
|
||||||
|
+ "RootShell" + File.separator
|
||||||
|
+ "containers" + File.separator
|
||||||
|
+ "RootClass$AnnotationsFinder$2.class";
|
||||||
|
String[] cmd;
|
||||||
|
boolean onWindows = (-1 != System.getProperty("os.name").toLowerCase().indexOf("win"));
|
||||||
|
if (onWindows) {
|
||||||
|
StringBuilder sb = new StringBuilder(
|
||||||
|
" " + rc1 + " " + rc2 + " " + rc3 + " " + rc4 + " " + rc5
|
||||||
|
);
|
||||||
|
for (File file : classFiles) {
|
||||||
|
sb.append(" " + file.getPath());
|
||||||
|
}
|
||||||
|
cmd = new String[]{
|
||||||
|
"cmd", "/C",
|
||||||
|
"jar cvf" +
|
||||||
|
" anbuild.jar" +
|
||||||
|
sb.toString()
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
ArrayList<String> al = new ArrayList<String>();
|
||||||
|
al.add("jar");
|
||||||
|
al.add("cf");
|
||||||
|
al.add("anbuild.jar");
|
||||||
|
al.add(rc1);
|
||||||
|
al.add(rc2);
|
||||||
|
al.add(rc3);
|
||||||
|
al.add(rc4);
|
||||||
|
al.add(rc5);
|
||||||
|
for (File file : classFiles) {
|
||||||
|
al.add(file.getPath());
|
||||||
|
}
|
||||||
|
cmd = al.toArray(new String[al.size()]);
|
||||||
|
}
|
||||||
|
ProcessBuilder jarBuilder = new ProcessBuilder(cmd);
|
||||||
|
jarBuilder.directory(builtPath);
|
||||||
|
try {
|
||||||
|
jarBuilder.start().waitFor();
|
||||||
|
} catch (IOException e) {
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
}
|
||||||
|
|
||||||
|
String strRawFolder = "res" + File.separator + "raw";
|
||||||
|
if (builtPath.toString().startsWith("build")); //Check if running in AndroidStudio
|
||||||
|
strRawFolder = "src" + File.separator + "main" + File.separator + "res" + File.separator + "raw";
|
||||||
|
|
||||||
|
File rawFolder = new File(strRawFolder);
|
||||||
|
if (!rawFolder.exists()) {
|
||||||
|
rawFolder.mkdirs();
|
||||||
|
}
|
||||||
|
|
||||||
|
System.out.println("Done building jar file. Creating dex file.");
|
||||||
|
if (onWindows) {
|
||||||
|
cmd = new String[]{
|
||||||
|
"cmd", "/C",
|
||||||
|
"dx --dex --output=" + strRawFolder + File.separator + "anbuild.dex "
|
||||||
|
+ builtPath + File.separator + "anbuild.jar"
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
cmd = new String[]{
|
||||||
|
getPathToDx(),
|
||||||
|
"--dex",
|
||||||
|
"--output=" + strRawFolder + File.separator + "anbuild.dex",
|
||||||
|
builtPath + File.separator + "anbuild.jar"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
ProcessBuilder dexBuilder = new ProcessBuilder(cmd);
|
||||||
|
try {
|
||||||
|
dexBuilder.start().waitFor();
|
||||||
|
} catch (IOException e) {
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
System.out.println("All done. ::: anbuild.dex should now be in your project's src" + File.separator + "main" + File.separator + "res" + File.separator + "raw" + File.separator + " folder :::");
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void lookup(File path, List<File> fileList) {
|
||||||
|
String desourcedPath = path.toString().replace("src" + File.separator, "").replace("main" + File.separator + "java" + File.separator, "");
|
||||||
|
|
||||||
|
File[] files = path.listFiles(new FileFilter() {
|
||||||
|
@Override
|
||||||
|
public boolean accept(File file) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
for (File file : files) {
|
||||||
|
if (file.isDirectory()) {
|
||||||
|
if (-1 == file.getAbsolutePath().indexOf(AVOIDDIRPATH)) {
|
||||||
|
lookup(file, fileList);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (file.getName().endsWith(".java")) {
|
||||||
|
if (hasClassAnnotation(file)) {
|
||||||
|
final String fileNamePrefix = file.getName().replace(".java", "");
|
||||||
|
final File compiledPath = new File(getBuiltPath().toString() + File.separator + desourcedPath);
|
||||||
|
File[] classAndInnerClassFiles = compiledPath.listFiles(new FilenameFilter() {
|
||||||
|
@Override
|
||||||
|
public boolean accept(File dir, String filename) {
|
||||||
|
return filename.startsWith(fileNamePrefix);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
for (final File matchingFile : classAndInnerClassFiles) {
|
||||||
|
fileList.add(new File(desourcedPath + File.separator + matchingFile.getName()));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected boolean hasClassAnnotation(File file) {
|
||||||
|
READ_STATE readState = READ_STATE.STARTING;
|
||||||
|
Pattern p = Pattern.compile(" class ([A-Za-z0-9_]+)");
|
||||||
|
try {
|
||||||
|
BufferedReader reader = new BufferedReader(new FileReader(file));
|
||||||
|
String line;
|
||||||
|
while (null != (line = reader.readLine())) {
|
||||||
|
switch (readState) {
|
||||||
|
case STARTING:
|
||||||
|
if (-1 < line.indexOf("@RootClass.Candidate")) {
|
||||||
|
readState = READ_STATE.FOUND_ANNOTATION;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case FOUND_ANNOTATION:
|
||||||
|
Matcher m = p.matcher(line);
|
||||||
|
if (m.find()) {
|
||||||
|
System.out.println(" Found annotated class: " + m.group(0));
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
System.err.println("Error: unmatched annotation in " +
|
||||||
|
file.getAbsolutePath());
|
||||||
|
readState = READ_STATE.STARTING;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (FileNotFoundException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected String getPathToDx() throws IOException {
|
||||||
|
String androidHome = System.getenv("ANDROID_HOME");
|
||||||
|
if (null == androidHome) {
|
||||||
|
throw new IOException("Error: you need to set $ANDROID_HOME globally");
|
||||||
|
}
|
||||||
|
String dxPath = null;
|
||||||
|
File[] files = new File(androidHome + File.separator + "build-tools").listFiles();
|
||||||
|
int recentSdkVersion = 0;
|
||||||
|
for (File file : files) {
|
||||||
|
|
||||||
|
String fileName = null;
|
||||||
|
if (file.getName().contains("-")) {
|
||||||
|
String[] splitFileName = file.getName().split("-");
|
||||||
|
if (splitFileName[1].contains("W")) {
|
||||||
|
char[] fileNameChars = splitFileName[1].toCharArray();
|
||||||
|
fileName = String.valueOf(fileNameChars[0]);
|
||||||
|
} else if (splitFileName[1].contains("rc")) {
|
||||||
|
continue; //Do not use release candidates
|
||||||
|
} else {
|
||||||
|
fileName = splitFileName[1];
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
fileName = file.getName();
|
||||||
|
}
|
||||||
|
|
||||||
|
int sdkVersion;
|
||||||
|
|
||||||
|
String[] sdkVersionBits = fileName.split("[.]");
|
||||||
|
sdkVersion = Integer.parseInt(sdkVersionBits[0]) * 10000;
|
||||||
|
if (sdkVersionBits.length > 1) {
|
||||||
|
sdkVersion += Integer.parseInt(sdkVersionBits[1]) * 100;
|
||||||
|
if (sdkVersionBits.length > 2) {
|
||||||
|
sdkVersion += Integer.parseInt(sdkVersionBits[2]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (sdkVersion > recentSdkVersion) {
|
||||||
|
String tentativePath = file.getAbsolutePath() + File.separator + "dx";
|
||||||
|
if (new File(tentativePath).exists()) {
|
||||||
|
recentSdkVersion = sdkVersion;
|
||||||
|
dxPath = tentativePath;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (dxPath == null) {
|
||||||
|
throw new IOException("Error: unable to find dx binary in $ANDROID_HOME");
|
||||||
|
}
|
||||||
|
return dxPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected File getBuiltPath() {
|
||||||
|
File foundPath = null;
|
||||||
|
|
||||||
|
File ideaPath = new File("out" + File.separator + "production"); // IntelliJ
|
||||||
|
if (ideaPath.isDirectory()) {
|
||||||
|
File[] children = ideaPath.listFiles(new FileFilter() {
|
||||||
|
@Override
|
||||||
|
public boolean accept(File pathname) {
|
||||||
|
return pathname.isDirectory();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (children.length > 0) {
|
||||||
|
foundPath = new File(ideaPath.getAbsolutePath() + File.separator + children[0].getName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (null == foundPath) {
|
||||||
|
File eclipsePath = new File("bin" + File.separator + "classes"); // Eclipse IDE
|
||||||
|
if (eclipsePath.isDirectory()) {
|
||||||
|
foundPath = eclipsePath;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (null == foundPath) {
|
||||||
|
File androidStudioPath = new File("build" + File.separator + "intermediates" + File.separator + "classes" + File.separator + "debug"); // Android Studio
|
||||||
|
if (androidStudioPath.isDirectory()) {
|
||||||
|
foundPath = androidStudioPath;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return foundPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
try {
|
||||||
|
if (args.length == 0) {
|
||||||
|
new AnnotationsFinder();
|
||||||
|
} else {
|
||||||
|
new RootClass(args);
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
displayError(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,32 @@
|
||||||
|
/*
|
||||||
|
* This file is part of the RootShell Project: https://github.com/Stericson/RootShell
|
||||||
|
*
|
||||||
|
* Copyright (c) 2014 Stephen Erickson, Chris Ravenscroft
|
||||||
|
*
|
||||||
|
* This code is dual-licensed under the terms of the Apache License Version 2.0 and
|
||||||
|
* the terms of the General Public License (GPL) Version 2.
|
||||||
|
* You may use this code according to either of these licenses as is most appropriate
|
||||||
|
* for your project on a case-by-case basis.
|
||||||
|
*
|
||||||
|
* The terms of each license can be found in the root directory of this project's repository as well as at:
|
||||||
|
*
|
||||||
|
* * http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
* * http://www.gnu.org/licenses/gpl-2.0.txt
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under these Licenses is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See each License for the specific language governing permissions and
|
||||||
|
* limitations under that License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.stericson.RootShell.exceptions;
|
||||||
|
|
||||||
|
public class RootDeniedException extends Exception {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = -8713947214162841310L;
|
||||||
|
|
||||||
|
public RootDeniedException(String error) {
|
||||||
|
super(error);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,350 @@
|
||||||
|
/*
|
||||||
|
* This file is part of the RootShell Project: http://code.google.com/p/RootShell/
|
||||||
|
*
|
||||||
|
* Copyright (c) 2014 Stephen Erickson, Chris Ravenscroft
|
||||||
|
*
|
||||||
|
* This code is dual-licensed under the terms of the Apache License Version 2.0 and
|
||||||
|
* the terms of the General Public License (GPL) Version 2.
|
||||||
|
* You may use this code according to either of these licenses as is most appropriate
|
||||||
|
* for your project on a case-by-case basis.
|
||||||
|
*
|
||||||
|
* The terms of each license can be found in the root directory of this project's repository as well as at:
|
||||||
|
*
|
||||||
|
* * http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
* * http://www.gnu.org/licenses/gpl-2.0.txt
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under these Licenses is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See each License for the specific language governing permissions and
|
||||||
|
* limitations under that License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.stericson.RootShell.execution;
|
||||||
|
|
||||||
|
import com.stericson.RootShell.RootShell;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.os.Build;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.os.Handler;
|
||||||
|
import android.os.Looper;
|
||||||
|
import android.os.Message;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
public class Command {
|
||||||
|
|
||||||
|
//directly modified by JavaCommand
|
||||||
|
protected boolean javaCommand = false;
|
||||||
|
protected Context context = null;
|
||||||
|
|
||||||
|
public int totalOutput = 0;
|
||||||
|
|
||||||
|
public int totalOutputProcessed = 0;
|
||||||
|
|
||||||
|
ExecutionMonitor executionMonitor = null;
|
||||||
|
|
||||||
|
Handler mHandler = null;
|
||||||
|
|
||||||
|
//Has this command already been used?
|
||||||
|
protected boolean used = false;
|
||||||
|
|
||||||
|
boolean executing = false;
|
||||||
|
|
||||||
|
String[] command = {};
|
||||||
|
|
||||||
|
boolean finished = false;
|
||||||
|
|
||||||
|
boolean terminated = false;
|
||||||
|
|
||||||
|
boolean handlerEnabled = true;
|
||||||
|
|
||||||
|
int exitCode = -1;
|
||||||
|
|
||||||
|
int id = 0;
|
||||||
|
|
||||||
|
int timeout = RootShell.defaultCommandTimeout;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor for executing a normal shell command
|
||||||
|
*
|
||||||
|
* @param id the id of the command being executed
|
||||||
|
* @param command the command, or commands, to be executed.
|
||||||
|
*/
|
||||||
|
public Command(int id, String... command) {
|
||||||
|
this.command = command;
|
||||||
|
this.id = id;
|
||||||
|
|
||||||
|
createHandler(RootShell.handlerEnabled);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor for executing a normal shell command
|
||||||
|
*
|
||||||
|
* @param id the id of the command being executed
|
||||||
|
* @param handlerEnabled when true the handler will be used to call the
|
||||||
|
* callback methods if possible.
|
||||||
|
* @param command the command, or commands, to be executed.
|
||||||
|
*/
|
||||||
|
public Command(int id, boolean handlerEnabled, String... command) {
|
||||||
|
this.command = command;
|
||||||
|
this.id = id;
|
||||||
|
|
||||||
|
createHandler(handlerEnabled);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor for executing a normal shell command
|
||||||
|
*
|
||||||
|
* @param id the id of the command being executed
|
||||||
|
* @param timeout the time allowed before the shell will give up executing the command
|
||||||
|
* and throw a TimeoutException.
|
||||||
|
* @param command the command, or commands, to be executed.
|
||||||
|
*/
|
||||||
|
public Command(int id, int timeout, String... command) {
|
||||||
|
this.command = command;
|
||||||
|
this.id = id;
|
||||||
|
this.timeout = timeout;
|
||||||
|
|
||||||
|
createHandler(RootShell.handlerEnabled);
|
||||||
|
}
|
||||||
|
|
||||||
|
//If you override this you MUST make a final call
|
||||||
|
//to the super method. The super call should be the last line of this method.
|
||||||
|
public void commandOutput(int id, String line) {
|
||||||
|
RootShell.log("Command", "ID: " + id + ", " + line);
|
||||||
|
totalOutputProcessed++;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void commandTerminated(int id, String reason) {
|
||||||
|
//pass
|
||||||
|
}
|
||||||
|
|
||||||
|
public void commandCompleted(int id, int exitcode) {
|
||||||
|
//pass
|
||||||
|
}
|
||||||
|
|
||||||
|
protected final void commandFinished() {
|
||||||
|
if (!terminated) {
|
||||||
|
synchronized (this) {
|
||||||
|
if (mHandler != null && handlerEnabled) {
|
||||||
|
Message msg = mHandler.obtainMessage();
|
||||||
|
Bundle bundle = new Bundle();
|
||||||
|
bundle.putInt(CommandHandler.ACTION, CommandHandler.COMMAND_COMPLETED);
|
||||||
|
msg.setData(bundle);
|
||||||
|
mHandler.sendMessage(msg);
|
||||||
|
} else {
|
||||||
|
commandCompleted(id, exitCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
RootShell.log("Command " + id + " finished.");
|
||||||
|
finishCommand();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void createHandler(boolean handlerEnabled) {
|
||||||
|
|
||||||
|
this.handlerEnabled = handlerEnabled;
|
||||||
|
|
||||||
|
if (Looper.myLooper() != null && handlerEnabled) {
|
||||||
|
RootShell.log("CommandHandler created");
|
||||||
|
mHandler = new CommandHandler();
|
||||||
|
} else {
|
||||||
|
RootShell.log("CommandHandler not created");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public final void finish()
|
||||||
|
{
|
||||||
|
RootShell.log("Command finished at users request!");
|
||||||
|
commandFinished();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected final void finishCommand() {
|
||||||
|
this.executing = false;
|
||||||
|
this.finished = true;
|
||||||
|
this.notifyAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public final String getCommand() {
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
|
||||||
|
if(javaCommand) {
|
||||||
|
String filePath = context.getFilesDir().getPath();
|
||||||
|
|
||||||
|
for (int i = 0; i < command.length; i++) {
|
||||||
|
/*
|
||||||
|
* TODO Make withFramework optional for applications
|
||||||
|
* that do not require access to the fw. -CFR
|
||||||
|
*/
|
||||||
|
//export CLASSPATH=/data/user/0/ch.masshardt.emailnotification/files/anbuild.dex ; app_process /system/bin
|
||||||
|
if (Build.VERSION.SDK_INT > 22) {
|
||||||
|
//dalvikvm command is not working in Android Marshmallow
|
||||||
|
sb.append(
|
||||||
|
"export CLASSPATH=" + filePath + "/anbuild.dex;"
|
||||||
|
+ " app_process /system/bin "
|
||||||
|
+ command[i]);
|
||||||
|
} else {
|
||||||
|
sb.append(
|
||||||
|
"dalvikvm -cp " + filePath + "/anbuild.dex"
|
||||||
|
+ " com.android.internal.util.WithFramework"
|
||||||
|
+ " com.stericson.RootTools.containers.RootClass "
|
||||||
|
+ command[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
sb.append('\n');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
for (int i = 0; i < command.length; i++) {
|
||||||
|
sb.append(command[i]);
|
||||||
|
sb.append('\n');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public final boolean isExecuting() {
|
||||||
|
return executing;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final boolean isHandlerEnabled() {
|
||||||
|
return handlerEnabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final boolean isFinished() {
|
||||||
|
return finished;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final int getExitCode() {
|
||||||
|
return this.exitCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected final void setExitCode(int code) {
|
||||||
|
synchronized (this) {
|
||||||
|
exitCode = code;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected final void startExecution() {
|
||||||
|
this.used = true;
|
||||||
|
executionMonitor = new ExecutionMonitor(this);
|
||||||
|
executionMonitor.setPriority(Thread.MIN_PRIORITY);
|
||||||
|
executionMonitor.start();
|
||||||
|
executing = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final void terminate()
|
||||||
|
{
|
||||||
|
RootShell.log("Terminating command at users request!");
|
||||||
|
terminated("Terminated at users request!");
|
||||||
|
}
|
||||||
|
|
||||||
|
protected final void terminate(String reason) {
|
||||||
|
try {
|
||||||
|
Shell.closeAll();
|
||||||
|
RootShell.log("Terminating all shells.");
|
||||||
|
terminated(reason);
|
||||||
|
} catch (IOException e) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected final void terminated(String reason) {
|
||||||
|
synchronized (Command.this) {
|
||||||
|
|
||||||
|
if (mHandler != null && handlerEnabled) {
|
||||||
|
Message msg = mHandler.obtainMessage();
|
||||||
|
Bundle bundle = new Bundle();
|
||||||
|
bundle.putInt(CommandHandler.ACTION, CommandHandler.COMMAND_TERMINATED);
|
||||||
|
bundle.putString(CommandHandler.TEXT, reason);
|
||||||
|
msg.setData(bundle);
|
||||||
|
mHandler.sendMessage(msg);
|
||||||
|
} else {
|
||||||
|
commandTerminated(id, reason);
|
||||||
|
}
|
||||||
|
|
||||||
|
RootShell.log("Command " + id + " did not finish because it was terminated. Termination reason: " + reason);
|
||||||
|
setExitCode(-1);
|
||||||
|
terminated = true;
|
||||||
|
finishCommand();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected final void output(int id, String line) {
|
||||||
|
totalOutput++;
|
||||||
|
|
||||||
|
if (mHandler != null && handlerEnabled) {
|
||||||
|
Message msg = mHandler.obtainMessage();
|
||||||
|
Bundle bundle = new Bundle();
|
||||||
|
bundle.putInt(CommandHandler.ACTION, CommandHandler.COMMAND_OUTPUT);
|
||||||
|
bundle.putString(CommandHandler.TEXT, line);
|
||||||
|
msg.setData(bundle);
|
||||||
|
mHandler.sendMessage(msg);
|
||||||
|
} else {
|
||||||
|
commandOutput(id, line);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class ExecutionMonitor extends Thread {
|
||||||
|
|
||||||
|
private final Command command;
|
||||||
|
|
||||||
|
public ExecutionMonitor(Command command) {
|
||||||
|
this.command = command;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void run() {
|
||||||
|
|
||||||
|
if(command.timeout > 0)
|
||||||
|
{
|
||||||
|
synchronized (command) {
|
||||||
|
try {
|
||||||
|
RootShell.log("Command " + command.id + " is waiting for: " + command.timeout);
|
||||||
|
command.wait(command.timeout);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
RootShell.log("Exception: " + e);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!command.isFinished()) {
|
||||||
|
RootShell.log("Timeout Exception has occurred for command: " + command.id + ".");
|
||||||
|
terminate("Timeout Exception");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class CommandHandler extends Handler {
|
||||||
|
|
||||||
|
static final public String ACTION = "action";
|
||||||
|
|
||||||
|
static final public String TEXT = "text";
|
||||||
|
|
||||||
|
static final public int COMMAND_OUTPUT = 0x01;
|
||||||
|
|
||||||
|
static final public int COMMAND_COMPLETED = 0x02;
|
||||||
|
|
||||||
|
static final public int COMMAND_TERMINATED = 0x03;
|
||||||
|
|
||||||
|
public final void handleMessage(Message msg) {
|
||||||
|
int action = msg.getData().getInt(ACTION);
|
||||||
|
String text = msg.getData().getString(TEXT);
|
||||||
|
|
||||||
|
switch (action) {
|
||||||
|
case COMMAND_OUTPUT:
|
||||||
|
commandOutput(id, text);
|
||||||
|
break;
|
||||||
|
case COMMAND_COMPLETED:
|
||||||
|
commandCompleted(id, exitCode);
|
||||||
|
break;
|
||||||
|
case COMMAND_TERMINATED:
|
||||||
|
commandTerminated(id, text);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,58 @@
|
||||||
|
package com.stericson.RootShell.execution;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
|
||||||
|
public class JavaCommand extends Command
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Constructor for executing Java commands rather than binaries
|
||||||
|
*
|
||||||
|
* @param context needed to execute java command.
|
||||||
|
*/
|
||||||
|
public JavaCommand(int id, Context context, String... command) {
|
||||||
|
super(id, command);
|
||||||
|
this.context = context;
|
||||||
|
this.javaCommand = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor for executing Java commands rather than binaries
|
||||||
|
*
|
||||||
|
* @param context needed to execute java command.
|
||||||
|
*/
|
||||||
|
public JavaCommand(int id, boolean handlerEnabled, Context context, String... command) {
|
||||||
|
super(id, handlerEnabled, command);
|
||||||
|
this.context = context;
|
||||||
|
this.javaCommand = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor for executing Java commands rather than binaries
|
||||||
|
*
|
||||||
|
* @param context needed to execute java command.
|
||||||
|
*/
|
||||||
|
public JavaCommand(int id, int timeout, Context context, String... command) {
|
||||||
|
super(id, timeout, command);
|
||||||
|
this.context = context;
|
||||||
|
this.javaCommand = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void commandOutput(int id, String line)
|
||||||
|
{
|
||||||
|
super.commandOutput(id, line);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void commandTerminated(int id, String reason)
|
||||||
|
{
|
||||||
|
// pass
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void commandCompleted(int id, int exitCode)
|
||||||
|
{
|
||||||
|
// pass
|
||||||
|
}
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
|
@ -1 +1 @@
|
||||||
include ':app', ':libmynteye'
|
include ':app', ':libmynteye', ':libshell'
|
||||||
|
|
Loading…
Reference in New Issue
Block a user