Repo created
This commit is contained in:
parent
51cf8bb4f9
commit
ee0cddf35c
548 changed files with 93129 additions and 2 deletions
84
app/build.gradle
Normal file
84
app/build.gradle
Normal file
|
|
@ -0,0 +1,84 @@
|
|||
apply plugin: 'com.android.application'
|
||||
|
||||
android {
|
||||
|
||||
defaultConfig {
|
||||
compileSdk 36
|
||||
targetSdk 36
|
||||
applicationId "dev.ukanth.ufirewall"
|
||||
//applicationId "dev.ukanth.ufirewall.donate"
|
||||
minSdkVersion 21
|
||||
versionCode 20251001
|
||||
versionName "4.0.0"
|
||||
buildConfigField 'boolean', 'DONATE', 'false'
|
||||
vectorDrawables.useSupportLibrary = true
|
||||
ndk {
|
||||
abiFilters 'armeabi-v7a', 'arm64-v8a', 'x86', 'x86_64'
|
||||
}
|
||||
}
|
||||
|
||||
buildFeatures {
|
||||
buildConfig true
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
debug {
|
||||
minifyEnabled false
|
||||
proguardFiles 'proguard-rules.pro'
|
||||
}
|
||||
release {
|
||||
minifyEnabled false
|
||||
proguardFiles 'proguard-rules.pro'
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
lint {
|
||||
abortOnError true
|
||||
disable 'MissingTranslation'
|
||||
}
|
||||
|
||||
namespace 'dev.ukanth.ufirewall'
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_17
|
||||
targetCompatibility JavaVersion.VERSION_17
|
||||
}
|
||||
compileSdk 36
|
||||
buildToolsVersion '34.0.0'
|
||||
|
||||
packagingOptions {
|
||||
jniLibs {
|
||||
useLegacyPackaging = true
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
dependencies {
|
||||
|
||||
def libsuVersion = '6.0.0'
|
||||
def dbFlowVersion = '4.2.4'
|
||||
|
||||
implementation "com.github.topjohnwu.libsu:core:${libsuVersion}"
|
||||
implementation "com.github.topjohnwu.libsu:service:${libsuVersion}"
|
||||
implementation "com.github.topjohnwu.libsu:nio:${libsuVersion}"
|
||||
implementation "eu.chainfire:libsuperuser:1.1.0"
|
||||
implementation "com.github.ukanth:android-lockpattern:8.0.4"
|
||||
implementation "com.afollestad.material-dialogs:core:0.9.6.0"
|
||||
implementation "androidx.appcompat:appcompat:1.7.0"
|
||||
implementation "com.google.android.material:material:1.12.0"
|
||||
implementation "androidx.cardview:cardview:1.0.0"
|
||||
implementation "androidx.recyclerview:recyclerview:1.3.2"
|
||||
implementation "androidx.annotation:annotation:1.9.0"
|
||||
implementation "androidx.core:core:1.15.0"
|
||||
implementation "androidx.preference:preference:1.2.1"
|
||||
implementation "androidx.legacy:legacy-support-v13:1.0.0"
|
||||
implementation "androidx.activity:activity:1.9.3"
|
||||
annotationProcessor "com.github.Raizlabs.DBFlow:dbflow-processor:${dbFlowVersion}"
|
||||
implementation "com.github.Raizlabs.DBFlow:dbflow-core:${dbFlowVersion}"
|
||||
implementation "com.github.Raizlabs.DBFlow:dbflow:${dbFlowVersion}"
|
||||
implementation "io.reactivex.rxjava3:rxjava:3.1.9"
|
||||
implementation "org.ocpsoft.prettytime:prettytime:5.0.6.Final"
|
||||
implementation "dnsjava:dnsjava:3.6.2"
|
||||
|
||||
}
|
||||
3
app/lint.xml
Normal file
3
app/lint.xml
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<lint>
|
||||
</lint>
|
||||
21
app/proguard-rules.pro
vendored
Normal file
21
app/proguard-rules.pro
vendored
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
-keepattributes
|
||||
-keep class org.ocpsoft.prettytime.i18n.**
|
||||
-keep class * extends com.raizlabs.android.dbflow.config.DatabaseHolder { *; }
|
||||
-dontpreverify
|
||||
-dontoptimize
|
||||
-dontobfuscate
|
||||
-keep class dev.ukanth.ufirewall.** { *; }
|
||||
-optimizations !code/allocation/variable
|
||||
|
||||
# Android 16 specific proguard rules
|
||||
-keep class android.window.** { *; }
|
||||
-keep class androidx.activity.** { *; }
|
||||
-dontwarn android.window.**
|
||||
-dontwarn androidx.window.**
|
||||
|
||||
# Edge-to-edge and window insets support
|
||||
-keep class androidx.core.view.WindowInsetsCompat** { *; }
|
||||
-keep class androidx.core.view.ViewCompat** { *; }
|
||||
|
||||
# Notification channel compatibility
|
||||
-keep class androidx.core.app.NotificationChannelCompat** { *; }
|
||||
366
app/src/main/AndroidManifest.xml
Normal file
366
app/src/main/AndroidManifest.xml
Normal file
|
|
@ -0,0 +1,366 @@
|
|||
<?xml version="1.0" encoding="utf-8"?><!--
|
||||
* Android Manifest
|
||||
* Copyright (C) 2007-2008 The Android Open Source Project
|
||||
* Copyright (C) 2009-2011 Rodrigo Zechin Rosauro
|
||||
* Copyright (C) 2011-2014 Umakanthan Chandran
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* @author Rodrigo Zechin Rosauro, Umakanthan Chandran
|
||||
* @version 1.2
|
||||
*/
|
||||
-->
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
|
||||
|
||||
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
|
||||
<!-- Allows ufwall to access information about networks -->
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||
|
||||
<!-- Allows ufwall to write to external storage -->
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
|
||||
android:maxSdkVersion="28" />
|
||||
|
||||
<!-- Allows access to IP configuration and tethering state -->
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
|
||||
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE"></uses-permission>
|
||||
<!-- Allows access to bluetooth tethering state -->
|
||||
<uses-permission android:name="android.permission.BLUETOOTH" />
|
||||
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
|
||||
|
||||
<!-- Allows ufwall to access information about Fingerprint -->
|
||||
<uses-permission android:name="android.permission.USE_FINGERPRINT" />
|
||||
<uses-permission android:name="android.permission.USE_BIOMETRIC" />
|
||||
|
||||
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT"/>
|
||||
|
||||
|
||||
<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" tools:ignore="QueryAllPackagesPermission"/>
|
||||
|
||||
<uses-permission
|
||||
android:name="android.permission.FOREGROUND_SERVICE"/>
|
||||
|
||||
<!-- Screen support -->
|
||||
<supports-screens
|
||||
android:anyDensity="true"
|
||||
android:largeScreens="true"
|
||||
android:normalScreens="true"
|
||||
android:smallScreens="true"
|
||||
android:xlargeScreens="true" />
|
||||
|
||||
<!-- donate
|
||||
android:icon="@mipmap/ic_launcher_donate"
|
||||
android:label="@string/app_name_donate" -->
|
||||
<application
|
||||
android:name=".util.G"
|
||||
android:allowBackup="false"
|
||||
android:hardwareAccelerated="true"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:largeHeap="true"
|
||||
android:launchMode="singleTop"
|
||||
android:resizeableActivity="true"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/AppDarkTheme"
|
||||
android:requestLegacyExternalStorage="true"
|
||||
tools:ignore="ManifestResource">
|
||||
|
||||
<!--<meta-data
|
||||
android:name="xposedmodule"
|
||||
android:value="true" />
|
||||
<meta-data
|
||||
android:name="xposeddescription"
|
||||
android:value="Xposed related fixes for AFWall+" />
|
||||
<meta-data
|
||||
android:name="xposedminversion"
|
||||
android:value="53" />-->
|
||||
|
||||
|
||||
<!-- donate android:label="@string/app_name_donate" -->
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:configChanges="orientation|keyboardHidden|screenSize|keyboard|locale|screenLayout"
|
||||
android:hardwareAccelerated="true"
|
||||
android:exported="true"
|
||||
android:label="@string/app_name">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
<category android:name="android.intent.category.MULTIWINDOW_LAUNCHER" />
|
||||
</intent-filter>
|
||||
<meta-data
|
||||
android:name="android.app.shortcuts"
|
||||
android:resource="@xml/shortcuts" />
|
||||
</activity>
|
||||
<activity
|
||||
android:name=".preferences.PreferencesActivity"
|
||||
android:theme="@style/AppDarkTheme"
|
||||
android:exported="true"
|
||||
android:configChanges="orientation|keyboardHidden|screenSize|keyboard|locale|screenLayout"
|
||||
android:hardwareAccelerated="true">
|
||||
<meta-data
|
||||
android:name="android.support.PARENT_ACTIVITY"
|
||||
android:value="dev.ukanth.ufirewall.MainActivity" />
|
||||
</activity>
|
||||
<activity
|
||||
android:name=".activity.HelpActivity"
|
||||
android:configChanges="orientation|keyboardHidden|screenSize|locale"
|
||||
android:hardwareAccelerated="true"></activity>
|
||||
<activity
|
||||
android:name=".activity.RulesActivity"
|
||||
android:configChanges="orientation|keyboardHidden|screenSize|keyboard|locale|screenLayout"></activity>
|
||||
<activity
|
||||
android:name=".activity.ProfileActivity"
|
||||
android:configChanges="orientation|keyboardHidden|screenSize|keyboard|locale|screenLayout"></activity>
|
||||
<activity
|
||||
android:name=".activity.AppDetailActivity"
|
||||
android:configChanges="orientation|keyboardHidden|screenSize|keyboard|locale|screenLayout">
|
||||
<meta-data
|
||||
android:name="android.support.PARENT_ACTIVITY"
|
||||
android:value="dev.ukanth.ufirewall.MainActivity" />
|
||||
</activity>
|
||||
<activity
|
||||
android:name=".activity.LogActivity"
|
||||
android:configChanges="orientation|keyboardHidden|screenSize|keyboard|locale|screenLayout"></activity>
|
||||
<activity
|
||||
android:name=".activity.OldLogActivity"
|
||||
android:configChanges="orientation|keyboardHidden|screenSize|keyboard|locale|screenLayout"></activity>
|
||||
<activity
|
||||
android:name=".activity.LogDetailActivity"
|
||||
android:configChanges="orientation|keyboardHidden|screenSize|keyboard|locale|screenLayout"></activity>
|
||||
<activity
|
||||
android:name=".activity.DataDumpActivity"
|
||||
android:configChanges="orientation|keyboardHidden|screenSize|keyboard|locale|screenLayout">
|
||||
<meta-data
|
||||
android:name="android.support.PARENT_ACTIVITY"
|
||||
android:value="dev.ukanth.ufirewall.MainActivity" />
|
||||
</activity>
|
||||
|
||||
<!-- <activity android:name="dev.ukanth.ufirewall.AlertDialogActivity"/> -->
|
||||
|
||||
<activity
|
||||
android:name=".widget.ToggleWidgetActivity"
|
||||
android:launchMode="singleInstance"
|
||||
android:exported="true"
|
||||
android:theme="@style/Theme.Transparent">
|
||||
<intent-filter>
|
||||
<action android:name="android.appwidget.action.APPWIDGET_CONFIGURE" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<activity
|
||||
android:name=".widget.ToggleWidgetOldActivity"
|
||||
android:launchMode="singleInstance"
|
||||
android:exported="true"
|
||||
android:theme="@style/Theme.Transparent">
|
||||
<intent-filter>
|
||||
<action android:name="android.appwidget.action.APPWIDGET_CONFIGURE" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<activity
|
||||
android:name=".activity.CustomScriptActivity"
|
||||
android:configChanges="orientation|keyboardHidden|screenSize|keyboard|locale|screenLayout">
|
||||
<meta-data
|
||||
android:name="android.support.PARENT_ACTIVITY"
|
||||
android:value="dev.ukanth.ufirewall.MainActivity" />
|
||||
</activity>
|
||||
|
||||
<!-- still will work in android 7 above -->
|
||||
<receiver android:name=".broadcast.OnBootReceiver"
|
||||
android:exported="true">
|
||||
<intent-filter android:priority="500">
|
||||
<action android:name="android.intent.action.BOOT_COMPLETED" />
|
||||
<action android:name="android.intent.action.QUICKBOOT_POWERON" />
|
||||
<action android:name="android.intent.action.MEDIA_MOUNTED" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
<!--<service android:name=".service.ApplyOnBootService" />-->
|
||||
|
||||
<service
|
||||
android:name=".service.FirewallService"
|
||||
android:foregroundServiceType="specialUse"
|
||||
android:exported="true"
|
||||
android:launchMode="singleTop" >
|
||||
|
||||
<property android:name="android.app.PROPERTY_SPECIAL_USE_FGS_SUBTYPE"
|
||||
android:value="Firewall service"/>
|
||||
|
||||
</service>
|
||||
|
||||
<service
|
||||
android:name=".service.ToggleTileService"
|
||||
android:icon="@drawable/notification_quest"
|
||||
android:exported="true"
|
||||
android:label="@string/widget_label_status"
|
||||
android:permission="android.permission.BIND_QUICK_SETTINGS_TILE">
|
||||
<intent-filter>
|
||||
<action android:name="android.service.quicksettings.action.QS_TILE" />
|
||||
</intent-filter>
|
||||
</service>
|
||||
|
||||
<receiver
|
||||
android:name=".widget.StatusWidget"
|
||||
android:enabled="true"
|
||||
android:exported="true"
|
||||
android:icon="@drawable/preview_toggle"
|
||||
android:label="@string/widget_label_status">
|
||||
<meta-data
|
||||
android:name="android.appwidget.provider"
|
||||
android:resource="@xml/onoff_widget" />
|
||||
|
||||
<intent-filter>
|
||||
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
|
||||
<action android:name="dev.ukanth.ufirewall.intent.action.STATUS_CHANGED" />
|
||||
<action android:name="dev.ukanth.ufirewall.intent.action.TOGGLE_REQUEST" />
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
<receiver
|
||||
android:name=".widget.ToggleWidget"
|
||||
android:exported="true"
|
||||
android:icon="@drawable/preview_new"
|
||||
android:label="@string/widget_label_settings">
|
||||
<meta-data
|
||||
android:name="android.appwidget.provider"
|
||||
android:resource="@xml/toggle_widget" />
|
||||
|
||||
<intent-filter>
|
||||
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
|
||||
<action android:name="dev.ukanth.ufirewall.intent.action.STATUS_CHANGED" />
|
||||
<action android:name="dev.ukanth.ufirewall.intent.action.TOGGLE_REQUEST" />
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
<receiver
|
||||
android:name=".widget.ToggleWidgetOld"
|
||||
android:exported="true"
|
||||
android:label="@string/widget_label_settings_old">
|
||||
<meta-data
|
||||
android:name="android.appwidget.provider"
|
||||
android:icon="@drawable/preview_old"
|
||||
android:resource="@xml/toggle_widget_old" />
|
||||
|
||||
<intent-filter>
|
||||
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
|
||||
<action android:name="dev.ukanth.ufirewall.intent.action.STATUS_CHANGED" />
|
||||
<action android:name="dev.ukanth.ufirewall.intent.action.TOGGLE_REQUEST" />
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
|
||||
<activity
|
||||
android:name="haibison.android.lockpattern.LockPatternActivity"
|
||||
android:theme="@style/Alp_42447968.Theme.Dark"
|
||||
tools:replace="android:theme" />
|
||||
|
||||
<!-- device admin -->
|
||||
<!-- This is required this receiver to become device admin component. -->
|
||||
|
||||
<receiver
|
||||
android:name=".admin.AdminDeviceReceiver"
|
||||
android:exported="true"
|
||||
android:description="@string/device_admin_desc"
|
||||
android:label="@string/enable_device_admin"
|
||||
android:permission="android.permission.BIND_DEVICE_ADMIN">
|
||||
<meta-data
|
||||
android:name="android.app.device_admin"
|
||||
android:resource="@xml/device_admin" />
|
||||
|
||||
<intent-filter>
|
||||
<action android:name="android.app.action.DEVICE_ADMIN_ENABLED" />
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
|
||||
|
||||
|
||||
<!-- Tasker/Locale Plugin -->
|
||||
|
||||
<activity
|
||||
android:name=".plugin.LocaleEdit"
|
||||
android:exported="true"
|
||||
android:icon="@drawable/ic_launcher_free"
|
||||
android:label="@string/tasker_lable"
|
||||
android:uiOptions="splitActionBarWhenNarrow"
|
||||
android:windowSoftInputMode="adjustResize"
|
||||
tools:ignore="ExportedActivity">
|
||||
|
||||
<!-- this Intent filter allows the plug-in to be discovered by Locale -->
|
||||
<intent-filter>
|
||||
<action android:name="com.twofortyfouram.locale.intent.action.EDIT_SETTING" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<receiver
|
||||
android:name=".plugin.FireReceiver"
|
||||
android:exported="true"
|
||||
tools:ignore="ExportedReceiver">
|
||||
|
||||
<!-- this Intent filter allows the plug-in to discovered by Locale -->
|
||||
<intent-filter>
|
||||
<action android:name="com.twofortyfouram.locale.intent.action.FIRE_SETTING" />
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
|
||||
<service
|
||||
android:name=".service.RootShellService"
|
||||
android:exported="false" />
|
||||
<service
|
||||
android:name=".service.RootShellService2"
|
||||
android:exported="false" />
|
||||
<service
|
||||
android:name=".service.LogService"
|
||||
android:permission="android.permission.BIND_JOB_SERVICE"
|
||||
android:enabled="true"
|
||||
android:label="AFWallLogService" />
|
||||
|
||||
<!-- Samsung MultiWindow Support -->
|
||||
<uses-library
|
||||
android:name="com.sec.android.app.multiwindow"
|
||||
android:required="false" />
|
||||
|
||||
<meta-data
|
||||
android:name="com.sec.android.multiwindow.DEFAULT_SIZE_W"
|
||||
android:resource="@dimen/app_defaultsize_w" />
|
||||
<meta-data
|
||||
android:name="com.sec.android.multiwindow.DEFAULT_SIZE_H"
|
||||
android:resource="@dimen/app_defaultsize_h" />
|
||||
<meta-data
|
||||
android:name="com.sec.android.multiwindow.MINIMUM_SIZE_W"
|
||||
android:resource="@dimen/app_minimumsize_w" />
|
||||
<meta-data
|
||||
android:name="com.sec.android.multiwindow.MINIMUM_SIZE_H"
|
||||
android:resource="@dimen/app_minimumsize_h" />
|
||||
|
||||
<provider
|
||||
android:name=".preferences.ShareProfilePreference"
|
||||
android:authorities="${applicationId}"
|
||||
android:exported="true"></provider>
|
||||
|
||||
<service
|
||||
android:name=".service.RulesApplyService"
|
||||
android:exported="false" />
|
||||
|
||||
<!-- <provider
|
||||
android:name=".util.XPreferenceProvider"
|
||||
android:authorities="${applicationId}"
|
||||
android:exported="true" />-->
|
||||
|
||||
<activity android:name=".activity.CustomRulesActivity"></activity>
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
78
app/src/main/assets/rules.json
Normal file
78
app/src/main/assets/rules.json
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
{
|
||||
"author": "ukanth",
|
||||
"version": "1.0",
|
||||
"rules": [
|
||||
{
|
||||
"name": "Allow Multicast DNS",
|
||||
"desc": "DNS based service discovery",
|
||||
"v4": {
|
||||
"on": [
|
||||
"-A afwall-wifi-fork -d 224.0.0.0/24 -j afwall-wifi-lan"
|
||||
],
|
||||
"off": [
|
||||
"-D afwall-wifi-fork -d 224.0.0.0/24 -j afwall-wifi-lan"
|
||||
]
|
||||
},
|
||||
"v6": {
|
||||
"on": [
|
||||
"-A afwall-wifi-fork -d ffx2::/16 -j afwall-wifi-lan"
|
||||
],
|
||||
"off": [
|
||||
"-D afwall-wifi-fork -d ffx2::/16 -j afwall-wifi-lan"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Allow ICMP",
|
||||
"desc": "To allow ICMP packets",
|
||||
"v4": {
|
||||
"on": [
|
||||
"-A INPUT -p icmp -m icmp --icmp-type echo-reply -j ACCEPT",
|
||||
"-A INPUT -p icmp -m icmp --icmp-type echo-request -j ACCEPT",
|
||||
"-A INPUT -p icmp -m icmp --icmp-type destination-unreachable -j ACCEPT"
|
||||
],
|
||||
"off": [
|
||||
"-D INPUT -p icmp -m icmp --icmp-type echo-reply -j ACCEPT",
|
||||
"-D INPUT -p icmp -m icmp --icmp-type echo-request -j ACCEPT",
|
||||
"-D INPUT -p icmp -m icmp --icmp-type destination-unreachable -j ACCEPT"
|
||||
]
|
||||
},
|
||||
"v6": {
|
||||
"on": [
|
||||
"-A INPUT -p icmp -m icmp --icmp-type echo-reply -j ACCEPT",
|
||||
"-A INPUT -p icmp -m icmp --icmp-type echo-request -j ACCEPT",
|
||||
"-A INPUT -p icmp -m icmp --icmp-type destination-unreachable -j ACCEPT"
|
||||
],
|
||||
"off": [
|
||||
"-D INPUT -p icmp -m icmp --icmp-type echo-reply -j ACCEPT",
|
||||
"-D INPUT -p icmp -m icmp --icmp-type echo-request -j ACCEPT",
|
||||
"-D INPUT -p icmp -m icmp --icmp-type destination-unreachable -j ACCEPT"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Allow Loopback Interface",
|
||||
"desc": "Allow lo interface routing (mostly on samsung)",
|
||||
"v4": {
|
||||
"on": [
|
||||
"-A INPUT -i lo -j ACCEPT",
|
||||
"-A afwall -o lo -j ACCEPT"
|
||||
],
|
||||
"off": [
|
||||
"-D INPUT -i lo -j ACCEPT",
|
||||
"-D afwall -o lo -j ACCEPT"
|
||||
]
|
||||
},
|
||||
"v6": {
|
||||
"on": [
|
||||
"-A INPUT -i lo -j ACCEPT",
|
||||
"-A afwall -o lo -j ACCEPT"
|
||||
],
|
||||
"off": [
|
||||
"-D INPUT -i lo -j ACCEPT",
|
||||
"-D afwall -o lo -j ACCEPT"
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
0
app/src/main/assets/xposed_init
Normal file
0
app/src/main/assets/xposed_init
Normal file
BIN
app/src/main/ic_launcher-web.png
Normal file
BIN
app/src/main/ic_launcher-web.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 27 KiB |
BIN
app/src/main/ic_launcher_donate-web.png
Normal file
BIN
app/src/main/ic_launcher_donate-web.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 26 KiB |
46
app/src/main/java/com/stericson/rootshell/NativeJavaClass.java
Executable file
46
app/src/main/java/com/stericson/rootshell/NativeJavaClass.java
Executable file
|
|
@ -0,0 +1,46 @@
|
|||
package com.stericson.rootshell;
|
||||
|
||||
import com.stericson.rootshell.containers.RootClass;
|
||||
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.File;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
|
||||
@RootClass.Candidate
|
||||
public class NativeJavaClass
|
||||
{
|
||||
|
||||
public NativeJavaClass(RootClass.RootArgs args)
|
||||
{
|
||||
System.out.println("NativeJavaClass says: oh hi there.");
|
||||
String p = "/data/data/com.android.browser/cache";
|
||||
File f = new File(p);
|
||||
String[] fl = f.list();
|
||||
if (fl != null)
|
||||
{
|
||||
System.out.println("Look at all the stuff in your browser's cache:");
|
||||
for (String af : fl)
|
||||
{
|
||||
System.out.println("-" + af);
|
||||
}
|
||||
System.out.println("Leaving my mark for posterity...");
|
||||
File f2 = new File(p + "/rootshell_was_here");
|
||||
try
|
||||
{
|
||||
FileWriter filewriter = new FileWriter(f2);
|
||||
BufferedWriter out = new BufferedWriter(filewriter);
|
||||
out.write("This is just a file created using RootShell's Sanity check tools..\n");
|
||||
out.close();
|
||||
System.out.println("Done!");
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
System.out.println("...and I failed miserably.");
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
585
app/src/main/java/com/stericson/rootshell/RootShell.java
Normal file
585
app/src/main/java/com/stericson/rootshell/RootShell.java
Normal file
|
|
@ -0,0 +1,585 @@
|
|||
/*
|
||||
* 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 android.util.Log;
|
||||
|
||||
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.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.4";
|
||||
|
||||
/**
|
||||
* 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 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>(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 RootShell.getCustomShell(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);
|
||||
}
|
||||
public static boolean isAccessGiven(int timeout, int retry) {
|
||||
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.startRootShell().add(command);
|
||||
commandWait(Shell.startRootShell(), 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 or Toybox was found.
|
||||
*/
|
||||
public static boolean isBusyboxAvailable()
|
||||
{
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
415
app/src/main/java/com/stericson/rootshell/SanityCheckRootShell.java
Executable file
415
app/src/main/java/com/stericson/rootshell/SanityCheckRootShell.java
Executable file
|
|
@ -0,0 +1,415 @@
|
|||
/*
|
||||
* This file is part of the RootShell Project: http://code.google.com/p/RootShell/
|
||||
*
|
||||
* Copyright (c) 2012 Stephen Erickson, Chris Ravenscroft, Dominik Schuermann, Adam Shanks
|
||||
*
|
||||
* 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 android.app.Activity;
|
||||
import android.app.ProgressDialog;
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.Message;
|
||||
import android.os.StrictMode;
|
||||
import android.widget.ScrollView;
|
||||
import android.widget.TextView;
|
||||
|
||||
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.List;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
public class SanityCheckRootShell extends Activity
|
||||
{
|
||||
private ScrollView mScrollView;
|
||||
private TextView mTextView;
|
||||
private ProgressDialog mPDialog;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState)
|
||||
{
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
|
||||
.detectDiskReads()
|
||||
.detectDiskWrites()
|
||||
.detectNetwork() // or .detectAll() for all detectable problems
|
||||
.penaltyLog()
|
||||
.build());
|
||||
StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder()
|
||||
.detectLeakedSqlLiteObjects()
|
||||
.detectLeakedClosableObjects()
|
||||
.penaltyLog()
|
||||
.penaltyDeath()
|
||||
.build());
|
||||
|
||||
RootShell.debugMode = true;
|
||||
|
||||
mTextView = new TextView(this);
|
||||
mTextView.setText("");
|
||||
mScrollView = new ScrollView(this);
|
||||
mScrollView.addView(mTextView);
|
||||
setContentView(mScrollView);
|
||||
|
||||
print("SanityCheckRootShell \n\n");
|
||||
|
||||
if (RootShell.isRootAvailable())
|
||||
{
|
||||
print("Root found.\n");
|
||||
}
|
||||
else
|
||||
{
|
||||
print("Root not found");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
RootShell.getShell(true);
|
||||
}
|
||||
catch (IOException e2)
|
||||
{
|
||||
// TODO Auto-generated catch block
|
||||
e2.printStackTrace();
|
||||
}
|
||||
catch (TimeoutException e)
|
||||
{
|
||||
print("[ TIMEOUT EXCEPTION! ]\n");
|
||||
e.printStackTrace();
|
||||
}
|
||||
catch (RootDeniedException e)
|
||||
{
|
||||
print("[ ROOT DENIED EXCEPTION! ]\n");
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (!RootShell.isAccessGiven())
|
||||
{
|
||||
print("ERROR: No root access to this device.\n");
|
||||
return;
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
print("ERROR: could not determine root access to this device.\n");
|
||||
return;
|
||||
}
|
||||
|
||||
// Display infinite progress bar
|
||||
mPDialog = new ProgressDialog(this);
|
||||
mPDialog.setCancelable(false);
|
||||
mPDialog.setProgressStyle(ProgressDialog.STYLE_SPINNER);
|
||||
|
||||
new SanityCheckThread(this, new TestHandler()).start();
|
||||
}
|
||||
|
||||
protected void print(CharSequence text)
|
||||
{
|
||||
mTextView.append(text);
|
||||
mScrollView.post(new Runnable()
|
||||
{
|
||||
public void run()
|
||||
{
|
||||
mScrollView.fullScroll(ScrollView.FOCUS_DOWN);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Run our long-running tests in their separate thread so as to
|
||||
// not interfere with proper rendering.
|
||||
private class SanityCheckThread extends Thread
|
||||
{
|
||||
private final Handler mHandler;
|
||||
|
||||
public SanityCheckThread(Context context, Handler handler)
|
||||
{
|
||||
mHandler = handler;
|
||||
}
|
||||
|
||||
public void run()
|
||||
{
|
||||
visualUpdate(TestHandler.ACTION_SHOW, null);
|
||||
|
||||
// First test: Install a binary file for future use
|
||||
// if it wasn't already installed.
|
||||
/*
|
||||
visualUpdate(TestHandler.ACTION_PDISPLAY, "Installing binary if needed");
|
||||
if(false == RootShell.installBinary(mContext, R.raw.nes, "nes_binary")) {
|
||||
visualUpdate(TestHandler.ACTION_HIDE, "ERROR: Failed to install binary. Please see log file.");
|
||||
return;
|
||||
}
|
||||
*/
|
||||
|
||||
boolean result;
|
||||
|
||||
visualUpdate(TestHandler.ACTION_PDISPLAY, "Testing getPath");
|
||||
visualUpdate(TestHandler.ACTION_DISPLAY, "[ getPath ]\n");
|
||||
|
||||
try
|
||||
{
|
||||
List<String> paths = RootShell.getPath();
|
||||
|
||||
for (String path : paths)
|
||||
{
|
||||
visualUpdate(TestHandler.ACTION_DISPLAY, path + " k\n\n");
|
||||
}
|
||||
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
visualUpdate(TestHandler.ACTION_PDISPLAY, "Testing A ton of commands");
|
||||
visualUpdate(TestHandler.ACTION_DISPLAY, "[ Ton of Commands ]\n");
|
||||
|
||||
for (int i = 0; i < 100; i++)
|
||||
{
|
||||
RootShell.exists("/system/xbin/busybox");
|
||||
}
|
||||
|
||||
visualUpdate(TestHandler.ACTION_PDISPLAY, "Testing Find Binary");
|
||||
result = RootShell.isRootAvailable();
|
||||
visualUpdate(TestHandler.ACTION_DISPLAY, "[ Checking Root ]\n");
|
||||
visualUpdate(TestHandler.ACTION_DISPLAY, result + " k\n\n");
|
||||
|
||||
result = RootShell.isBusyboxAvailable();
|
||||
visualUpdate(TestHandler.ACTION_DISPLAY, "[ Checking Busybox ]\n");
|
||||
visualUpdate(TestHandler.ACTION_DISPLAY, result + " k\n\n");
|
||||
|
||||
visualUpdate(TestHandler.ACTION_PDISPLAY, "Testing file exists");
|
||||
visualUpdate(TestHandler.ACTION_DISPLAY, "[ Checking Exists() ]\n");
|
||||
visualUpdate(TestHandler.ACTION_DISPLAY, RootShell.exists("/system/sbin/[") + " k\n\n");
|
||||
|
||||
visualUpdate(TestHandler.ACTION_PDISPLAY, "Testing Is Access Given");
|
||||
result = RootShell.isAccessGiven();
|
||||
visualUpdate(TestHandler.ACTION_DISPLAY, "[ Checking for Access to Root ]\n");
|
||||
visualUpdate(TestHandler.ACTION_DISPLAY, result + " k\n\n");
|
||||
|
||||
|
||||
Shell shell;
|
||||
|
||||
visualUpdate(TestHandler.ACTION_PDISPLAY, "Testing output capture");
|
||||
visualUpdate(TestHandler.ACTION_DISPLAY, "[ busybox ash --help ]\n");
|
||||
|
||||
try
|
||||
{
|
||||
shell = RootShell.getShell(true);
|
||||
Command cmd = new Command(
|
||||
0,
|
||||
"busybox ash --help")
|
||||
{
|
||||
|
||||
@Override
|
||||
public void commandOutput(int id, String line)
|
||||
{
|
||||
visualUpdate(TestHandler.ACTION_DISPLAY, line + "\n");
|
||||
//super.commandOutput(id, line);
|
||||
}
|
||||
};
|
||||
shell.add(cmd);
|
||||
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
visualUpdate(TestHandler.ACTION_PDISPLAY, "Switching RootContext - SYSTEM_APP");
|
||||
visualUpdate(TestHandler.ACTION_DISPLAY, "[ Switching Root Context - SYSTEM_APP ]\n");
|
||||
|
||||
try
|
||||
{
|
||||
shell = RootShell.getShell(true, Shell.ShellContext.SYSTEM_APP);
|
||||
Command cmd = new Command(
|
||||
0,
|
||||
"id")
|
||||
{
|
||||
|
||||
@Override
|
||||
public void commandOutput(int id, String line)
|
||||
{
|
||||
visualUpdate(TestHandler.ACTION_DISPLAY, line + "\n");
|
||||
super.commandOutput(id, line);
|
||||
}
|
||||
};
|
||||
shell.add(cmd);
|
||||
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
visualUpdate(TestHandler.ACTION_PDISPLAY, "Switching RootContext - UNTRUSTED");
|
||||
visualUpdate(TestHandler.ACTION_DISPLAY, "[ Switching Root Context - UNTRUSTED ]\n");
|
||||
|
||||
try
|
||||
{
|
||||
shell = RootShell.getShell(true, Shell.ShellContext.UNTRUSTED_APP);
|
||||
Command cmd = new Command(
|
||||
0,
|
||||
"id")
|
||||
{
|
||||
|
||||
@Override
|
||||
public void commandOutput(int id, String line)
|
||||
{
|
||||
visualUpdate(TestHandler.ACTION_DISPLAY, line + "\n");
|
||||
super.commandOutput(id, line);
|
||||
}
|
||||
};
|
||||
shell.add(cmd);
|
||||
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
shell = RootShell.getShell(true);
|
||||
|
||||
Command cmd = new Command(42, false, "echo done")
|
||||
{
|
||||
|
||||
boolean _catch = false;
|
||||
|
||||
@Override
|
||||
public void commandOutput(int id, String line)
|
||||
{
|
||||
if (_catch)
|
||||
{
|
||||
RootShell.log("CAUGHT!!!");
|
||||
}
|
||||
|
||||
super.commandOutput(id, line);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void commandTerminated(int id, String reason)
|
||||
{
|
||||
synchronized (SanityCheckRootShell.this)
|
||||
{
|
||||
|
||||
_catch = true;
|
||||
visualUpdate(TestHandler.ACTION_PDISPLAY, "All tests complete.");
|
||||
visualUpdate(TestHandler.ACTION_HIDE, null);
|
||||
|
||||
try
|
||||
{
|
||||
RootShell.closeAllShells();
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
// TODO Auto-generated catch block
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void commandCompleted(int id, int exitCode)
|
||||
{
|
||||
synchronized (SanityCheckRootShell.this)
|
||||
{
|
||||
_catch = true;
|
||||
|
||||
visualUpdate(TestHandler.ACTION_PDISPLAY, "All tests complete.");
|
||||
visualUpdate(TestHandler.ACTION_HIDE, null);
|
||||
|
||||
try
|
||||
{
|
||||
RootShell.closeAllShells();
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
// TODO Auto-generated catch block
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
shell.add(cmd);
|
||||
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void visualUpdate(int action, String text)
|
||||
{
|
||||
Message msg = mHandler.obtainMessage();
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putInt(TestHandler.ACTION, action);
|
||||
bundle.putString(TestHandler.TEXT, text);
|
||||
msg.setData(bundle);
|
||||
mHandler.sendMessage(msg);
|
||||
}
|
||||
}
|
||||
|
||||
private class TestHandler extends Handler
|
||||
{
|
||||
static final public String ACTION = "action";
|
||||
static final public int ACTION_SHOW = 0x01;
|
||||
static final public int ACTION_HIDE = 0x02;
|
||||
static final public int ACTION_DISPLAY = 0x03;
|
||||
static final public int ACTION_PDISPLAY = 0x04;
|
||||
static final public String TEXT = "text";
|
||||
|
||||
public void handleMessage(Message msg)
|
||||
{
|
||||
int action = msg.getData().getInt(ACTION);
|
||||
String text = msg.getData().getString(TEXT);
|
||||
|
||||
switch (action)
|
||||
{
|
||||
case ACTION_SHOW:
|
||||
mPDialog.show();
|
||||
mPDialog.setMessage("Running Root Library Tests...");
|
||||
break;
|
||||
case ACTION_HIDE:
|
||||
if (null != text)
|
||||
{ print(text); }
|
||||
mPDialog.hide();
|
||||
break;
|
||||
case ACTION_DISPLAY:
|
||||
print(text);
|
||||
break;
|
||||
case ACTION_PDISPLAY:
|
||||
mPDialog.setMessage(text);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,328 @@
|
|||
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 static 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 final 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(" ").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[0]);
|
||||
}
|
||||
ProcessBuilder jarBuilder = new ProcessBuilder(cmd);
|
||||
jarBuilder.directory(builtPath);
|
||||
try {
|
||||
jarBuilder.start().waitFor();
|
||||
} catch (IOException e) {
|
||||
} catch (InterruptedException e) {
|
||||
}
|
||||
|
||||
File rawFolder = new File("res" + File.separator + "raw");
|
||||
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=res" + File.separator + "raw" + File.separator + "anbuild.dex "
|
||||
+ builtPath + File.separator + "anbuild.jar"
|
||||
};
|
||||
} else {
|
||||
cmd = new String[]{
|
||||
getPathToDx(),
|
||||
"--dex",
|
||||
"--output=res" + File.separator + "raw" + 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 res" + File.separator + "raw" + File.separator + " folder :::");
|
||||
}
|
||||
|
||||
protected void lookup(File path, List<File> fileList) {
|
||||
String desourcedPath = path.toString().replace("src" + File.separator, "");
|
||||
File[] files = path.listFiles();
|
||||
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 {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
325
app/src/main/java/com/stericson/rootshell/execution/Command.java
Normal file
325
app/src/main/java/com/stericson/rootshell/execution/Command.java
Normal file
|
|
@ -0,0 +1,325 @@
|
|||
/*
|
||||
* 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 android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.os.Message;
|
||||
|
||||
import com.stericson.rootshell.RootShell;
|
||||
|
||||
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();
|
||||
|
||||
for (int i = 0; i < command.length; i++) {
|
||||
if (i > 0) {
|
||||
sb.append('\n');
|
||||
}
|
||||
|
||||
sb.append(command[i]);
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
1029
app/src/main/java/com/stericson/rootshell/execution/Shell.java
Normal file
1029
app/src/main/java/com/stericson/rootshell/execution/Shell.java
Normal file
File diff suppressed because it is too large
Load diff
15
app/src/main/java/com/stericson/roottools/Constants.java
Normal file
15
app/src/main/java/com/stericson/roottools/Constants.java
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
package com.stericson.roottools;
|
||||
|
||||
public class Constants
|
||||
{
|
||||
public static final String TAG = "RootTools v4.4";
|
||||
public static final int FPS = 1;
|
||||
public static final int BBA = 3;
|
||||
public static final int BBV = 4;
|
||||
public static final int GI = 5;
|
||||
public static final int GS = 6;
|
||||
public static final int GSYM = 7;
|
||||
public static final int GET_MOUNTS = 8;
|
||||
public static final int GET_SYMLINKS = 9;
|
||||
|
||||
}
|
||||
848
app/src/main/java/com/stericson/roottools/RootTools.java
Normal file
848
app/src/main/java/com/stericson/roottools/RootTools.java
Normal file
|
|
@ -0,0 +1,848 @@
|
|||
/*
|
||||
* This file is part of the RootTools Project: http://code.google.com/p/RootTools/
|
||||
*
|
||||
* Copyright (c) 2012 Stephen Erickson, Chris Ravenscroft, Dominik Schuermann, Adam Shanks
|
||||
*
|
||||
* 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.roottools;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.util.Log;
|
||||
|
||||
import com.stericson.rootshell.RootShell;
|
||||
import com.stericson.rootshell.exceptions.RootDeniedException;
|
||||
import com.stericson.rootshell.execution.Command;
|
||||
import com.stericson.rootshell.execution.Shell;
|
||||
import com.stericson.roottools.containers.Mount;
|
||||
import com.stericson.roottools.containers.Permissions;
|
||||
import com.stericson.roottools.containers.Symlink;
|
||||
import com.stericson.roottools.internal.Remounter;
|
||||
import com.stericson.roottools.internal.RootToolsInternalMethods;
|
||||
import com.stericson.roottools.internal.Runner;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
public final class RootTools {
|
||||
|
||||
/**
|
||||
* This class is the gateway to every functionality within the RootTools library.The developer
|
||||
* should only have access to this class and this class only.This means that this class should
|
||||
* be the only one to be public.The rest of the classes within this library must not have the
|
||||
* public modifier.
|
||||
* <p/>
|
||||
* All methods and Variables that the developer may need to have access to should be here.
|
||||
* <p/>
|
||||
* If a method, or a specific functionality, requires a fair amount of code, or work to be done,
|
||||
* then that functionality should probably be moved to its own class and the call to it done
|
||||
* here.For examples of this being done, look at the remount functionality.
|
||||
*/
|
||||
|
||||
private static RootToolsInternalMethods rim = null;
|
||||
|
||||
public static void setRim(RootToolsInternalMethods rim) {
|
||||
RootTools.rim = rim;
|
||||
}
|
||||
|
||||
private static final RootToolsInternalMethods getInternals() {
|
||||
if (rim == null) {
|
||||
RootToolsInternalMethods.getInstance();
|
||||
return rim;
|
||||
} else {
|
||||
return rim;
|
||||
}
|
||||
}
|
||||
|
||||
// --------------------
|
||||
// # Public Variables #
|
||||
// --------------------
|
||||
|
||||
public static boolean debugMode = false;
|
||||
public static String utilPath;
|
||||
|
||||
/**
|
||||
* 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 default_Command_Timeout = 20000;
|
||||
|
||||
|
||||
// ---------------------------
|
||||
// # Public Variable Getters #
|
||||
// ---------------------------
|
||||
|
||||
// ------------------
|
||||
// # Public Methods #
|
||||
// ------------------
|
||||
|
||||
/**
|
||||
* This will check a given binary, determine if it exists and determine that it has either the
|
||||
* permissions 755, 775, or 777.
|
||||
*
|
||||
* @param util Name of the utility to check.
|
||||
* @return boolean to indicate whether the binary is installed and has appropriate permissions.
|
||||
*/
|
||||
public static boolean checkUtil(String util) {
|
||||
|
||||
return getInternals().checkUtil(util);
|
||||
}
|
||||
|
||||
/**
|
||||
* This will close all open shells.
|
||||
*
|
||||
* @throws IOException
|
||||
*/
|
||||
public static void closeAllShells() throws IOException {
|
||||
RootShell.closeAllShells();
|
||||
}
|
||||
|
||||
/**
|
||||
* This will close the custom shell that you opened.
|
||||
*
|
||||
* @throws IOException
|
||||
*/
|
||||
public static void closeCustomShell() throws IOException {
|
||||
RootShell.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.
|
||||
* @throws IOException
|
||||
*/
|
||||
public static void closeShell(boolean root) throws IOException {
|
||||
RootShell.closeShell(root);
|
||||
}
|
||||
|
||||
/**
|
||||
* Copys a file to a destination. Because cp is not available on all android devices, we have a
|
||||
* fallback on the cat command
|
||||
*
|
||||
* @param source example: /data/data/org.adaway/files/hosts
|
||||
* @param destination example: /system/etc/hosts
|
||||
* @param remountAsRw remounts the destination as read/write before writing to it
|
||||
* @param preserveFileAttributes tries to copy file attributes from source to destination, if only cat is available
|
||||
* only permissions are preserved
|
||||
* @return true if it was successfully copied
|
||||
*/
|
||||
public static boolean copyFile(String source, String destination, boolean remountAsRw,
|
||||
boolean preserveFileAttributes) {
|
||||
return getInternals().copyFile(source, destination, remountAsRw, preserveFileAttributes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes a file or directory
|
||||
*
|
||||
* @param target example: /data/data/org.adaway/files/hosts
|
||||
* @param remountAsRw remounts the destination as read/write before writing to it
|
||||
* @return true if it was successfully deleted
|
||||
*/
|
||||
public static boolean deleteFileOrDirectory(String target, boolean remountAsRw) {
|
||||
return getInternals().deleteFileOrDirectory(target, remountAsRw);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
return RootShell.exists(file, isDir);
|
||||
}
|
||||
|
||||
/**
|
||||
* This will try and fix a given binary. (This is for Busybox applets or Toolbox applets) By
|
||||
* "fix", I mean it will try and symlink the binary from either toolbox or Busybox and fix the
|
||||
* permissions if the permissions are not correct.
|
||||
*
|
||||
* @param util Name of the utility to fix.
|
||||
* @param utilPath path to the toolbox that provides ln, rm, and chmod. This can be a blank string, a
|
||||
* path to a binary that will provide these, or you can use
|
||||
* RootTools.getWorkingToolbox()
|
||||
*/
|
||||
public static void fixUtil(String util, String utilPath) {
|
||||
getInternals().fixUtil(util, utilPath);
|
||||
}
|
||||
|
||||
/**
|
||||
* This will check an array of binaries, determine if they exist and determine that it has
|
||||
* either the permissions 755, 775, or 777. If an applet is not setup correctly it will try and
|
||||
* fix it. (This is for Busybox applets or Toolbox applets)
|
||||
*
|
||||
* @param utils Name of the utility to check.
|
||||
* @return boolean to indicate whether the operation completed. Note that this is not indicative
|
||||
* of whether the problem was fixed, just that the method did not encounter any
|
||||
* exceptions.
|
||||
* @throws Exception if the operation cannot be completed.
|
||||
*/
|
||||
public static boolean fixUtils(String[] utils) throws Exception {
|
||||
return getInternals().fixUtils(utils);
|
||||
}
|
||||
|
||||
/**
|
||||
* @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 paths the binary was found at.
|
||||
*/
|
||||
public static List<String> findBinary(String binaryName, boolean singlePath) {
|
||||
return RootShell.findBinary(binaryName, singlePath);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param path String that represents the path to the Busybox binary you want to retrieve the version of.
|
||||
* @return BusyBox version is found, "" if not found.
|
||||
*/
|
||||
public static String getBusyBoxVersion(String path) {
|
||||
return getInternals().getBusyBoxVersion(path);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return BusyBox version is found, "" if not found.
|
||||
*/
|
||||
public static String getBusyBoxVersion() {
|
||||
return RootTools.getBusyBoxVersion("");
|
||||
}
|
||||
|
||||
/**
|
||||
* This will return an List of Strings. Each string represents an applet available from BusyBox.
|
||||
* <p/>
|
||||
*
|
||||
* @return <code>null</code> If we cannot return the list of applets.
|
||||
*/
|
||||
public static List<String> getBusyBoxApplets() throws Exception {
|
||||
return RootTools.getBusyBoxApplets("");
|
||||
}
|
||||
|
||||
/**
|
||||
* This will return an List of Strings. Each string represents an applet available from BusyBox.
|
||||
* <p/>
|
||||
*
|
||||
* @param path Path to the busybox binary that you want the list of applets from.
|
||||
* @return <code>null</code> If we cannot return the list of applets.
|
||||
*/
|
||||
public static List<String> getBusyBoxApplets(String path) throws Exception {
|
||||
return getInternals().getBusyBoxApplets(path);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 RootShell.getCustomShell(shellPath, timeout);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* @throws TimeoutException
|
||||
* @throws com.stericson.RootShell.exceptions.RootDeniedException
|
||||
* @throws IOException
|
||||
*/
|
||||
public static Shell getCustomShell(String shellPath) throws IOException, TimeoutException, RootDeniedException {
|
||||
return RootTools.getCustomShell(shellPath, 10000);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param file String that represent the file, including the full path to the file and its name.
|
||||
* @return An instance of the class permissions from which you can get the permissions of the
|
||||
* file or if the file could not be found or permissions couldn't be determined then
|
||||
* permissions will be null.
|
||||
*/
|
||||
public static Permissions getFilePermissionsSymlinks(String file) {
|
||||
return getInternals().getFilePermissionsSymlinks(file);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method will return the inode number of a file. This method is dependent on having a version of
|
||||
* ls that supports the -i parameter.
|
||||
*
|
||||
* @param file path to the file that you wish to return the inode number
|
||||
* @return String The inode number for this file or "" if the inode number could not be found.
|
||||
*/
|
||||
public static String getInode(String file) {
|
||||
return getInternals().getInode(file);
|
||||
}
|
||||
|
||||
/**
|
||||
* This will return an ArrayList of the class Mount. The class mount contains the following
|
||||
* property's: device mountPoint type flags
|
||||
* <p/>
|
||||
* These will provide you with any information you need to work with the mount points.
|
||||
*
|
||||
* @return <code>ArrayList<Mount></code> an ArrayList of the class Mount.
|
||||
* @throws Exception if we cannot return the mount points.
|
||||
*/
|
||||
public static ArrayList<Mount> getMounts() throws Exception {
|
||||
return getInternals().getMounts();
|
||||
}
|
||||
|
||||
/**
|
||||
* This will tell you how the specified mount is mounted. rw, ro, etc...
|
||||
* <p/>
|
||||
*
|
||||
* @param path The mount you want to check
|
||||
* @return <code>String</code> What the mount is mounted as.
|
||||
* @throws Exception if we cannot determine how the mount is mounted.
|
||||
*/
|
||||
public static String getMountedAs(String path) throws Exception {
|
||||
return getInternals().getMountedAs(path);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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...
|
||||
* @throws TimeoutException
|
||||
* @throws com.stericson.RootShell.exceptions.RootDeniedException
|
||||
* @throws IOException
|
||||
*/
|
||||
public static Shell getShell(boolean root, int timeout, Shell.ShellContext shellContext, int retry) throws IOException, TimeoutException, RootDeniedException {
|
||||
return RootShell.getShell(root, timeout, shellContext, retry);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
* @throws TimeoutException
|
||||
* @throws com.stericson.RootShell.exceptions.RootDeniedException
|
||||
* @throws IOException
|
||||
*/
|
||||
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
|
||||
* @throws TimeoutException
|
||||
* @throws com.stericson.RootShell.exceptions.RootDeniedException
|
||||
* @throws IOException
|
||||
*/
|
||||
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.
|
||||
* @throws TimeoutException
|
||||
* @throws com.stericson.RootShell.exceptions.RootDeniedException
|
||||
* @throws IOException
|
||||
*/
|
||||
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
|
||||
* @throws TimeoutException
|
||||
* @throws com.stericson.RootShell.exceptions.RootDeniedException
|
||||
* @throws IOException
|
||||
*/
|
||||
public static Shell getShell(boolean root) throws IOException, TimeoutException, RootDeniedException {
|
||||
return RootTools.getShell(root, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the space for a desired partition.
|
||||
*
|
||||
* @param path The partition to find the space for.
|
||||
* @return the amount if space found within the desired partition. If the space was not found
|
||||
* then the value is -1
|
||||
* @throws TimeoutException
|
||||
*/
|
||||
public static long getSpace(String path) {
|
||||
return getInternals().getSpace(path);
|
||||
}
|
||||
|
||||
/**
|
||||
* This will return a String that represent the symlink for a specified file.
|
||||
* <p/>
|
||||
*
|
||||
* @param file path to the file to get the Symlink for. (must have absolute path)
|
||||
* @return <code>String</code> a String that represent the symlink for a specified file or an
|
||||
* empty string if no symlink exists.
|
||||
*/
|
||||
public static String getSymlink(String file) {
|
||||
return getInternals().getSymlink(file);
|
||||
}
|
||||
|
||||
/**
|
||||
* This will return an ArrayList of the class Symlink. The class Symlink contains the following
|
||||
* property's: path SymplinkPath
|
||||
* <p/>
|
||||
* These will provide you with any Symlinks in the given path.
|
||||
*
|
||||
* @param path path to search for Symlinks.
|
||||
* @return <code>ArrayList<Symlink></code> an ArrayList of the class Symlink.
|
||||
* @throws Exception if we cannot return the Symlinks.
|
||||
*/
|
||||
public static ArrayList<Symlink> getSymlinks(String path) throws Exception {
|
||||
return getInternals().getSymlinks(path);
|
||||
}
|
||||
|
||||
/**
|
||||
* This will return to you a string to be used in your shell commands which will represent the
|
||||
* valid working toolbox with correct permissions. For instance, if Busybox is available it will
|
||||
* return "busybox", if busybox is not available but toolbox is then it will return "toolbox"
|
||||
*
|
||||
* @return String that indicates the available toolbox to use for accessing applets.
|
||||
*/
|
||||
public static String getWorkingToolbox() {
|
||||
return getInternals().getWorkingToolbox();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if there is enough Space on SDCard
|
||||
*
|
||||
* @param updateSize size to Check (long)
|
||||
* @return <code>true</code> if the Update will fit on SDCard, <code>false</code> if not enough
|
||||
* space on SDCard. Will also return <code>false</code>, if the SDCard is not mounted as
|
||||
* read/write
|
||||
*/
|
||||
public static boolean hasEnoughSpaceOnSdCard(long updateSize) {
|
||||
return getInternals().hasEnoughSpaceOnSdCard(updateSize);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the toolbox or busybox binary contains a specific util
|
||||
*
|
||||
* @param util
|
||||
* @param box Should contain "toolbox" or "busybox"
|
||||
* @return true if it contains this util
|
||||
*/
|
||||
public static boolean hasUtil(final String util, final String box) {
|
||||
//TODO Convert this to use the new shell.
|
||||
return getInternals().hasUtil(util, box);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method can be used to unpack a binary from the raw resources folder and store it in
|
||||
* /data/data/app.package/files/ This is typically useful if you provide your own C- or
|
||||
* C++-based binary. This binary can then be executed using sendShell() and its full path.
|
||||
*
|
||||
* @param context the current activity's <code>Context</code>
|
||||
* @param sourceId resource id; typically <code>R.raw.id</code>
|
||||
* @param destName destination file name; appended to /data/data/app.package/files/
|
||||
* @param mode chmod value for this file
|
||||
* @return a <code>boolean</code> which indicates whether or not we were able to create the new
|
||||
* file.
|
||||
*/
|
||||
public static boolean installBinary(Context context, int sourceId, String destName, String mode) {
|
||||
return getInternals().installBinary(context, sourceId, destName, mode);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method can be used to unpack a binary from the raw resources folder and store it in
|
||||
* /data/data/app.package/files/ This is typically useful if you provide your own C- or
|
||||
* C++-based binary. This binary can then be executed using sendShell() and its full path.
|
||||
*
|
||||
* @param context the current activity's <code>Context</code>
|
||||
* @param sourceId resource id; typically <code>R.raw.id</code>
|
||||
* @param binaryName destination file name; appended to /data/data/app.package/files/
|
||||
* @return a <code>boolean</code> which indicates whether or not we were able to create the new
|
||||
* file.
|
||||
*/
|
||||
public static boolean installBinary(Context context, int sourceId, String binaryName) {
|
||||
return installBinary(context, sourceId, binaryName, "700");
|
||||
}
|
||||
|
||||
/**
|
||||
* This method checks whether a binary is installed.
|
||||
*
|
||||
* @param context the current activity's <code>Context</code>
|
||||
* @param binaryName binary file name; appended to /data/data/app.package/files/
|
||||
* @return a <code>boolean</code> which indicates whether or not
|
||||
* the binary already exists.
|
||||
*/
|
||||
public static boolean hasBinary(Context context, String binaryName) {
|
||||
return getInternals().isBinaryAvailable(context, binaryName);
|
||||
}
|
||||
|
||||
/**
|
||||
* This will let you know if an applet is available from BusyBox
|
||||
* <p/>
|
||||
*
|
||||
* @param applet The applet to check for.
|
||||
* @param path Path to the busybox binary that you want to check. (do not include binary name)
|
||||
* @return <code>true</code> if applet is available, false otherwise.
|
||||
*/
|
||||
public static boolean isAppletAvailable(String applet, String path) {
|
||||
return getInternals().isAppletAvailable(applet, path);
|
||||
}
|
||||
|
||||
/**
|
||||
* This will let you know if an applet is available from BusyBox
|
||||
* <p/>
|
||||
*
|
||||
* @param applet The applet to check for.
|
||||
* @return <code>true</code> if applet is available, false otherwise.
|
||||
*/
|
||||
public static boolean isAppletAvailable(String applet) {
|
||||
return RootTools.isAppletAvailable(applet, "");
|
||||
}
|
||||
/**
|
||||
* @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 RootShell.isAccessGiven();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
return RootShell.isAccessGiven(timeout, retries);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return <code>true</code> if BusyBox was found.
|
||||
*/
|
||||
public static boolean isBusyboxAvailable() {
|
||||
return RootShell.isBusyboxAvailable();
|
||||
}
|
||||
|
||||
public static boolean isNativeToolsReady(int nativeToolsId, Context context) {
|
||||
return getInternals().isNativeToolsReady(nativeToolsId, context);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method can be used to to check if a process is running
|
||||
*
|
||||
* @param processName name of process to check
|
||||
* @return <code>true</code> if process was found
|
||||
* @throws TimeoutException (Could not determine if the process is running)
|
||||
*/
|
||||
public static boolean isProcessRunning(final String processName) {
|
||||
//TODO convert to new shell
|
||||
return getInternals().isProcessRunning(processName);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return <code>true</code> if su was found.
|
||||
*/
|
||||
public static boolean isRootAvailable() {
|
||||
return RootShell.isRootAvailable();
|
||||
}
|
||||
|
||||
/**
|
||||
* This method can be used to kill a running process
|
||||
*
|
||||
* @param processName name of process to kill
|
||||
* @return <code>true</code> if process was found and killed successfully
|
||||
*/
|
||||
public static boolean killProcess(final String processName) {
|
||||
//TODO convert to new shell
|
||||
return getInternals().killProcess(processName);
|
||||
}
|
||||
|
||||
/**
|
||||
* This will launch the Android market looking for BusyBox
|
||||
*
|
||||
* @param activity pass in your Activity
|
||||
*/
|
||||
public static void offerBusyBox(Activity activity) {
|
||||
getInternals().offerBusyBox(activity);
|
||||
}
|
||||
|
||||
/**
|
||||
* This will launch the Android market looking for BusyBox, but will return the intent fired and
|
||||
* starts the activity with startActivityForResult
|
||||
*
|
||||
* @param activity pass in your Activity
|
||||
* @param requestCode pass in the request code
|
||||
* @return intent fired
|
||||
*/
|
||||
public static Intent offerBusyBox(Activity activity, int requestCode) {
|
||||
return getInternals().offerBusyBox(activity, requestCode);
|
||||
}
|
||||
|
||||
/**
|
||||
* This will launch the Android market looking for SuperUser
|
||||
*
|
||||
* @param activity pass in your Activity
|
||||
*/
|
||||
public static void offerSuperUser(Activity activity) {
|
||||
getInternals().offerSuperUser(activity);
|
||||
}
|
||||
|
||||
/**
|
||||
* This will launch the Android market looking for SuperUser, but will return the intent fired
|
||||
* and starts the activity with startActivityForResult
|
||||
*
|
||||
* @param activity pass in your Activity
|
||||
* @param requestCode pass in the request code
|
||||
* @return intent fired
|
||||
*/
|
||||
public static Intent offerSuperUser(Activity activity, int requestCode) {
|
||||
return getInternals().offerSuperUser(activity, requestCode);
|
||||
}
|
||||
|
||||
/**
|
||||
* This will take a path, which can contain the file name as well, and attempt to remount the
|
||||
* underlying partition.
|
||||
* <p/>
|
||||
* For example, passing in the following string:
|
||||
* "/system/bin/some/directory/that/really/would/never/exist" will result in /system ultimately
|
||||
* being remounted. However, keep in mind that the longer the path you supply, the more work
|
||||
* this has to do, and the slower it will run.
|
||||
*
|
||||
* @param file file path
|
||||
* @param mountType mount type: pass in RO (Read only) or RW (Read Write)
|
||||
* @return a <code>boolean</code> which indicates whether or not the partition has been
|
||||
* remounted as specified.
|
||||
*/
|
||||
public static boolean remount(String file, String mountType) {
|
||||
// Recieved a request, get an instance of Remounter
|
||||
Remounter remounter = new Remounter();
|
||||
// send the request.
|
||||
return (remounter.remount(file, mountType));
|
||||
}
|
||||
|
||||
public static boolean remount(String file, String mountType, String customPath) {
|
||||
// Recieved a request, get an instance of Remounter
|
||||
Remounter remounter = new Remounter(customPath);
|
||||
// send the request.
|
||||
return (remounter.remount(file, mountType));
|
||||
}
|
||||
|
||||
/**
|
||||
* This restarts only Android OS without rebooting the whole device. This does NOT work on all
|
||||
* devices. This is done by killing the main init process named zygote. Zygote is restarted
|
||||
* automatically by Android after killing it.
|
||||
*
|
||||
* @throws TimeoutException
|
||||
*/
|
||||
/* public static void restartAndroid() {
|
||||
RootTools.log("Restart Android");
|
||||
killProcess("zygote");
|
||||
}*/
|
||||
|
||||
/**
|
||||
* Executes binary in a separated process. Before using this method, the binary has to be
|
||||
* installed in /data/data/app.package/files/ using the installBinary method.
|
||||
*
|
||||
* @param context the current activity's <code>Context</code>
|
||||
* @param binaryName name of installed binary
|
||||
* @param parameter parameter to append to binary like "-vxf"
|
||||
*/
|
||||
public static void runBinary(Context context, String binaryName, String parameter) {
|
||||
Runner runner = new Runner(context, binaryName, parameter);
|
||||
runner.start();
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes a given command with root access or without depending on the value of the boolean passed.
|
||||
* This will also start a root shell or a standard shell without you having to open it specifically.
|
||||
* <p/>
|
||||
* You will still need to close the shell after you are done using the shell.
|
||||
*
|
||||
* @param shell The shell to execute the command on, this can be a root shell or a standard shell.
|
||||
* @param command The command to execute in the shell
|
||||
*
|
||||
* @throws IOException
|
||||
*/
|
||||
public static void runShellCommand(Shell shell, Command command) throws IOException {
|
||||
shell.add(command);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 RootTools.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, 3, 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 RootTools.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, 3, 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 RootTools.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
|
||||
* @param e The exception that was thrown (Needed for errors)
|
||||
*/
|
||||
public static void log(String msg, int 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 RootTools.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, int type, Exception e) {
|
||||
if (msg != null && !msg.equals("")) {
|
||||
if (debugMode) {
|
||||
if (TAG == null) {
|
||||
TAG = Constants.TAG;
|
||||
}
|
||||
|
||||
switch (type) {
|
||||
case 1:
|
||||
Log.v(TAG, msg);
|
||||
break;
|
||||
case 2:
|
||||
Log.e(TAG, msg, e);
|
||||
break;
|
||||
case 3:
|
||||
Log.d(TAG, msg);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,459 @@
|
|||
/*
|
||||
* This file is part of the RootTools Project: http://code.google.com/p/RootTools/
|
||||
*
|
||||
* Copyright (c) 2012 Stephen Erickson, Chris Ravenscroft, Dominik Schuermann, Adam Shanks
|
||||
*
|
||||
* 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.roottools;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.ProgressDialog;
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.Message;
|
||||
import android.os.StrictMode;
|
||||
import android.widget.ScrollView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.stericson.rootshell.exceptions.RootDeniedException;
|
||||
import com.stericson.rootshell.execution.Command;
|
||||
import com.stericson.rootshell.execution.Shell;
|
||||
import com.stericson.roottools.containers.Permissions;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
public class SanityCheckRootTools extends Activity {
|
||||
private ScrollView mScrollView;
|
||||
private TextView mTextView;
|
||||
private ProgressDialog mPDialog;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
|
||||
.detectDiskReads()
|
||||
.detectDiskWrites()
|
||||
.detectNetwork() // or .detectAll() for all detectable problems
|
||||
.penaltyLog()
|
||||
.build());
|
||||
StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder()
|
||||
.detectLeakedSqlLiteObjects()
|
||||
.detectLeakedClosableObjects()
|
||||
.penaltyLog()
|
||||
.penaltyDeath()
|
||||
.build());
|
||||
|
||||
RootTools.debugMode = true;
|
||||
|
||||
mTextView = new TextView(this);
|
||||
mTextView.setText("");
|
||||
mScrollView = new ScrollView(this);
|
||||
mScrollView.addView(mTextView);
|
||||
setContentView(mScrollView);
|
||||
|
||||
print("SanityCheckRootTools \n\n");
|
||||
|
||||
if (RootTools.isRootAvailable()) {
|
||||
print("Root found.\n");
|
||||
} else {
|
||||
print("Root not found");
|
||||
}
|
||||
|
||||
try {
|
||||
Shell.startRootShell();
|
||||
} catch (IOException e2) {
|
||||
// TODO Auto-generated catch block
|
||||
e2.printStackTrace();
|
||||
} catch (TimeoutException e) {
|
||||
print("[ TIMEOUT EXCEPTION! ]\n");
|
||||
e.printStackTrace();
|
||||
} catch (RootDeniedException e) {
|
||||
print("[ ROOT DENIED EXCEPTION! ]\n");
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
try {
|
||||
if (!RootTools.isAccessGiven()) {
|
||||
print("ERROR: No root access to this device.\n");
|
||||
return;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
print("ERROR: could not determine root access to this device.\n");
|
||||
return;
|
||||
}
|
||||
|
||||
// Display infinite progress bar
|
||||
mPDialog = new ProgressDialog(this);
|
||||
mPDialog.setCancelable(false);
|
||||
mPDialog.setProgressStyle(ProgressDialog.STYLE_SPINNER);
|
||||
|
||||
new SanityCheckThread(this, new TestHandler()).start();
|
||||
}
|
||||
|
||||
protected void print(CharSequence text) {
|
||||
mTextView.append(text);
|
||||
mScrollView.post(new Runnable() {
|
||||
public void run() {
|
||||
mScrollView.fullScroll(ScrollView.FOCUS_DOWN);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Run our long-running tests in their separate thread so as to
|
||||
// not interfere with proper rendering.
|
||||
private class SanityCheckThread extends Thread {
|
||||
private final Handler mHandler;
|
||||
|
||||
public SanityCheckThread(Context context, Handler handler) {
|
||||
mHandler = handler;
|
||||
}
|
||||
|
||||
public void run() {
|
||||
visualUpdate(TestHandler.ACTION_SHOW, null);
|
||||
|
||||
// First test: Install a binary file for future use
|
||||
// if it wasn't already installed.
|
||||
/*
|
||||
visualUpdate(TestHandler.ACTION_PDISPLAY, "Installing binary if needed");
|
||||
if(false == RootTools.installBinary(mContext, R.raw.nes, "nes_binary")) {
|
||||
visualUpdate(TestHandler.ACTION_HIDE, "ERROR: Failed to install binary. Please see log file.");
|
||||
return;
|
||||
}
|
||||
*/
|
||||
|
||||
boolean result;
|
||||
|
||||
visualUpdate(TestHandler.ACTION_PDISPLAY, "Testing getPath");
|
||||
visualUpdate(TestHandler.ACTION_DISPLAY, "[ getPath ]\n");
|
||||
|
||||
try {
|
||||
List<String> paths = RootTools.getPath();
|
||||
|
||||
for (String path : paths) {
|
||||
visualUpdate(TestHandler.ACTION_DISPLAY, path + " k\n\n");
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
visualUpdate(TestHandler.ACTION_PDISPLAY, "Testing A ton of commands");
|
||||
visualUpdate(TestHandler.ACTION_DISPLAY, "[ Ton of Commands ]\n");
|
||||
|
||||
for (int i = 0; i < 100; i++) {
|
||||
RootTools.exists("/system/xbin/busybox");
|
||||
}
|
||||
|
||||
visualUpdate(TestHandler.ACTION_PDISPLAY, "Testing Find Binary");
|
||||
result = RootTools.isRootAvailable();
|
||||
visualUpdate(TestHandler.ACTION_DISPLAY, "[ Checking Root ]\n");
|
||||
visualUpdate(TestHandler.ACTION_DISPLAY, result + " k\n\n");
|
||||
|
||||
visualUpdate(TestHandler.ACTION_PDISPLAY, "Testing file exists");
|
||||
visualUpdate(TestHandler.ACTION_DISPLAY, "[ Checking Exists() ]\n");
|
||||
visualUpdate(TestHandler.ACTION_DISPLAY, RootTools.exists("/system/sbin/[") + " k\n\n");
|
||||
|
||||
visualUpdate(TestHandler.ACTION_PDISPLAY, "Testing Is Access Given");
|
||||
result = RootTools.isAccessGiven();
|
||||
visualUpdate(TestHandler.ACTION_DISPLAY, "[ Checking for Access to Root ]\n");
|
||||
visualUpdate(TestHandler.ACTION_DISPLAY, result + " k\n\n");
|
||||
|
||||
visualUpdate(TestHandler.ACTION_PDISPLAY, "Testing Remount");
|
||||
result = RootTools.remount("/system", "rw");
|
||||
visualUpdate(TestHandler.ACTION_DISPLAY, "[ Remounting System as RW ]\n");
|
||||
visualUpdate(TestHandler.ACTION_DISPLAY, result + " k\n\n");
|
||||
|
||||
visualUpdate(TestHandler.ACTION_PDISPLAY, "Testing CheckUtil");
|
||||
visualUpdate(TestHandler.ACTION_DISPLAY, "[ Checking busybox is setup ]\n");
|
||||
visualUpdate(TestHandler.ACTION_DISPLAY, RootTools.checkUtil("busybox") + " k\n\n");
|
||||
|
||||
visualUpdate(TestHandler.ACTION_PDISPLAY, "Testing getBusyBoxVersion");
|
||||
visualUpdate(TestHandler.ACTION_DISPLAY, "[ Checking busybox version ]\n");
|
||||
visualUpdate(TestHandler.ACTION_DISPLAY, RootTools.getBusyBoxVersion("/system/xbin/") + " k\n\n");
|
||||
|
||||
try {
|
||||
visualUpdate(TestHandler.ACTION_PDISPLAY, "Testing fixUtils");
|
||||
visualUpdate(TestHandler.ACTION_DISPLAY, "[ Checking Utils ]\n");
|
||||
visualUpdate(TestHandler.ACTION_DISPLAY, RootTools.fixUtils(new String[]{"ls", "rm", "ln", "dd", "chmod", "mount"}) + " k\n\n");
|
||||
} catch (Exception e2) {
|
||||
// TODO Auto-generated catch block
|
||||
e2.printStackTrace();
|
||||
}
|
||||
|
||||
try {
|
||||
visualUpdate(TestHandler.ACTION_PDISPLAY, "Testing getSymlink");
|
||||
visualUpdate(TestHandler.ACTION_DISPLAY, "[ Checking [[ for symlink ]\n");
|
||||
visualUpdate(TestHandler.ACTION_DISPLAY, RootTools.getSymlink("/system/bin/[[") + " k\n\n");
|
||||
} catch (Exception e2) {
|
||||
// TODO Auto-generated catch block
|
||||
e2.printStackTrace();
|
||||
}
|
||||
|
||||
visualUpdate(TestHandler.ACTION_PDISPLAY, "Testing getInode");
|
||||
visualUpdate(TestHandler.ACTION_DISPLAY, "[ Checking Inodes ]\n");
|
||||
visualUpdate(TestHandler.ACTION_DISPLAY, RootTools.getInode("/system/bin/busybox") + " k\n\n");
|
||||
|
||||
visualUpdate(TestHandler.ACTION_PDISPLAY, "Testing GetBusyBoxapplets");
|
||||
try {
|
||||
|
||||
visualUpdate(TestHandler.ACTION_DISPLAY, "[ Getting all available Busybox applets ]\n");
|
||||
for (String applet : RootTools.getBusyBoxApplets("/data/data/stericson.busybox/files/bb/busybox")) {
|
||||
visualUpdate(TestHandler.ACTION_DISPLAY, applet + " k\n\n");
|
||||
}
|
||||
|
||||
} catch (Exception e1) {
|
||||
// TODO Auto-generated catch block
|
||||
e1.printStackTrace();
|
||||
}
|
||||
|
||||
visualUpdate(TestHandler.ACTION_PDISPLAY, "Testing GetBusyBox version in a special directory!");
|
||||
try {
|
||||
|
||||
visualUpdate(TestHandler.ACTION_DISPLAY, "[ Testing GetBusyBox version in a special directory! ]\n");
|
||||
String v = RootTools.getBusyBoxVersion("/data/data/stericson.busybox/files/bb/");
|
||||
|
||||
visualUpdate(TestHandler.ACTION_DISPLAY, v + " k\n\n");
|
||||
|
||||
} catch (Exception e1) {
|
||||
// TODO Auto-generated catch block
|
||||
e1.printStackTrace();
|
||||
}
|
||||
|
||||
visualUpdate(TestHandler.ACTION_PDISPLAY, "Testing getFilePermissionsSymlinks");
|
||||
Permissions permissions = RootTools.getFilePermissionsSymlinks("/system/xbin/busybox");
|
||||
visualUpdate(TestHandler.ACTION_DISPLAY, "[ Checking busybox permissions and symlink ]\n");
|
||||
|
||||
if (permissions != null) {
|
||||
visualUpdate(TestHandler.ACTION_DISPLAY, "Symlink: " + permissions.getSymlink() + " k\n\n");
|
||||
visualUpdate(TestHandler.ACTION_DISPLAY, "Group Permissions: " + permissions.getGroupPermissions() + " k\n\n");
|
||||
visualUpdate(TestHandler.ACTION_DISPLAY, "Owner Permissions: " + permissions.getOtherPermissions() + " k\n\n");
|
||||
visualUpdate(TestHandler.ACTION_DISPLAY, "Permissions: " + permissions.getPermissions() + " k\n\n");
|
||||
visualUpdate(TestHandler.ACTION_DISPLAY, "Type: " + permissions.getType() + " k\n\n");
|
||||
visualUpdate(TestHandler.ACTION_DISPLAY, "User Permissions: " + permissions.getUserPermissions() + " k\n\n");
|
||||
} else {
|
||||
visualUpdate(TestHandler.ACTION_DISPLAY, "Permissions == null k\n\n");
|
||||
}
|
||||
|
||||
Shell shell;
|
||||
|
||||
visualUpdate(TestHandler.ACTION_PDISPLAY, "Testing output capture");
|
||||
visualUpdate(TestHandler.ACTION_DISPLAY, "[ busybox ash --help ]\n");
|
||||
|
||||
try {
|
||||
shell = RootTools.getShell(true);
|
||||
Command cmd = new Command(
|
||||
0,
|
||||
"busybox ash --help") {
|
||||
|
||||
@Override
|
||||
public void commandOutput(int id, String line) {
|
||||
visualUpdate(TestHandler.ACTION_DISPLAY, line + "\n");
|
||||
super.commandOutput(id, line);
|
||||
}
|
||||
};
|
||||
shell.add(cmd);
|
||||
|
||||
visualUpdate(TestHandler.ACTION_PDISPLAY, "getevent - /dev/input/event0");
|
||||
visualUpdate(TestHandler.ACTION_DISPLAY, "[ getevent - /dev/input/event0 ]\n");
|
||||
|
||||
cmd = new Command(0, 0, "getevent /dev/input/event0") {
|
||||
@Override
|
||||
public void commandOutput(int id, String line) {
|
||||
visualUpdate(TestHandler.ACTION_DISPLAY, line + "\n");
|
||||
super.commandOutput(id, line);
|
||||
}
|
||||
|
||||
};
|
||||
shell.add(cmd);
|
||||
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
visualUpdate(TestHandler.ACTION_PDISPLAY, "Switching RootContext - SYSTEM_APP");
|
||||
visualUpdate(TestHandler.ACTION_DISPLAY, "[ Switching Root Context - SYSTEM_APP ]\n");
|
||||
|
||||
try {
|
||||
shell = RootTools.getShell(true, Shell.ShellContext.SYSTEM_APP);
|
||||
Command cmd = new Command(
|
||||
0,
|
||||
"id") {
|
||||
|
||||
@Override
|
||||
public void commandOutput(int id, String line) {
|
||||
visualUpdate(TestHandler.ACTION_DISPLAY, line + "\n");
|
||||
super.commandOutput(id, line);
|
||||
}
|
||||
};
|
||||
shell.add(cmd);
|
||||
|
||||
visualUpdate(TestHandler.ACTION_PDISPLAY, "Testing PM");
|
||||
visualUpdate(TestHandler.ACTION_DISPLAY, "[ Testing pm list packages -d ]\n");
|
||||
|
||||
cmd = new Command(
|
||||
0,
|
||||
"sh /system/bin/pm list packages -d") {
|
||||
|
||||
@Override
|
||||
public void commandOutput(int id, String line) {
|
||||
visualUpdate(TestHandler.ACTION_DISPLAY, line + "\n");
|
||||
super.commandOutput(id, line);
|
||||
}
|
||||
};
|
||||
shell.add(cmd);
|
||||
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
visualUpdate(TestHandler.ACTION_PDISPLAY, "Switching RootContext - UNTRUSTED");
|
||||
visualUpdate(TestHandler.ACTION_DISPLAY, "[ Switching Root Context - UNTRUSTED ]\n");
|
||||
|
||||
try {
|
||||
shell = RootTools.getShell(true, Shell.ShellContext.UNTRUSTED_APP);
|
||||
Command cmd = new Command(
|
||||
0,
|
||||
"id") {
|
||||
|
||||
@Override
|
||||
public void commandOutput(int id, String line) {
|
||||
visualUpdate(TestHandler.ACTION_DISPLAY, line + "\n");
|
||||
super.commandOutput(id, line);
|
||||
}
|
||||
};
|
||||
shell.add(cmd);
|
||||
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
visualUpdate(TestHandler.ACTION_PDISPLAY, "Testing df");
|
||||
long spaceValue = RootTools.getSpace("/data");
|
||||
visualUpdate(TestHandler.ACTION_DISPLAY, "[ Checking /data partition size]\n");
|
||||
visualUpdate(TestHandler.ACTION_DISPLAY, spaceValue + "k\n\n");
|
||||
|
||||
try {
|
||||
shell = RootTools.getShell(true);
|
||||
|
||||
Command cmd = new Command(42, false, "echo done") {
|
||||
|
||||
boolean _catch = false;
|
||||
|
||||
@Override
|
||||
public void commandOutput(int id, String line) {
|
||||
if (_catch) {
|
||||
RootTools.log("CAUGHT!!!");
|
||||
}
|
||||
|
||||
super.commandOutput(id, line);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void commandTerminated(int id, String reason) {
|
||||
synchronized (SanityCheckRootTools.this) {
|
||||
|
||||
_catch = true;
|
||||
visualUpdate(TestHandler.ACTION_PDISPLAY, "All tests complete.");
|
||||
visualUpdate(TestHandler.ACTION_HIDE, null);
|
||||
|
||||
try {
|
||||
RootTools.closeAllShells();
|
||||
} catch (IOException e) {
|
||||
// TODO Auto-generated catch block
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void commandCompleted(int id, int exitCode) {
|
||||
synchronized (SanityCheckRootTools.this) {
|
||||
_catch = true;
|
||||
|
||||
visualUpdate(TestHandler.ACTION_PDISPLAY, "All tests complete.");
|
||||
visualUpdate(TestHandler.ACTION_HIDE, null);
|
||||
|
||||
try {
|
||||
RootTools.closeAllShells();
|
||||
} catch (IOException e) {
|
||||
// TODO Auto-generated catch block
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
shell.add(cmd);
|
||||
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void visualUpdate(int action, String text) {
|
||||
Message msg = mHandler.obtainMessage();
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putInt(TestHandler.ACTION, action);
|
||||
bundle.putString(TestHandler.TEXT, text);
|
||||
msg.setData(bundle);
|
||||
mHandler.sendMessage(msg);
|
||||
}
|
||||
}
|
||||
|
||||
private class TestHandler extends Handler {
|
||||
static final public String ACTION = "action";
|
||||
static final public int ACTION_SHOW = 0x01;
|
||||
static final public int ACTION_HIDE = 0x02;
|
||||
static final public int ACTION_DISPLAY = 0x03;
|
||||
static final public int ACTION_PDISPLAY = 0x04;
|
||||
static final public String TEXT = "text";
|
||||
|
||||
public void handleMessage(Message msg) {
|
||||
int action = msg.getData().getInt(ACTION);
|
||||
String text = msg.getData().getString(TEXT);
|
||||
|
||||
switch (action) {
|
||||
case ACTION_SHOW:
|
||||
mPDialog.show();
|
||||
mPDialog.setMessage("Running Root Library Tests...");
|
||||
break;
|
||||
case ACTION_HIDE:
|
||||
if (null != text) {
|
||||
print(text);
|
||||
}
|
||||
mPDialog.hide();
|
||||
break;
|
||||
case ACTION_DISPLAY:
|
||||
print(text);
|
||||
break;
|
||||
case ACTION_PDISPLAY:
|
||||
mPDialog.setMessage(text);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,70 @@
|
|||
/*
|
||||
* This file is part of the RootTools Project: http://code.google.com/p/RootTools/
|
||||
*
|
||||
* Copyright (c) 2012 Stephen Erickson, Chris Ravenscroft, Dominik Schuermann, Adam Shanks
|
||||
*
|
||||
* 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.roottools.containers;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Arrays;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Set;
|
||||
|
||||
public class Mount
|
||||
{
|
||||
final File mDevice;
|
||||
final File mMountPoint;
|
||||
final String mType;
|
||||
final Set<String> mFlags;
|
||||
|
||||
public Mount(File device, File path, String type, String flagsStr)
|
||||
{
|
||||
mDevice = device;
|
||||
mMountPoint = path;
|
||||
mType = type;
|
||||
mFlags = new LinkedHashSet<String>(Arrays.asList(flagsStr.split(",")));
|
||||
}
|
||||
|
||||
public File getDevice()
|
||||
{
|
||||
return mDevice;
|
||||
}
|
||||
|
||||
public File getMountPoint()
|
||||
{
|
||||
return mMountPoint;
|
||||
}
|
||||
|
||||
public String getType()
|
||||
{
|
||||
return mType;
|
||||
}
|
||||
|
||||
public Set<String> getFlags()
|
||||
{
|
||||
return mFlags;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return String.format("%s on %s type %s %s", mDevice, mMountPoint, mType, mFlags);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,125 @@
|
|||
/*
|
||||
* This file is part of the RootTools Project: http://code.google.com/p/RootTools/
|
||||
*
|
||||
* Copyright (c) 2012 Stephen Erickson, Chris Ravenscroft, Dominik Schuermann, Adam Shanks
|
||||
*
|
||||
* 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.roottools.containers;
|
||||
|
||||
public class Permissions
|
||||
{
|
||||
String type;
|
||||
String user;
|
||||
String group;
|
||||
String other;
|
||||
String symlink;
|
||||
int permissions;
|
||||
|
||||
public String getSymlink()
|
||||
{
|
||||
return this.symlink;
|
||||
}
|
||||
|
||||
public String getType()
|
||||
{
|
||||
return type;
|
||||
}
|
||||
|
||||
public int getPermissions()
|
||||
{
|
||||
return this.permissions;
|
||||
}
|
||||
|
||||
public String getUserPermissions()
|
||||
{
|
||||
return this.user;
|
||||
}
|
||||
|
||||
public String getGroupPermissions()
|
||||
{
|
||||
return this.group;
|
||||
}
|
||||
|
||||
public String getOtherPermissions()
|
||||
{
|
||||
return this.other;
|
||||
}
|
||||
|
||||
public void setSymlink(String symlink)
|
||||
{
|
||||
this.symlink = symlink;
|
||||
}
|
||||
|
||||
public void setType(String type)
|
||||
{
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public void setPermissions(int permissions)
|
||||
{
|
||||
this.permissions = permissions;
|
||||
}
|
||||
|
||||
public void setUserPermissions(String user)
|
||||
{
|
||||
this.user = user;
|
||||
}
|
||||
|
||||
public void setGroupPermissions(String group)
|
||||
{
|
||||
this.group = group;
|
||||
}
|
||||
|
||||
public void setOtherPermissions(String other)
|
||||
{
|
||||
this.other = other;
|
||||
}
|
||||
|
||||
public String getUser()
|
||||
{
|
||||
return user;
|
||||
}
|
||||
|
||||
public void setUser(String user)
|
||||
{
|
||||
this.user = user;
|
||||
}
|
||||
|
||||
public String getGroup()
|
||||
{
|
||||
return group;
|
||||
}
|
||||
|
||||
public void setGroup(String group)
|
||||
{
|
||||
this.group = group;
|
||||
}
|
||||
|
||||
public String getOther()
|
||||
{
|
||||
return other;
|
||||
}
|
||||
|
||||
public void setOther(String other)
|
||||
{
|
||||
this.other = other;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
/*
|
||||
* This file is part of the RootTools Project: http://code.google.com/p/RootTools/
|
||||
*
|
||||
* Copyright (c) 2012 Stephen Erickson, Chris Ravenscroft, Dominik Schuermann, Adam Shanks
|
||||
*
|
||||
* 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.roottools.containers;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
public class Symlink
|
||||
{
|
||||
protected final File file;
|
||||
protected final File symlinkPath;
|
||||
|
||||
public Symlink(File file, File path)
|
||||
{
|
||||
this.file = file;
|
||||
symlinkPath = path;
|
||||
}
|
||||
|
||||
public File getFile()
|
||||
{
|
||||
return this.file;
|
||||
}
|
||||
|
||||
public File getSymlinkPath()
|
||||
{
|
||||
return symlinkPath;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,300 @@
|
|||
/*
|
||||
* This file is part of the RootTools Project: http://code.google.com/p/RootTools/
|
||||
*
|
||||
* Copyright (c) 2012 Stephen Erickson, Chris Ravenscroft, Dominik Schuermann, Adam Shanks
|
||||
*
|
||||
* 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.roottools.internal;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.Log;
|
||||
|
||||
import com.stericson.rootshell.execution.Command;
|
||||
import com.stericson.rootshell.execution.Shell;
|
||||
import com.stericson.roottools.RootTools;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.channels.Channels;
|
||||
import java.nio.channels.FileChannel;
|
||||
import java.nio.channels.ReadableByteChannel;
|
||||
import java.security.DigestInputStream;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
|
||||
class Installer
|
||||
{
|
||||
|
||||
//-------------
|
||||
//# Installer #
|
||||
//-------------
|
||||
|
||||
static final String LOG_TAG = "RootTools::Installer";
|
||||
|
||||
static final String BOGUS_FILE_NAME = "bogus";
|
||||
|
||||
Context context;
|
||||
String filesPath;
|
||||
|
||||
public Installer(Context context)
|
||||
throws IOException
|
||||
{
|
||||
|
||||
this.context = context;
|
||||
this.filesPath = context.getFilesDir().getCanonicalPath();
|
||||
}
|
||||
|
||||
/**
|
||||
* This method can be used to unpack a binary from the raw resources folder and store it in
|
||||
* /data/data/app.package/files/
|
||||
* This is typically useful if you provide your own C- or C++-based binary.
|
||||
* This binary can then be executed using sendShell() and its full path.
|
||||
*
|
||||
* @param sourceId resource id; typically <code>R.raw.id</code>
|
||||
* @param destName destination file name; appended to /data/data/app.package/files/
|
||||
* @param mode chmod value for this file
|
||||
* @return a <code>boolean</code> which indicates whether or not we were
|
||||
* able to create the new file.
|
||||
*/
|
||||
protected boolean installBinary(int sourceId, String destName, String mode)
|
||||
{
|
||||
File mf = new File(filesPath + File.separator + destName);
|
||||
if (!mf.exists() ||
|
||||
!getFileSignature(mf).equals(
|
||||
getStreamSignature(
|
||||
context.getResources().openRawResource(sourceId))
|
||||
))
|
||||
{
|
||||
Log.e(LOG_TAG, "Installing a new version of binary: " + destName);
|
||||
// First, does our files/ directory even exist?
|
||||
// We cannot wait for android to lazily create it as we will soon
|
||||
// need it.
|
||||
try
|
||||
{
|
||||
FileInputStream fis = context.openFileInput(BOGUS_FILE_NAME);
|
||||
fis.close();
|
||||
}
|
||||
catch (FileNotFoundException e)
|
||||
{
|
||||
FileOutputStream fos = null;
|
||||
try
|
||||
{
|
||||
fos = context.openFileOutput("bogus", Context.MODE_PRIVATE);
|
||||
fos.write("justcreatedfilesdirectory".getBytes());
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (RootTools.debugMode)
|
||||
{
|
||||
Log.e(LOG_TAG, ex.toString());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (null != fos)
|
||||
{
|
||||
try
|
||||
{
|
||||
fos.close();
|
||||
context.deleteFile(BOGUS_FILE_NAME);
|
||||
}
|
||||
catch (IOException e1)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (IOException ex)
|
||||
{
|
||||
if (RootTools.debugMode)
|
||||
{
|
||||
Log.e(LOG_TAG, ex.toString());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Only now can we start creating our actual file
|
||||
InputStream iss = context.getResources().openRawResource(sourceId);
|
||||
ReadableByteChannel rfc = Channels.newChannel(iss);
|
||||
FileOutputStream oss = null;
|
||||
try
|
||||
{
|
||||
oss = new FileOutputStream(mf);
|
||||
FileChannel ofc = oss.getChannel();
|
||||
long pos = 0;
|
||||
try
|
||||
{
|
||||
long size = iss.available();
|
||||
while ((pos += ofc.transferFrom(rfc, pos, size - pos)) < size)
|
||||
{
|
||||
}
|
||||
}
|
||||
catch (IOException ex)
|
||||
{
|
||||
if (RootTools.debugMode)
|
||||
{
|
||||
Log.e(LOG_TAG, ex.toString());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
catch (FileNotFoundException ex)
|
||||
{
|
||||
if (RootTools.debugMode)
|
||||
{
|
||||
Log.e(LOG_TAG, ex.toString());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (oss != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
oss.flush();
|
||||
oss.getFD().sync();
|
||||
oss.close();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
try
|
||||
{
|
||||
iss.close();
|
||||
}
|
||||
catch (IOException ex)
|
||||
{
|
||||
if (RootTools.debugMode)
|
||||
{
|
||||
Log.e(LOG_TAG, ex.toString());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
Command command = new Command(0, false, "chmod " + mode + " " + filesPath + File.separator + destName);
|
||||
Shell.startRootShell().add(command);
|
||||
commandWait(command);
|
||||
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
protected boolean isBinaryInstalled(String destName)
|
||||
{
|
||||
boolean installed = false;
|
||||
File mf = new File(filesPath + File.separator + destName);
|
||||
if (mf.exists())
|
||||
{
|
||||
installed = true;
|
||||
// TODO: pass mode as argument and check it matches
|
||||
}
|
||||
return installed;
|
||||
}
|
||||
|
||||
protected String getFileSignature(File f)
|
||||
{
|
||||
String signature = "";
|
||||
try
|
||||
{
|
||||
signature = getStreamSignature(new FileInputStream(f));
|
||||
}
|
||||
catch (FileNotFoundException ex)
|
||||
{
|
||||
Log.e(LOG_TAG, ex.toString());
|
||||
}
|
||||
return signature;
|
||||
}
|
||||
|
||||
/*
|
||||
* Note: this method will close any string passed to it
|
||||
*/
|
||||
protected String getStreamSignature(InputStream is)
|
||||
{
|
||||
String signature = "";
|
||||
try
|
||||
{
|
||||
MessageDigest md = MessageDigest.getInstance("MD5");
|
||||
DigestInputStream dis = new DigestInputStream(is, md);
|
||||
byte[] buffer = new byte[4096];
|
||||
while (-1 != dis.read(buffer))
|
||||
{
|
||||
}
|
||||
byte[] digest = md.digest();
|
||||
StringBuffer sb = new StringBuffer();
|
||||
|
||||
for (int i = 0; i < digest.length; i++)
|
||||
{
|
||||
sb.append(Integer.toHexString(digest[i] & 0xFF));
|
||||
}
|
||||
|
||||
signature = sb.toString();
|
||||
}
|
||||
catch (IOException ex)
|
||||
{
|
||||
Log.e(LOG_TAG, ex.toString());
|
||||
}
|
||||
catch (NoSuchAlgorithmException ex)
|
||||
{
|
||||
Log.e(LOG_TAG, ex.toString());
|
||||
}
|
||||
finally
|
||||
{
|
||||
try
|
||||
{
|
||||
is.close();
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
}
|
||||
}
|
||||
return signature;
|
||||
}
|
||||
|
||||
private void commandWait(Command cmd)
|
||||
{
|
||||
synchronized (cmd)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!cmd.isFinished())
|
||||
{
|
||||
cmd.wait(2000);
|
||||
}
|
||||
}
|
||||
catch (InterruptedException ex)
|
||||
{
|
||||
Log.e(LOG_TAG, ex.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,62 @@
|
|||
/*
|
||||
* This file is part of the RootTools Project: http://code.google.com/p/RootTools/
|
||||
*
|
||||
* Copyright (c) 2012 Stephen Erickson, Chris Ravenscroft, Dominik Schuermann, Adam Shanks
|
||||
*
|
||||
* 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.roottools.internal;
|
||||
|
||||
import com.stericson.roottools.containers.Mount;
|
||||
import com.stericson.roottools.containers.Permissions;
|
||||
import com.stericson.roottools.containers.Symlink;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public class InternalVariables
|
||||
{
|
||||
|
||||
// ----------------------
|
||||
// # Internal Variables #
|
||||
// ----------------------
|
||||
|
||||
|
||||
protected static boolean nativeToolsReady = false;
|
||||
protected static boolean found = false;
|
||||
protected static boolean processRunning = false;
|
||||
|
||||
protected static String[] space;
|
||||
protected static String getSpaceFor;
|
||||
protected static String busyboxVersion;
|
||||
protected static String pid_list = "";
|
||||
protected static ArrayList<Mount> mounts;
|
||||
protected static ArrayList<Symlink> symlinks;
|
||||
protected static String inode = "";
|
||||
protected static Permissions permissions;
|
||||
|
||||
// regex to get pid out of ps line, example:
|
||||
// root 2611 0.0 0.0 19408 2104 pts/2 S 13:41 0:00 bash
|
||||
protected static final String PS_REGEX = "^\\S+\\s+([0-9]+).*$";
|
||||
protected static Pattern psPattern;
|
||||
|
||||
static
|
||||
{
|
||||
psPattern = Pattern.compile(PS_REGEX);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,238 @@
|
|||
/*
|
||||
* This file is part of the RootTools Project: http://code.google.com/p/RootTools/
|
||||
*
|
||||
* Copyright (c) 2012 Stephen Erickson, Chris Ravenscroft, Dominik Schuermann, Adam Shanks
|
||||
*
|
||||
* 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.roottools.internal;
|
||||
|
||||
import com.stericson.rootshell.execution.Command;
|
||||
import com.stericson.rootshell.execution.Shell;
|
||||
import com.stericson.roottools.Constants;
|
||||
import com.stericson.roottools.RootTools;
|
||||
import com.stericson.roottools.containers.Mount;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
|
||||
import dev.ukanth.ufirewall.Api;
|
||||
import dev.ukanth.ufirewall.log.Log;
|
||||
|
||||
|
||||
public class Remounter
|
||||
{
|
||||
|
||||
private String customPath;
|
||||
|
||||
public Remounter() {
|
||||
}
|
||||
|
||||
public Remounter(String path) {
|
||||
this.customPath = path;
|
||||
}
|
||||
//-------------
|
||||
//# Remounter #
|
||||
//-------------
|
||||
|
||||
/**
|
||||
* This will take a path, which can contain the file name as well,
|
||||
* and attempt to remount the underlying partition.
|
||||
* <p/>
|
||||
* For example, passing in the following string:
|
||||
* "/system/bin/some/directory/that/really/would/never/exist"
|
||||
* will result in /system ultimately being remounted.
|
||||
* However, keep in mind that the longer the path you supply, the more work this has to do,
|
||||
* and the slower it will run.
|
||||
*
|
||||
* @param file file path
|
||||
* @param mountType mount type: pass in RO (Read only) or RW (Read Write)
|
||||
* @return a <code>boolean</code> which indicates whether or not the partition
|
||||
* has been remounted as specified.
|
||||
*/
|
||||
public boolean remount(String file, String mountType)
|
||||
{
|
||||
//if the path has a trailing slash get rid of it.
|
||||
if (file.endsWith("/") && !file.equals("/"))
|
||||
{
|
||||
file = file.substring(0, file.lastIndexOf("/"));
|
||||
}
|
||||
//Make sure that what we are trying to remount is in the mount list.
|
||||
boolean foundMount = false;
|
||||
while (!foundMount)
|
||||
{
|
||||
try
|
||||
{
|
||||
for (Mount mount : RootTools.getMounts())
|
||||
{
|
||||
RootTools.log(mount.getMountPoint().toString());
|
||||
|
||||
if (file.equals(mount.getMountPoint().toString()))
|
||||
{
|
||||
foundMount = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
if (RootTools.debugMode)
|
||||
{
|
||||
Log.d(Api.TAG, e.getMessage(), e);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
if (!foundMount)
|
||||
{
|
||||
try
|
||||
{
|
||||
file = (new File(file).getParent());
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.e(Api.TAG, e.getMessage(), e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Mount mountPoint = findMountPointRecursive(file);
|
||||
|
||||
if (mountPoint != null)
|
||||
{
|
||||
|
||||
RootTools.log(Constants.TAG, "Remounting " + mountPoint.getMountPoint().getAbsolutePath() + " as " + mountType.toLowerCase());
|
||||
final boolean isMountMode = mountPoint.getFlags().contains(mountType.toLowerCase());
|
||||
|
||||
if (!isMountMode)
|
||||
{
|
||||
//grab an instance of the internal class
|
||||
try
|
||||
{
|
||||
Command command = new Command(0,
|
||||
true,
|
||||
"busybox mount -o remount," + mountType.toLowerCase() + " " + mountPoint.getDevice().getAbsolutePath() + " " + mountPoint.getMountPoint().getAbsolutePath(),
|
||||
"toolbox mount -o remount," + mountType.toLowerCase() + " " + mountPoint.getDevice().getAbsolutePath() + " " + mountPoint.getMountPoint().getAbsolutePath(),
|
||||
"toybox mount -o remount," + mountType.toLowerCase() + " " + mountPoint.getDevice().getAbsolutePath() + " " + mountPoint.getMountPoint().getAbsolutePath(),
|
||||
"mount -o remount," + mountType.toLowerCase() + " " + mountPoint.getDevice().getAbsolutePath() + " " + mountPoint.getMountPoint().getAbsolutePath(),
|
||||
"mount -o remount," + mountType.toLowerCase() + " " + file,
|
||||
"/system/bin/toolbox mount -o remount," + mountType.toLowerCase() + " " + mountPoint.getDevice().getAbsolutePath() + " " + mountPoint.getMountPoint().getAbsolutePath(),
|
||||
"/system/bin/toybox mount -o remount," + mountType.toLowerCase() + " " + mountPoint.getDevice().getAbsolutePath() + " " + mountPoint.getMountPoint().getAbsolutePath()
|
||||
);
|
||||
Shell.startRootShell().add(command);
|
||||
commandWait(command);
|
||||
|
||||
if(customPath != null) {
|
||||
command = new Command(0,
|
||||
true,
|
||||
customPath + " mount -o remount," + mountType.toLowerCase() + " " + mountPoint.getDevice().getAbsolutePath() + " " + mountPoint.getMountPoint().getAbsolutePath());
|
||||
Shell.startRootShell().add(command);
|
||||
commandWait(command);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
}
|
||||
|
||||
mountPoint = findMountPointRecursive(file);
|
||||
}
|
||||
|
||||
if (mountPoint != null)
|
||||
{
|
||||
RootTools.log(Constants.TAG, mountPoint.getFlags() + " AND " + mountType.toLowerCase());
|
||||
if (mountPoint.getFlags().contains(mountType.toLowerCase()))
|
||||
{
|
||||
RootTools.log(mountPoint.getFlags().toString());
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
RootTools.log(mountPoint.getFlags().toString());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
RootTools.log("mount is null, file was: " + file + " mountType was: " + mountType);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
RootTools.log("mount is null, file was: " + file + " mountType was: " + mountType);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private Mount findMountPointRecursive(String file)
|
||||
{
|
||||
try
|
||||
{
|
||||
ArrayList<Mount> mounts = RootTools.getMounts();
|
||||
|
||||
for (File path = new File(file); path != null; )
|
||||
{
|
||||
for (Mount mount : mounts)
|
||||
{
|
||||
if (mount.getMountPoint().equals(path))
|
||||
{
|
||||
return mount;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
if (RootTools.debugMode)
|
||||
{
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
if (RootTools.debugMode)
|
||||
{
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private void commandWait(Command cmd)
|
||||
{
|
||||
synchronized (cmd)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!cmd.isFinished())
|
||||
{
|
||||
cmd.wait(2000);
|
||||
}
|
||||
}
|
||||
catch (InterruptedException e)
|
||||
{
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -0,0 +1,98 @@
|
|||
/*
|
||||
* This file is part of the RootTools Project: http://code.google.com/p/RootTools/
|
||||
*
|
||||
* Copyright (c) 2012 Stephen Erickson, Chris Ravenscroft, Dominik Schuermann, Adam Shanks
|
||||
*
|
||||
* 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.roottools.internal;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.Log;
|
||||
|
||||
import com.stericson.rootshell.execution.Command;
|
||||
import com.stericson.rootshell.execution.Shell;
|
||||
import com.stericson.roottools.RootTools;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public class Runner extends Thread
|
||||
{
|
||||
|
||||
private static final String LOG_TAG = "RootTools::Runner";
|
||||
|
||||
Context context;
|
||||
String binaryName;
|
||||
String parameter;
|
||||
|
||||
public Runner(Context context, String binaryName, String parameter)
|
||||
{
|
||||
this.context = context;
|
||||
this.binaryName = binaryName;
|
||||
this.parameter = parameter;
|
||||
}
|
||||
|
||||
public void run()
|
||||
{
|
||||
String privateFilesPath = null;
|
||||
try
|
||||
{
|
||||
privateFilesPath = context.getFilesDir().getCanonicalPath();
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
if (RootTools.debugMode)
|
||||
{
|
||||
Log.e(LOG_TAG, "Problem occured while trying to locate private files directory!");
|
||||
}
|
||||
e.printStackTrace();
|
||||
}
|
||||
if (privateFilesPath != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
Command command = new Command(0, false, privateFilesPath + "/" + binaryName + " " + parameter);
|
||||
Shell.startRootShell().add(command);
|
||||
commandWait(command);
|
||||
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void commandWait(Command cmd)
|
||||
{
|
||||
synchronized (cmd)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!cmd.isFinished())
|
||||
{
|
||||
cmd.wait(2000);
|
||||
}
|
||||
}
|
||||
catch (InterruptedException e)
|
||||
{
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,90 @@
|
|||
/*
|
||||
* Copyright 2012 two forty four a.m. LLC <http://www.twofortyfouram.com>
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in
|
||||
* compliance with the License. You may obtain a copy of the License at
|
||||
* <http://www.apache.org/licenses/LICENSE-2.0>
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License is
|
||||
* distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and limitations under the License.
|
||||
*/
|
||||
|
||||
package com.twofortyfouram.locale;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.util.Log;
|
||||
|
||||
import dev.ukanth.ufirewall.R;
|
||||
|
||||
/**
|
||||
* Utility class to generate a breadcrumb title string for {@code Activity} instances in Locale.
|
||||
* <p>
|
||||
* This class cannot be instantiated.
|
||||
*/
|
||||
public final class BreadCrumber
|
||||
{
|
||||
/**
|
||||
* Private constructor prevents instantiation
|
||||
*
|
||||
* @throws UnsupportedOperationException because this class cannot be instantiated.
|
||||
*/
|
||||
private BreadCrumber()
|
||||
{
|
||||
throw new UnsupportedOperationException("This class is non-instantiable"); //$NON-NLS-1$
|
||||
}
|
||||
|
||||
/**
|
||||
* Static helper method to generate bread crumbs. Bread crumb strings will be properly formatted for the
|
||||
* current language, including right-to-left languages, as long as the proper
|
||||
* {@link com.twofortyfouram.locale.platform.R.string#twofortyfouram_locale_breadcrumb_format} string
|
||||
* resources have been created.
|
||||
*
|
||||
* @param context {@code Context} for loading platform resources. Cannot be null.
|
||||
* @param intent {@code Intent} to extract the bread crumb from.
|
||||
* @param currentCrumb The last element of the bread crumb path.
|
||||
* @return {@code String} presentation of the bread crumb. If the intent parameter is null, then this
|
||||
* method returns currentCrumb. If currentCrumb is null, then this method returns the empty string
|
||||
* "". If intent contains a private Serializable instances as an extra, then this method returns
|
||||
* the empty string "".
|
||||
* @throws IllegalArgumentException if {@code context} is null.
|
||||
*/
|
||||
public static CharSequence generateBreadcrumb(final Context context, final Intent intent,
|
||||
final String currentCrumb)
|
||||
{
|
||||
if (null == context)
|
||||
{
|
||||
throw new IllegalArgumentException("context cannot be null"); //$NON-NLS-1$
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (null == currentCrumb)
|
||||
{
|
||||
Log.w(Constants.LOG_TAG, "currentCrumb cannot be null"); //$NON-NLS-1$
|
||||
return ""; //$NON-NLS-1$
|
||||
}
|
||||
if (null == intent)
|
||||
{
|
||||
Log.w(Constants.LOG_TAG, "intent cannot be null"); //$NON-NLS-1$
|
||||
return currentCrumb;
|
||||
}
|
||||
|
||||
/*
|
||||
* Note: this is vulnerable to a private serializable attack, but the try-catch will solve that.
|
||||
*/
|
||||
final String breadcrumbString = intent.getStringExtra(com.twofortyfouram.locale.Intent.EXTRA_STRING_BREADCRUMB);
|
||||
if (null != breadcrumbString)
|
||||
{
|
||||
return context.getString(R.string.twofortyfouram_locale_breadcrumb_format, breadcrumbString, context.getString(R.string.twofortyfouram_locale_breadcrumb_separator), currentCrumb);
|
||||
}
|
||||
return currentCrumb;
|
||||
}
|
||||
catch (final Exception e)
|
||||
{
|
||||
Log.e(Constants.LOG_TAG, "Encountered error generating breadcrumb", e); //$NON-NLS-1$
|
||||
return ""; //$NON-NLS-1$
|
||||
}
|
||||
}
|
||||
}
|
||||
48
app/src/main/java/com/twofortyfouram/locale/Constants.java
Normal file
48
app/src/main/java/com/twofortyfouram/locale/Constants.java
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
/*
|
||||
* Copyright 2012 two forty four a.m. LLC <http://www.twofortyfouram.com>
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in
|
||||
* compliance with the License. You may obtain a copy of the License at
|
||||
* <http://www.apache.org/licenses/LICENSE-2.0>
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License is
|
||||
* distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and limitations under the License.
|
||||
*/
|
||||
|
||||
package com.twofortyfouram.locale;
|
||||
|
||||
/**
|
||||
* Utility class containing constants for the Locale Developer Platform.
|
||||
*/
|
||||
/*
|
||||
* This class is NOT part of the public API.
|
||||
*/
|
||||
/* package */final class Constants
|
||||
{
|
||||
/**
|
||||
* Private constructor prevents instantiation
|
||||
*
|
||||
* @throws UnsupportedOperationException because this class cannot be instantiated.
|
||||
*/
|
||||
private Constants()
|
||||
{
|
||||
throw new UnsupportedOperationException("This class is non-instantiable"); //$NON-NLS-1$
|
||||
}
|
||||
|
||||
/**
|
||||
* Log tag for logcat messages generated by the Locale Developer Platform
|
||||
*/
|
||||
/*
|
||||
* This is NOT a public API. Third party apps should NOT use this log tag for their own log messages.
|
||||
*/
|
||||
/* package */static final String LOG_TAG = "LocaleApiLibrary"; //$NON-NLS-1$
|
||||
|
||||
/**
|
||||
* String package name for Locale.
|
||||
*/
|
||||
/*
|
||||
* This is NOT a public API. Third parties should NOT rely on this being the only package name for Locale.
|
||||
*/
|
||||
/* package */static final String LOCALE_PACKAGE = "com.twofortyfouram.locale"; //$NON-NLS-1$
|
||||
}
|
||||
195
app/src/main/java/com/twofortyfouram/locale/Intent.java
Normal file
195
app/src/main/java/com/twofortyfouram/locale/Intent.java
Normal file
|
|
@ -0,0 +1,195 @@
|
|||
/*
|
||||
* Copyright 2012 two forty four a.m. LLC <http://www.twofortyfouram.com>
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in
|
||||
* compliance with the License. You may obtain a copy of the License at
|
||||
* <http://www.apache.org/licenses/LICENSE-2.0>
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License is
|
||||
* distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and limitations under the License.
|
||||
*/
|
||||
|
||||
package com.twofortyfouram.locale;
|
||||
|
||||
import android.os.Parcelable;
|
||||
|
||||
/**
|
||||
* Contains Intent constants necessary for interacting with the Locale Developer Platform.
|
||||
*/
|
||||
public final class Intent
|
||||
{
|
||||
/**
|
||||
* Private constructor prevents instantiation.
|
||||
*
|
||||
* @throws UnsupportedOperationException because this class cannot be instantiated.
|
||||
*/
|
||||
private Intent()
|
||||
{
|
||||
throw new UnsupportedOperationException("This class is non-instantiable"); //$NON-NLS-1$
|
||||
}
|
||||
|
||||
/**
|
||||
* Ordered broadcast result code indicating that a plug-in condition's state is satisfied (true).
|
||||
*
|
||||
* @see Intent#ACTION_QUERY_CONDITION
|
||||
*/
|
||||
public static final int RESULT_CONDITION_SATISFIED = 16;
|
||||
|
||||
/**
|
||||
* Ordered broadcast result code indicating that a plug-in condition's state is not satisfied (false).
|
||||
*
|
||||
* @see Intent#ACTION_QUERY_CONDITION
|
||||
*/
|
||||
public static final int RESULT_CONDITION_UNSATISFIED = 17;
|
||||
|
||||
/**
|
||||
* Ordered broadcast result code indicating that a plug-in condition's state is unknown (neither true nor
|
||||
* false).
|
||||
* <p>
|
||||
* If a condition returns UNKNOWN, then Locale will use the last known return value on a best-effort
|
||||
* basis. Best-effort means that Locale may not persist known values forever (e.g. last known values could
|
||||
* hypothetically be cleared after a device reboot, a restart of the Locale process, or other events). If
|
||||
* there is no last known return value, then unknown is treated as not satisfied (false).
|
||||
* <p>
|
||||
* The purpose of an UNKNOWN result is to allow a plug-in condition more than 10 seconds to process a
|
||||
* requery. A {@code BroadcastReceiver} must return within 10 seconds, otherwise it will be killed by
|
||||
* Android. A plug-in that needs more than 10 seconds might initially return
|
||||
* {@link #RESULT_CONDITION_UNKNOWN}, subsequently request a requery, and then return either
|
||||
* {@link #RESULT_CONDITION_SATISFIED} or {@link #RESULT_CONDITION_UNSATISFIED}.
|
||||
*
|
||||
* @see Intent#ACTION_QUERY_CONDITION
|
||||
*/
|
||||
public static final int RESULT_CONDITION_UNKNOWN = 18;
|
||||
|
||||
/**
|
||||
* {@code Intent} action {@code String} broadcast by Locale to create or edit a plug-in setting. When
|
||||
* Locale broadcasts this {@code Intent}, it will be sent directly to the package and class of the
|
||||
* plug-in's {@code Activity}. The {@code Intent} may contain a {@link #EXTRA_BUNDLE} that was previously
|
||||
* set by the {@code Activity} result of {@link #ACTION_EDIT_SETTING}.
|
||||
* <p>
|
||||
* There SHOULD be only one {@code Activity} per APK that implements this {@code Intent}. If a single APK
|
||||
* wishes to export multiple plug-ins, it MAY implement multiple Activity instances that implement this
|
||||
* {@code Intent}, however there must only be a single {@link #ACTION_FIRE_SETTING} receiver. In this
|
||||
* scenario, it is the responsibility of the Activities to store enough data in {@link #EXTRA_BUNDLE} to
|
||||
* allow this receiver to disambiguate which "plug-in" is being fired. To avoid user confusion, it is
|
||||
* recommended that only a single plug-in be implemented per APK.
|
||||
*
|
||||
* @see Intent#EXTRA_BUNDLE
|
||||
* @see Intent#EXTRA_STRING_BREADCRUMB
|
||||
*/
|
||||
public static final String ACTION_EDIT_SETTING = "com.twofortyfouram.locale.intent.action.EDIT_SETTING"; //$NON-NLS-1$
|
||||
|
||||
/**
|
||||
* {@code Intent} action {@code String} broadcast by Locale to fire a plug-in setting. When Locale
|
||||
* broadcasts this {@code Intent}, it will be sent directly to the package and class of the plug-in's
|
||||
* {@code BroadcastReceiver}. The {@code Intent} will contain a {@link #EXTRA_BUNDLE} that was previously
|
||||
* set by the {@code Activity} result of {@link #ACTION_EDIT_SETTING}.
|
||||
* <p>
|
||||
* There MUST be only one {@code BroadcastReceiver} per APK that implements this {@code Intent}.
|
||||
*
|
||||
* @see Intent#EXTRA_BUNDLE
|
||||
*/
|
||||
public static final String ACTION_FIRE_SETTING = "com.twofortyfouram.locale.intent.action.FIRE_SETTING"; //$NON-NLS-1$
|
||||
|
||||
/**
|
||||
* {@code Intent} action {@code String} broadcast by Locale to create or edit a plug-in condition. When
|
||||
* Locale broadcasts this {@code Intent}, it will be sent directly to the package and class of the
|
||||
* plug-in's {@code Activity}. The {@code Intent} may contain a store-and-forward {@link #EXTRA_BUNDLE}
|
||||
* that was previously set by the {@code Activity} result of {@link #ACTION_EDIT_CONDITION}.
|
||||
* <p>
|
||||
* There SHOULD be only one {@code Activity} per APK that implements this {@code Intent}. If a single APK
|
||||
* wishes to export multiple plug-ins, it MAY implement multiple Activity instances that implement this
|
||||
* {@code Intent}, however there must only be a single {@link #ACTION_QUERY_CONDITION} receiver. In this
|
||||
* scenario, it is the responsibility of the Activities to store enough data in {@link #EXTRA_BUNDLE} to
|
||||
* allow this receiver to disambiguate which "plug-in" is being queried. To avoid user confusion, it is
|
||||
* recommended that only a single plug-in be implemented per APK.
|
||||
*
|
||||
* @see Intent#EXTRA_BUNDLE
|
||||
* @see Intent#EXTRA_STRING_BREADCRUMB
|
||||
*/
|
||||
public static final String ACTION_EDIT_CONDITION = "com.twofortyfouram.locale.intent.action.EDIT_CONDITION"; //$NON-NLS-1$
|
||||
|
||||
/**
|
||||
* Ordered {@code Intent} action {@code String} broadcast by Locale to query a plug-in condition. When
|
||||
* Locale broadcasts this {@code Intent}, it will be sent directly to the package and class of the
|
||||
* plug-in's {@code BroadcastReceiver}. The {@code Intent} will contain a {@link #EXTRA_BUNDLE} that was
|
||||
* previously set by the {@code Activity} result of {@link #ACTION_EDIT_CONDITION}.
|
||||
* <p>
|
||||
* Since this is an ordered broadcast, the receiver is expected to set an appropriate result code from
|
||||
* {@link #RESULT_CONDITION_SATISFIED}, {@link #RESULT_CONDITION_UNSATISFIED}, and
|
||||
* {@link #RESULT_CONDITION_UNKNOWN}.
|
||||
* <p>
|
||||
* There MUST be only one {@code BroadcastReceiver} per APK that implements this {@code Intent}.
|
||||
*
|
||||
* @see Intent#EXTRA_BUNDLE
|
||||
* @see Intent#RESULT_CONDITION_SATISFIED
|
||||
* @see Intent#RESULT_CONDITION_UNSATISFIED
|
||||
* @see Intent#RESULT_CONDITION_UNKNOWN
|
||||
*/
|
||||
public static final String ACTION_QUERY_CONDITION = "com.twofortyfouram.locale.intent.action.QUERY_CONDITION"; //$NON-NLS-1$
|
||||
|
||||
/**
|
||||
* {@code Intent} action {@code String} to notify Locale that a plug-in condition is requesting that
|
||||
* Locale query it via {@link #ACTION_QUERY_CONDITION}. This merely serves as a hint to Locale that a
|
||||
* condition wants to be queried. There is no guarantee as to when or if the plug-in will be queried after
|
||||
* this {@code Intent} is broadcast. If Locale does not respond to the plug-in condition after a
|
||||
* {@link #ACTION_REQUEST_QUERY} Intent is sent, the plug-in SHOULD shut itself down and stop requesting
|
||||
* requeries. A lack of response from Locale indicates that Locale is not currently interested in this
|
||||
* plug-in. When Locale becomes interested in the plug-in again, Locale will send
|
||||
* {@link #ACTION_QUERY_CONDITION}.
|
||||
* <p>
|
||||
* The extra {@link #EXTRA_ACTIVITY} MUST be included, otherwise Locale will ignore this {@code Intent}.
|
||||
* <p>
|
||||
* Plug-in conditions SHOULD NOT use this unless there is some sort of asynchronous event that has
|
||||
* occurred, such as a broadcast {@code Intent} being received by the plug-in. Plug-ins SHOULD NOT
|
||||
* periodically request a requery as a way of implementing polling behavior.
|
||||
*
|
||||
* @see Intent#EXTRA_ACTIVITY
|
||||
*/
|
||||
public static final String ACTION_REQUEST_QUERY = "com.twofortyfouram.locale.intent.action.REQUEST_QUERY"; //$NON-NLS-1$
|
||||
|
||||
/**
|
||||
* Type: {@code String}
|
||||
* <p>
|
||||
* Maps to a {@code String} that represents the {@code Activity} bread crumb path.
|
||||
*
|
||||
* @see BreadCrumber
|
||||
*/
|
||||
public static final String EXTRA_STRING_BREADCRUMB = "com.twofortyfouram.locale.intent.extra.BREADCRUMB"; //$NON-NLS-1$
|
||||
|
||||
/**
|
||||
* Type: {@code String}
|
||||
* <p>
|
||||
* Maps to a {@code String} that represents a blurb. This is returned as an {@code Activity} result extra
|
||||
* from {@link #ACTION_EDIT_CONDITION} or {@link #ACTION_EDIT_SETTING}.
|
||||
* <p>
|
||||
* The blurb is a concise description displayed to the user of what the plug-in is configured to do.
|
||||
*/
|
||||
public static final String EXTRA_STRING_BLURB = "com.twofortyfouram.locale.intent.extra.BLURB"; //$NON-NLS-1$
|
||||
|
||||
/**
|
||||
* Type: {@code Bundle}
|
||||
* <p>
|
||||
* Maps to a {@code Bundle} that contains all of a plug-in's extras.
|
||||
* <p>
|
||||
* Plug-ins MUST NOT store {@link Parcelable} objects in this {@code Bundle}, because {@code Parcelable}
|
||||
* is not a long-term storage format. Also, plug-ins MUST NOT store any serializable object that is not
|
||||
* exposed by the Android SDK.
|
||||
* <p>
|
||||
* The maximum size of a Bundle that can be sent across process boundaries is on the order of 500
|
||||
* kilobytes (base-10), while Locale further limits plug-in Bundles to about 100 kilobytes (base-10).
|
||||
* Although the maximum size is about 100 kilobytes, plug-ins SHOULD keep Bundles much smaller for
|
||||
* performance and memory usage reasons.
|
||||
*/
|
||||
public static final String EXTRA_BUNDLE = "com.twofortyfouram.locale.intent.extra.BUNDLE"; //$NON-NLS-1$
|
||||
|
||||
/**
|
||||
* Type: {@code String}
|
||||
* <p>
|
||||
* Maps to a {@code String} that represents the name of a plug-in's {@code Activity}.
|
||||
*
|
||||
* @see Intent#ACTION_REQUEST_QUERY
|
||||
*/
|
||||
public static final String EXTRA_ACTIVITY = "com.twofortyfouram.locale.intent.extra.ACTIVITY"; //$NON-NLS-1$
|
||||
}
|
||||
|
|
@ -0,0 +1,123 @@
|
|||
/*
|
||||
* Copyright 2012 two forty four a.m. LLC <http://www.twofortyfouram.com>
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in
|
||||
* compliance with the License. You may obtain a copy of the License at
|
||||
* <http://www.apache.org/licenses/LICENSE-2.0>
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License is
|
||||
* distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and limitations under the License.
|
||||
*/
|
||||
|
||||
package com.twofortyfouram.locale;
|
||||
|
||||
import android.content.pm.PackageInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* A simple utility class to find a package that is compatible with hosting the Locale Developer Platform.
|
||||
*/
|
||||
/*
|
||||
* This class is NOT part of the public Locale Developer Platform API
|
||||
*/
|
||||
public final class PackageUtilities
|
||||
{
|
||||
/**
|
||||
* A hard-coded set of Android packages that support the Locale Developer Platform.
|
||||
*/
|
||||
/*
|
||||
* This is NOT a public field and is subject to change in future releases of the Developer Platform. A
|
||||
* conscious design decision was made to use hard-coded package names, rather than dynamic discovery of
|
||||
* packages that might be compatible with hosting the Locale Developer Platform API. This is for two
|
||||
* reasons: to ensure the host is implemented correctly (hosts must pass the extensive Locale Platform
|
||||
* Host compatibility test suite) and to prevent malicious applications from crashing plug-ins by
|
||||
* providing bad values. As additional apps implement the Locale Developer Platform, their package names
|
||||
* will also be added to this list.
|
||||
*/
|
||||
/*
|
||||
* Note: this is implemented as a Set<String> rather than a String[], in order to enforce immutability.
|
||||
*/
|
||||
private static final Set<String> COMPATIBLE_PACKAGES = constructPackageSet();
|
||||
|
||||
/**
|
||||
* @return a list wrapped in {@link Collections#unmodifiableList(List)} that represents the set of
|
||||
* Locale-compatible packages.
|
||||
*/
|
||||
private static Set<String> constructPackageSet()
|
||||
{
|
||||
final HashSet<String> packages = new HashSet<String>();
|
||||
|
||||
packages.add(Constants.LOCALE_PACKAGE);
|
||||
|
||||
/*
|
||||
* Note: Tasker is not 100% compatible with Locale's plug-in API, but it is close enough that these
|
||||
* packages are enabled. Tasker's known incompatibilities are documented on the Tasker website.
|
||||
*/
|
||||
packages.add("net.dinglisch.android.taskerm"); //$NON-NLS-1$
|
||||
packages.add("net.dinglisch.android.tasker"); //$NON-NLS-1$
|
||||
packages.add("net.dinglisch.android.taskercupcake"); //$NON-NLS-1$
|
||||
|
||||
return Collections.unmodifiableSet(packages);
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtains the {@code String} package name of a currently-installed package which implements the host
|
||||
* component of the Locale Developer Platform.
|
||||
* <p>
|
||||
* Note: A TOCTOU error exists, due to the fact that the package could be uninstalled at any time.
|
||||
* <p>
|
||||
* Note: If there are multiple hosts, this method will return one of them. The interface of this method
|
||||
* makes no guarantee which host will returned, nor whether that host will be consistently returned.
|
||||
*
|
||||
* @param manager an instance of {@code PackageManager}. Cannot be null.
|
||||
* @param packageHint hint as to which package should take precedence. This parameter may be null.
|
||||
* @return {@code String} package name of a host for the Locale Developer Platform, such as
|
||||
* "com.twofortyfouram.locale". If no such package is found, returns null.
|
||||
*/
|
||||
public static String getCompatiblePackage(final PackageManager manager, final String packageHint)
|
||||
{
|
||||
/*
|
||||
* The interface for this method makes no guarantees as to which host will be returned. However the
|
||||
* implementation is more predictable.
|
||||
*/
|
||||
|
||||
final List<PackageInfo> installedPackages = manager.getInstalledPackages(0);
|
||||
|
||||
if (COMPATIBLE_PACKAGES.contains(packageHint))
|
||||
{
|
||||
for (final PackageInfo packageInfo : installedPackages)
|
||||
{
|
||||
final String temp = packageInfo.packageName;
|
||||
if (packageHint.equals(temp))
|
||||
{
|
||||
return temp;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (final String compatiblePackageName : COMPATIBLE_PACKAGES)
|
||||
{
|
||||
if (compatiblePackageName.equals(packageHint))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
for (final PackageInfo packageInfo : installedPackages)
|
||||
{
|
||||
final String temp = packageInfo.packageName;
|
||||
if (compatiblePackageName.equals(temp))
|
||||
{
|
||||
return temp;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
4419
app/src/main/java/dev/ukanth/ufirewall/Api.java
Normal file
4419
app/src/main/java/dev/ukanth/ufirewall/Api.java
Normal file
File diff suppressed because it is too large
Load diff
68
app/src/main/java/dev/ukanth/ufirewall/InterfaceDetails.java
Normal file
68
app/src/main/java/dev/ukanth/ufirewall/InterfaceDetails.java
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
/**
|
||||
* Class to store wifi/3G/tethering status and LAN IP ranges.
|
||||
*
|
||||
* Copyright (C) 2013 Kevin Cernekee
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* @author Kevin Cernekee
|
||||
* @version 1.0
|
||||
*/
|
||||
|
||||
package dev.ukanth.ufirewall;
|
||||
|
||||
public class InterfaceDetails {
|
||||
// firewall policy
|
||||
public boolean isRoaming = false;
|
||||
|
||||
public boolean isWifiTethered = false;
|
||||
public boolean tetherWifiStatusKnown = false;
|
||||
|
||||
public boolean isBluetoothTethered = false;
|
||||
public boolean tetherBluetoothStatusKnown = false;
|
||||
|
||||
public boolean isUsbTethered = false;
|
||||
public boolean tetherUsbStatusKnown = false;
|
||||
|
||||
public String lanMaskV4 = "";
|
||||
public String lanMaskV6 = "";
|
||||
|
||||
// DNS servers for targeted rules instead of opening port 53 to all LAN hosts
|
||||
public java.util.List<String> dnsServersV4 = new java.util.ArrayList<>();
|
||||
public java.util.List<String> dnsServersV6 = new java.util.ArrayList<>();
|
||||
|
||||
// supplementary info
|
||||
String wifiName = "";
|
||||
boolean netEnabled = false;
|
||||
boolean noIP = false;
|
||||
public int netType = -1;
|
||||
|
||||
public boolean equals(InterfaceDetails that) {
|
||||
return this.isRoaming == that.isRoaming &&
|
||||
this.isWifiTethered == that.isWifiTethered &&
|
||||
this.tetherWifiStatusKnown == that.tetherWifiStatusKnown &&
|
||||
this.isBluetoothTethered == that.isBluetoothTethered &&
|
||||
this.tetherBluetoothStatusKnown == that.tetherBluetoothStatusKnown &&
|
||||
this.isUsbTethered == that.isUsbTethered &&
|
||||
this.tetherUsbStatusKnown == that.tetherUsbStatusKnown &&
|
||||
this.lanMaskV4.equals(that.lanMaskV4) &&
|
||||
this.lanMaskV6.equals(that.lanMaskV6) &&
|
||||
this.dnsServersV4.equals(that.dnsServersV4) &&
|
||||
this.dnsServersV6.equals(that.dnsServersV6) &&
|
||||
this.wifiName.equals(that.wifiName) &&
|
||||
this.netEnabled == that.netEnabled &&
|
||||
this.netType == that.netType &&
|
||||
this.noIP == that.noIP;
|
||||
}
|
||||
}
|
||||
523
app/src/main/java/dev/ukanth/ufirewall/InterfaceTracker.java
Normal file
523
app/src/main/java/dev/ukanth/ufirewall/InterfaceTracker.java
Normal file
|
|
@ -0,0 +1,523 @@
|
|||
/**
|
||||
* Keep track of wifi/3G/tethering status and LAN IP ranges.
|
||||
* <p/>
|
||||
* Copyright (C) 2013 Kevin Cernekee
|
||||
* <p/>
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
* <p/>
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
* <p/>
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* @author Kevin Cernekee
|
||||
* @version 1.0
|
||||
*/
|
||||
|
||||
package dev.ukanth.ufirewall;
|
||||
|
||||
import static dev.ukanth.ufirewall.util.G.ctx;
|
||||
|
||||
import android.bluetooth.BluetoothAdapter;
|
||||
import android.bluetooth.BluetoothProfile;
|
||||
import android.content.Context;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.net.ConnectivityManager;
|
||||
import android.net.NetworkInfo;
|
||||
import android.net.wifi.WifiManager;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.net.Inet4Address;
|
||||
import java.net.Inet6Address;
|
||||
import java.net.InetAddress;
|
||||
import java.net.InterfaceAddress;
|
||||
import java.net.NetworkInterface;
|
||||
import java.util.Enumeration;
|
||||
import java.util.Iterator;
|
||||
|
||||
import dev.ukanth.ufirewall.log.Log;
|
||||
import dev.ukanth.ufirewall.service.FirewallService;
|
||||
import dev.ukanth.ufirewall.service.RootCommand;
|
||||
import dev.ukanth.ufirewall.util.G;
|
||||
|
||||
public final class InterfaceTracker {
|
||||
|
||||
public static final String TAG = "AFWall";
|
||||
|
||||
public static final String[] ITFS_WIFI = {"eth+", "wlan+", "tiwlan+", "ra+", "bnep+"};
|
||||
|
||||
public static final String[] ITFS_3G = {"rmnet+", "pdp+", "uwbr+", "wimax+", "vsnet+",
|
||||
"rmnet_sdio+", "ccmni+", "qmi+", "svnet0+", "ccemni+",
|
||||
"wwan+", "cdma_rmnet+", "clat4+", "cc2mni+", "bond1+", "rmnet_smux+", "ccinet+",
|
||||
"v4-rmnet+", "seth_w+", "v4-rmnet_data+", "rmnet_ipa+", "rmnet_data+", "r_rmnet_data+"};
|
||||
|
||||
public static final String[] ITFS_VPN = {"tun+", "ppp+", "tap+"};
|
||||
|
||||
public static final String[] ITFS_TETHER = {"bt-pan", "usb+", "rndis+", "rmnet_usb+"};
|
||||
|
||||
public static final String BOOT_COMPLETED = "BOOT_COMPLETED";
|
||||
public static final String CONNECTIVITY_CHANGE = "CONNECTIVITY_CHANGE";
|
||||
public static final String TETHER_STATE_CHANGED = "TETHER_STATE_CHANGED";
|
||||
|
||||
|
||||
private static InterfaceDetails currentCfg = null;
|
||||
|
||||
private static String truncAfter(String in, String regexp) {
|
||||
return in.split(regexp)[0];
|
||||
}
|
||||
|
||||
private static void getTetherStatus(Context context, InterfaceDetails d) {
|
||||
getWifiTetherStatus(context, d);
|
||||
getBluetoothTetherStatus(context, d);
|
||||
getUsbTetherStatus(context, d);
|
||||
}
|
||||
|
||||
private static void getWifiTetherStatus(Context context, InterfaceDetails d) {
|
||||
WifiManager wifi = (WifiManager) context.getApplicationContext().getSystemService(Context.WIFI_SERVICE);
|
||||
Method[] wmMethods = wifi.getClass().getDeclaredMethods();
|
||||
|
||||
d.isWifiTethered = false;
|
||||
d.tetherWifiStatusKnown = false;
|
||||
|
||||
for (Method method : wmMethods) {
|
||||
if (method.getName().equals("isWifiApEnabled")) {
|
||||
try {
|
||||
d.isWifiTethered = ((Boolean) method.invoke(wifi)).booleanValue();
|
||||
d.tetherWifiStatusKnown = true;
|
||||
Log.d(TAG, "isWifiApEnabled is " + d.isWifiTethered);
|
||||
} catch (Exception e) {
|
||||
Log.e(G.TAG, "Exception in getting Wifi tether status");
|
||||
Log.e(Api.TAG, android.util.Log.getStackTraceString(e));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
// To get bluetooth tethering, we need valid BluetoothPan instance
|
||||
// It is obtainable only in ServiceListener.onServiceConnected callback
|
||||
|
||||
|
||||
public static BluetoothProfile getBtProfile() {
|
||||
return FirewallService.getBtPanProfile();
|
||||
}
|
||||
|
||||
private static void getBluetoothTetherStatus(Context context, InterfaceDetails d) {
|
||||
if (FirewallService.getBtPanProfile() != null) {
|
||||
Method[] btMethods = FirewallService.getBtPanProfile().getClass().getDeclaredMethods();
|
||||
|
||||
d.isBluetoothTethered = false;
|
||||
d.tetherBluetoothStatusKnown = false;
|
||||
|
||||
for (Method method : btMethods) {
|
||||
if (method.getName().equals("isTetheringOn")) {
|
||||
try {
|
||||
d.isBluetoothTethered = ((Boolean) method.invoke(FirewallService.getBtPanProfile())).booleanValue();
|
||||
d.tetherBluetoothStatusKnown = true;
|
||||
Log.d(TAG, "isBluetoothTetheringOn is " + d.isBluetoothTethered);
|
||||
} catch (Exception e) {
|
||||
Log.e(Api.TAG, android.util.Log.getStackTraceString(e));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void getUsbTetherStatus(Context context, InterfaceDetails d) {
|
||||
ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
|
||||
if (cm == null) {
|
||||
d.isUsbTethered = false;
|
||||
d.tetherUsbStatusKnown = false;
|
||||
return;
|
||||
}
|
||||
|
||||
d.isUsbTethered = false;
|
||||
d.tetherUsbStatusKnown = false;
|
||||
|
||||
try {
|
||||
// Use reflection to access hidden getTetheredIfaces() method
|
||||
Method getTetheredIfaces = cm.getClass().getDeclaredMethod("getTetheredIfaces");
|
||||
getTetheredIfaces.setAccessible(true);
|
||||
String[] tetheredIfaces = (String[]) getTetheredIfaces.invoke(cm);
|
||||
|
||||
if (tetheredIfaces != null) {
|
||||
for (String iface : tetheredIfaces) {
|
||||
// USB tethering typically uses interfaces like "rndis0", "usb0", etc.
|
||||
if (iface != null && (iface.startsWith("rndis") || iface.startsWith("usb"))) {
|
||||
d.isUsbTethered = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
d.tetherUsbStatusKnown = true;
|
||||
Log.d(TAG, "USB tethering status: " + d.isUsbTethered);
|
||||
|
||||
} catch (Exception e) {
|
||||
Log.e(G.TAG, "Exception in getting USB tether status");
|
||||
Log.e(Api.TAG, android.util.Log.getStackTraceString(e));
|
||||
|
||||
// Fallback: Check if USB tethering interface exists using NetworkInterface
|
||||
try {
|
||||
d.isUsbTethered = isUsbTetherInterfaceUp();
|
||||
d.tetherUsbStatusKnown = true;
|
||||
Log.d(TAG, "USB tethering status (fallback): " + d.isUsbTethered);
|
||||
} catch (Exception fallbackException) {
|
||||
Log.e(Api.TAG, "Fallback USB tether detection failed: " + android.util.Log.getStackTraceString(fallbackException));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isUsbTetherInterfaceUp() {
|
||||
try {
|
||||
java.util.Enumeration<java.net.NetworkInterface> interfaces = java.net.NetworkInterface.getNetworkInterfaces();
|
||||
while (interfaces.hasMoreElements()) {
|
||||
java.net.NetworkInterface networkInterface = interfaces.nextElement();
|
||||
String name = networkInterface.getName();
|
||||
if (name != null && (name.startsWith("rndis") || name.startsWith("usb")) && networkInterface.isUp()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.e(Api.TAG, "Error checking network interfaces: " + android.util.Log.getStackTraceString(e));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static InterfaceDetails getInterfaceDetails(Context context) {
|
||||
|
||||
InterfaceDetails ret = new InterfaceDetails();
|
||||
|
||||
ConnectivityManager cm = (ConnectivityManager) context
|
||||
.getSystemService(Context.CONNECTIVITY_SERVICE);
|
||||
|
||||
NetworkInfo info = cm.getActiveNetworkInfo();
|
||||
|
||||
if (info == null || !info.isConnected()) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
switch (info.getType()) {
|
||||
case ConnectivityManager.TYPE_MOBILE:
|
||||
case ConnectivityManager.TYPE_MOBILE_DUN:
|
||||
case ConnectivityManager.TYPE_MOBILE_HIPRI:
|
||||
case ConnectivityManager.TYPE_MOBILE_MMS:
|
||||
case ConnectivityManager.TYPE_MOBILE_SUPL:
|
||||
case ConnectivityManager.TYPE_WIMAX:
|
||||
ret.isRoaming = info.isRoaming();
|
||||
ret.netType = ConnectivityManager.TYPE_MOBILE;
|
||||
ret.netEnabled = true;
|
||||
break;
|
||||
case ConnectivityManager.TYPE_WIFI:
|
||||
case ConnectivityManager.TYPE_BLUETOOTH:
|
||||
case ConnectivityManager.TYPE_ETHERNET:
|
||||
ret.netType = ConnectivityManager.TYPE_WIFI;
|
||||
ret.netEnabled = true;
|
||||
break;
|
||||
}
|
||||
try {
|
||||
if(G.enableTether()) {
|
||||
getTetherStatus(context, ret);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.i(Api.TAG, "Exception in getInterfaceDetails.checkTether" + e.getLocalizedMessage());
|
||||
}
|
||||
NewInterfaceScanner.populateLanMasks(ret);
|
||||
getDnsServers(context, ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
private static void getDnsServers(Context context, InterfaceDetails d) {
|
||||
d.dnsServersV4.clear();
|
||||
d.dnsServersV6.clear();
|
||||
|
||||
try {
|
||||
ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
|
||||
if (cm == null) return;
|
||||
|
||||
// Get active network
|
||||
android.net.Network activeNetwork = null;
|
||||
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) {
|
||||
activeNetwork = cm.getActiveNetwork();
|
||||
}
|
||||
|
||||
if (activeNetwork != null && android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) {
|
||||
// Modern approach using LinkProperties (API 23+)
|
||||
android.net.LinkProperties linkProperties = cm.getLinkProperties(activeNetwork);
|
||||
if (linkProperties != null) {
|
||||
for (java.net.InetAddress dns : linkProperties.getDnsServers()) {
|
||||
if (dns instanceof java.net.Inet4Address) {
|
||||
d.dnsServersV4.add(dns.getHostAddress());
|
||||
} else if (dns instanceof java.net.Inet6Address) {
|
||||
d.dnsServersV6.add(dns.getHostAddress());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback: Try system properties (older Android versions or backup method)
|
||||
if (d.dnsServersV4.isEmpty() && d.dnsServersV6.isEmpty()) {
|
||||
getDnsFromSystemProperties(d);
|
||||
}
|
||||
|
||||
Log.d(TAG, "DNS servers IPv4: " + d.dnsServersV4);
|
||||
Log.d(TAG, "DNS servers IPv6: " + d.dnsServersV6);
|
||||
|
||||
} catch (Exception e) {
|
||||
Log.e(Api.TAG, "Exception getting DNS servers: " + android.util.Log.getStackTraceString(e));
|
||||
}
|
||||
}
|
||||
|
||||
private static void getDnsFromSystemProperties(InterfaceDetails d) {
|
||||
try {
|
||||
// Try common system properties for DNS servers
|
||||
String[] dnsProps = {"net.dns1", "net.dns2", "net.dns3", "net.dns4"};
|
||||
|
||||
for (String prop : dnsProps) {
|
||||
String dnsServer = System.getProperty(prop);
|
||||
if (dnsServer != null && !dnsServer.isEmpty() && !dnsServer.equals("0.0.0.0")) {
|
||||
try {
|
||||
java.net.InetAddress addr = java.net.InetAddress.getByName(dnsServer);
|
||||
if (addr instanceof java.net.Inet4Address) {
|
||||
if (!d.dnsServersV4.contains(dnsServer)) {
|
||||
d.dnsServersV4.add(dnsServer);
|
||||
}
|
||||
} else if (addr instanceof java.net.Inet6Address) {
|
||||
if (!d.dnsServersV6.contains(dnsServer)) {
|
||||
d.dnsServersV6.add(dnsServer);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.w(TAG, "Invalid DNS server address: " + dnsServer);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.e(Api.TAG, "Exception getting DNS from system properties: " + android.util.Log.getStackTraceString(e));
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean checkForNewCfg(Context context) {
|
||||
InterfaceDetails newCfg = getInterfaceDetails(context);
|
||||
|
||||
//always check for new config
|
||||
if (currentCfg != null && currentCfg.equals(newCfg)) {
|
||||
return false;
|
||||
}
|
||||
Log.i(TAG, "Getting interface details...");
|
||||
|
||||
currentCfg = newCfg;
|
||||
|
||||
if (!newCfg.netEnabled) {
|
||||
Log.i(TAG, "Now assuming NO connection (all interfaces down)");
|
||||
} else {
|
||||
if (newCfg.netType == ConnectivityManager.TYPE_WIFI) {
|
||||
Log.i(TAG, "Now assuming wifi connection (" +
|
||||
"bluetooth-tethered: " + (newCfg.isBluetoothTethered ? "yes" : "no") + ", " +
|
||||
"usb-tethered: " + (newCfg.isUsbTethered ? "yes" : "no") + ")");
|
||||
} else if (newCfg.netType == ConnectivityManager.TYPE_MOBILE) {
|
||||
Log.i(TAG, "Now assuming 3G connection (" +
|
||||
"roaming: " + (newCfg.isRoaming ? "yes" : "no") +
|
||||
"wifi-tethered: " + (newCfg.isWifiTethered ? "yes" : "no") + ", " +
|
||||
"bluetooth-tethered: " + (newCfg.isBluetoothTethered ? "yes" : "no") + ", " +
|
||||
"usb-tethered: " + (newCfg.isUsbTethered ? "yes" : "no") + ")");
|
||||
} else if (newCfg.netType == ConnectivityManager.TYPE_BLUETOOTH) {
|
||||
Log.i(TAG, "Now assuming bluetooth connection (" +
|
||||
"wifi-tethered: " + (newCfg.isWifiTethered ? "yes" : "no") + ", " +
|
||||
"usb-tethered: " + (newCfg.isUsbTethered ? "yes" : "no") + ")");
|
||||
}
|
||||
|
||||
if (!newCfg.lanMaskV4.equals("")) {
|
||||
Log.i(TAG, "IPv4 LAN netmask on " + newCfg.wifiName + ": " + newCfg.lanMaskV4);
|
||||
}
|
||||
if (!newCfg.lanMaskV6.equals("")) {
|
||||
Log.i(TAG, "IPv6 LAN netmask on " + newCfg.wifiName + ": " + newCfg.lanMaskV6);
|
||||
}
|
||||
if (newCfg.lanMaskV6.equals("") && newCfg.lanMaskV4.equals("")) {
|
||||
Log.i(TAG, "No ipaddress found");
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public static InterfaceDetails getCurrentCfg(Context context, boolean force) {
|
||||
Log.i(TAG, "Forcing configuration: " + force);
|
||||
if (currentCfg == null || force) {
|
||||
currentCfg = getInterfaceDetails(context);
|
||||
}
|
||||
return currentCfg;
|
||||
}
|
||||
|
||||
public static void applyRulesOnChange(Context context, final String reason) {
|
||||
final Context ctx = context.getApplicationContext();
|
||||
if (!checkForNewCfg(ctx)) {
|
||||
Log.d(TAG, reason + ": interface state has not changed, ignoring");
|
||||
return;
|
||||
} else if (!Api.isEnabled(ctx)) {
|
||||
Log.d(TAG, reason + ": firewall is disabled, ignoring");
|
||||
return;
|
||||
}
|
||||
Log.d(TAG, reason + " applying rules");
|
||||
// update Api.PREFS_NAME so we pick up the right profile
|
||||
// REVISIT: this can be removed once we're confident that G is in sync with profile changes
|
||||
G.reloadPrefs();
|
||||
|
||||
if (reason.equals(InterfaceTracker.BOOT_COMPLETED) || reason.startsWith(InterfaceTracker.BOOT_COMPLETED)) {
|
||||
Log.i(TAG, "Applying boot-specific rules for reason: " + reason);
|
||||
applyBootRules(reason);
|
||||
} else {
|
||||
Log.i(TAG, "Applying regular rules for reason: " + reason);
|
||||
applyRules(reason);
|
||||
}
|
||||
}
|
||||
|
||||
public static void applyRules(final String reason) {
|
||||
Api.fastApply(ctx, new RootCommand()
|
||||
.setFailureToast(R.string.error_apply)
|
||||
.setCallback(new RootCommand.Callback() {
|
||||
@Override
|
||||
public void cbFunc(RootCommand state) {
|
||||
if (state.exitCode == 0) {
|
||||
Log.i(TAG, reason + ": applied rules at " + System.currentTimeMillis());
|
||||
Api.applyDefaultChains(ctx, new RootCommand()
|
||||
.setCallback(new RootCommand.Callback() {
|
||||
@Override
|
||||
public void cbFunc(RootCommand state) {
|
||||
if (state.exitCode != 0) {
|
||||
Api.errorNotification(ctx);
|
||||
}
|
||||
}
|
||||
}));
|
||||
} else {
|
||||
//lets try applying all rules
|
||||
Api.setRulesUpToDate(false);
|
||||
Api.fastApply(ctx, new RootCommand()
|
||||
.setCallback(new RootCommand.Callback() {
|
||||
@Override
|
||||
public void cbFunc(RootCommand state) {
|
||||
if (state.exitCode == 0) {
|
||||
Log.i(TAG, reason + ": applied rules at " + System.currentTimeMillis());
|
||||
} else {
|
||||
Log.e(TAG, reason + ": applySavedIptablesRules() returned an error");
|
||||
Api.errorNotification(ctx);
|
||||
}
|
||||
Api.applyDefaultChains(ctx, new RootCommand()
|
||||
.setFailureToast(R.string.error_apply)
|
||||
.setCallback(new RootCommand.Callback() {
|
||||
@Override
|
||||
public void cbFunc(RootCommand state) {
|
||||
if (state.exitCode != 0) {
|
||||
Api.errorNotification(ctx);
|
||||
}
|
||||
}
|
||||
}));
|
||||
}
|
||||
}));
|
||||
}
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
public static void applyBootRules(final String reason) {
|
||||
Api.applySavedIptablesRules(ctx, true, new RootCommand()
|
||||
.setFailureToast(R.string.error_apply)
|
||||
.setCallback(new RootCommand.Callback() {
|
||||
@Override
|
||||
public void cbFunc(RootCommand state) {
|
||||
if (state.exitCode == 0) {
|
||||
Log.i(TAG, reason + ": applied rules at " + System.currentTimeMillis());
|
||||
Api.applyDefaultChains(ctx, new RootCommand()
|
||||
.setCallback(new RootCommand.Callback() {
|
||||
@Override
|
||||
public void cbFunc(RootCommand state) {
|
||||
if (state.exitCode != 0) {
|
||||
Api.errorNotification(ctx);
|
||||
}
|
||||
}
|
||||
}));
|
||||
} else {
|
||||
//lets try applying all rules
|
||||
Api.setRulesUpToDate(false);
|
||||
Api.applySavedIptablesRules(ctx, true, new RootCommand()
|
||||
.setCallback(new RootCommand.Callback() {
|
||||
@Override
|
||||
public void cbFunc(RootCommand state) {
|
||||
if (state.exitCode == 0) {
|
||||
Log.i(TAG, reason + ": applied rules at " + System.currentTimeMillis());
|
||||
} else {
|
||||
Log.e(TAG, reason + ": applySavedIptablesRules() returned an error");
|
||||
Api.errorNotification(ctx);
|
||||
}
|
||||
Api.applyDefaultChains(ctx, new RootCommand()
|
||||
.setFailureToast(R.string.error_apply)
|
||||
.setCallback(new RootCommand.Callback() {
|
||||
@Override
|
||||
public void cbFunc(RootCommand state) {
|
||||
if (state.exitCode != 0) {
|
||||
Api.errorNotification(ctx);
|
||||
}
|
||||
}
|
||||
}));
|
||||
}
|
||||
}));
|
||||
}
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
private static class NewInterfaceScanner {
|
||||
|
||||
public static void populateLanMasks(InterfaceDetails ret) {
|
||||
try {
|
||||
Enumeration<NetworkInterface> en = NetworkInterface.getNetworkInterfaces();
|
||||
|
||||
while (en.hasMoreElements()) {
|
||||
NetworkInterface intf = en.nextElement();
|
||||
boolean match = false;
|
||||
|
||||
if (!intf.isUp() || intf.isLoopback()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (String pattern : ITFS_WIFI) {
|
||||
if (intf.getName().startsWith(truncAfter(pattern, "\\+"))) {
|
||||
match = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!match)
|
||||
continue;
|
||||
ret.wifiName = intf.getName();
|
||||
|
||||
Iterator<InterfaceAddress> addrList = intf.getInterfaceAddresses().iterator();
|
||||
while (addrList.hasNext()) {
|
||||
InterfaceAddress addr = addrList.next();
|
||||
InetAddress ip = addr.getAddress();
|
||||
String mask = truncAfter(ip.getHostAddress(), "%") + "/" +
|
||||
addr.getNetworkPrefixLength();
|
||||
|
||||
if(ret.lanMaskV4.isEmpty() || ret.lanMaskV6.isEmpty()) {
|
||||
if (ip instanceof Inet4Address) {
|
||||
ret.lanMaskV4 = mask;
|
||||
} else if (ip instanceof Inet6Address) {
|
||||
ret.lanMaskV6 = mask;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (ret.lanMaskV4.equals("") && ret.lanMaskV6.equals("")) {
|
||||
ret.noIP = true;
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.i(TAG, "Error fetching network interface list: " + android.util.Log.getStackTraceString(e));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
2723
app/src/main/java/dev/ukanth/ufirewall/MainActivity.java
Normal file
2723
app/src/main/java/dev/ukanth/ufirewall/MainActivity.java
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -0,0 +1,232 @@
|
|||
package dev.ukanth.ufirewall.activity;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.content.pm.PackageInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.PackageManager.NameNotFoundException;
|
||||
import android.os.Bundle;
|
||||
import android.view.MenuItem;
|
||||
import android.widget.Button;
|
||||
import android.widget.CheckBox;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
|
||||
import com.raizlabs.android.dbflow.sql.language.SQLite;
|
||||
import com.stericson.rootshell.execution.Command;
|
||||
import com.stericson.rootshell.execution.Shell;
|
||||
import com.stericson.roottools.RootTools;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
|
||||
import dev.ukanth.ufirewall.Api;
|
||||
import dev.ukanth.ufirewall.R;
|
||||
import dev.ukanth.ufirewall.log.Log;
|
||||
import dev.ukanth.ufirewall.log.LogPreference;
|
||||
import dev.ukanth.ufirewall.log.LogPreference_Table;
|
||||
import dev.ukanth.ufirewall.util.G;
|
||||
|
||||
public class AppDetailActivity extends AppCompatActivity {
|
||||
public static final String TAG = "AFWall";
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
initTheme();
|
||||
setTitle(getString(R.string.traffic_detail_title));
|
||||
setContentView(R.layout.app_detail);
|
||||
|
||||
int appid = getIntent().getIntExtra("appid", -1);
|
||||
String packageName = getIntent().getStringExtra("package");
|
||||
try {
|
||||
CheckBox logOption = findViewById(R.id.notification_p);
|
||||
LogPreference logPreference = SQLite.select()
|
||||
.from(LogPreference.class)
|
||||
.where(LogPreference_Table.uid.eq(appid)).querySingle();
|
||||
|
||||
if (logPreference != null) {
|
||||
logOption.setChecked(logPreference.isDisable());
|
||||
}
|
||||
|
||||
logOption.setOnCheckedChangeListener((buttonView, isChecked) -> {
|
||||
//only use when triggered by user
|
||||
if (buttonView.isPressed()) {
|
||||
// write the logic here
|
||||
G.updateLogNotification(appid, isChecked);
|
||||
}
|
||||
});
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
|
||||
Toolbar toolbar = findViewById(R.id.app_toolbar);
|
||||
setSupportActionBar(toolbar);
|
||||
|
||||
if (getSupportActionBar() != null) {
|
||||
getSupportActionBar().setHomeButtonEnabled(true);
|
||||
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
||||
}
|
||||
|
||||
final Context ctx = getApplicationContext();
|
||||
|
||||
ImageView image = findViewById(R.id.app_icon);
|
||||
TextView textView = findViewById(R.id.app_title);
|
||||
TextView textView2 = findViewById(R.id.app_package);
|
||||
TextView up = findViewById(R.id.up);
|
||||
TextView down = findViewById(R.id.down);
|
||||
|
||||
/**/
|
||||
|
||||
final PackageManager packageManager = getApplicationContext().getPackageManager();
|
||||
|
||||
HashMap<Integer,String> listMaps = Api.getPackagesForUser(Api.getListOfUids());
|
||||
String packageNameList = "";
|
||||
PackageInfo packageInfo = Api.getPackageDetails(ctx, listMaps, appid);
|
||||
if(packageInfo != null && packageInfo.applicationInfo != null) {
|
||||
packageNameList = packageInfo.applicationInfo.name;
|
||||
}
|
||||
|
||||
//final String[] packageNameList = ctx.getPackageManager().getPackagesForUid(appid);
|
||||
|
||||
final String pName = packageName;
|
||||
Button button = findViewById(R.id.app_settings);
|
||||
button.setOnClickListener(v -> Api.showInstalledAppDetails(getApplicationContext(), pName));
|
||||
ApplicationInfo applicationInfo;
|
||||
|
||||
try {
|
||||
assert packageName != null;
|
||||
if (!packageName.startsWith("dev.afwall.special.")) {
|
||||
applicationInfo = packageManager.getApplicationInfo(packageName, PackageManager.GET_META_DATA);
|
||||
try {
|
||||
image.setImageDrawable(applicationInfo.loadIcon(packageManager));
|
||||
} catch (Exception e){
|
||||
image.setImageDrawable(applicationInfo.loadIcon(packageManager));
|
||||
}
|
||||
String name = packageManager.getApplicationLabel(applicationInfo).toString();
|
||||
textView.setText(name);
|
||||
setTotalBytesManual(down, up, applicationInfo.uid);
|
||||
} else {
|
||||
image.setImageDrawable(getApplicationContext().getDrawable(R.drawable.ic_unknown));
|
||||
if(appid >= 0) {
|
||||
textView.setText(Api.getSpecialDescription(getApplicationContext(), packageName.replace("dev.afwall.special.", "")));
|
||||
} else {
|
||||
textView.setText(Api.getSpecialDescriptionSystem(getApplicationContext(), packageName.replace("dev.afwall.special.", "")));
|
||||
}
|
||||
down.setText(" : " + humanReadableByteCount(0, false));
|
||||
up.setText(" : " + humanReadableByteCount(0, false));
|
||||
button.setEnabled(false);
|
||||
}
|
||||
|
||||
if (packageNameList != null) {
|
||||
textView2.setText(packageNameList);
|
||||
button.setEnabled(false);
|
||||
} else {
|
||||
textView2.setText(packageName);
|
||||
}
|
||||
|
||||
} catch (final NameNotFoundException e) {
|
||||
down.setText(" : " + humanReadableByteCount(0, false));
|
||||
up.setText(" : " + humanReadableByteCount(0, false));
|
||||
button.setEnabled(false);
|
||||
}
|
||||
}
|
||||
|
||||
private void initTheme() {
|
||||
switch(G.getSelectedTheme()) {
|
||||
case "D":
|
||||
setTheme(R.style.AppDarkTheme);
|
||||
break;
|
||||
case "L":
|
||||
setTheme(R.style.AppLightTheme);
|
||||
//set other colors
|
||||
break;
|
||||
case "B":
|
||||
setTheme(R.style.AppBlackTheme);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void setTotalBytesManual(TextView down, TextView up, int localUid) {
|
||||
File dir = new File("/proc/uid_stat/");
|
||||
down.setText(" : " + humanReadableByteCount(Long.parseLong("0"), false));
|
||||
up.setText(" : " + humanReadableByteCount(Long.parseLong("0"), false));
|
||||
if (dir.exists()) {
|
||||
String[] children = dir.list();
|
||||
if (children != null) {
|
||||
if (!Arrays.asList(children).contains(String.valueOf(localUid))) {
|
||||
down.setText(" : " + humanReadableByteCount(Long.parseLong("0"), false));
|
||||
up.setText(" : " + humanReadableByteCount(Long.parseLong("0"), false));
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
down.setText(" : " + humanReadableByteCount(Long.parseLong("0"), false));
|
||||
up.setText(" : " + humanReadableByteCount(Long.parseLong("0"), false));
|
||||
return;
|
||||
}
|
||||
|
||||
File uidFileDir = new File("/proc/uid_stat/" + localUid);
|
||||
if (uidFileDir.exists()) {
|
||||
File uidActualFileReceived = new File(uidFileDir, "tcp_rcv");
|
||||
File uidActualFileSent = new File(uidFileDir, "tcp_snd");
|
||||
String textReceived = "0";
|
||||
String textSent = "0";
|
||||
try {
|
||||
if (uidActualFileReceived.exists() && uidActualFileSent.exists()) {
|
||||
Command command = new Command(0, "cat " + uidActualFileReceived.getAbsolutePath())
|
||||
{
|
||||
@Override
|
||||
public void commandOutput(int id, String line) {
|
||||
down.setText(" : " + humanReadableByteCount(Long.parseLong(line), false));
|
||||
super.commandOutput(id, line);
|
||||
}
|
||||
};
|
||||
Command command1 = new Command(1, "cat " + uidActualFileSent.getAbsolutePath())
|
||||
{
|
||||
@Override
|
||||
public void commandOutput(int id, String line) {
|
||||
up.setText(" : " + humanReadableByteCount(Long.parseLong(line), false));
|
||||
super.commandOutput(id, line);
|
||||
}
|
||||
};
|
||||
Shell shell = RootTools.getShell(true);
|
||||
shell.add(command);
|
||||
shell.add(command1);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Exception while reading tx bytes: " + e.getLocalizedMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
// return Long.valueOf(textReceived).longValue() + Long.valueOf(textReceived).longValue();
|
||||
}
|
||||
|
||||
public static String humanReadableByteCount(long bytes, boolean si) {
|
||||
int unit = si ? 1000 : 1024;
|
||||
if (bytes < 0) return "0 B";
|
||||
if (bytes < unit) return bytes + " B";
|
||||
int exp = (int) (Math.log(bytes) / Math.log(unit));
|
||||
String pre = (si ? "kMGTPE" : "KMGTPE").charAt(exp - 1) + (si ? "" : "i");
|
||||
return String.format("%.1f %sB", bytes / Math.pow(unit, exp), pre);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
case android.R.id.home:
|
||||
onBackPressed();
|
||||
return true;
|
||||
}
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
super.onDestroy();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
package dev.ukanth.ufirewall.activity;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.ContextWrapper;
|
||||
import android.content.res.Configuration;
|
||||
import android.content.res.Resources;
|
||||
import android.os.Build;
|
||||
import android.os.LocaleList;
|
||||
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
public class BaseActivity extends AppCompatActivity {
|
||||
|
||||
@Override
|
||||
protected void attachBaseContext(Context newBase) {
|
||||
super.attachBaseContext(newBase);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,101 @@
|
|||
package dev.ukanth.ufirewall.activity;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.util.DisplayMetrics;
|
||||
import android.view.Gravity;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.appcompat.widget.SwitchCompat;
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
import androidx.cardview.widget.CardView;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import dev.ukanth.ufirewall.R;
|
||||
import dev.ukanth.ufirewall.util.CustomRuleOld;
|
||||
import dev.ukanth.ufirewall.util.Rule;
|
||||
|
||||
public class CustomRulesActivity extends AppCompatActivity {
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
final View view = getLayoutInflater().inflate(R.layout.activity_custom_rules, null);
|
||||
setTitle(R.string.custom_rules);
|
||||
|
||||
LinearLayout linearLayout = view.findViewById(R.id.activity_custom_rules);
|
||||
try {
|
||||
|
||||
List<Rule> rules = CustomRuleOld.getRules(getApplicationContext());
|
||||
for (final Rule rule : rules) {
|
||||
CardView cardView = new CardView(this);
|
||||
cardView.setElevation(5);
|
||||
cardView.setRadius(5);
|
||||
cardView.setLayoutParams(new LinearLayout.LayoutParams(
|
||||
LinearLayout.LayoutParams.WRAP_CONTENT, 100));
|
||||
cardView.setMinimumHeight(60);
|
||||
|
||||
|
||||
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT);
|
||||
params.height = 200;
|
||||
params.gravity = Gravity.TOP;
|
||||
params.setMargins(0, 2, 0, 40);
|
||||
cardView.setLayoutParams(params);
|
||||
|
||||
|
||||
SwitchCompat switchButton = new SwitchCompat(this);
|
||||
switchButton.setTag(rule.getName());
|
||||
switchButton.setOnCheckedChangeListener((buttonView, isChecked) -> {
|
||||
Toast.makeText(getApplicationContext(), buttonView.getTag().toString() + isChecked, Toast.LENGTH_SHORT).show();
|
||||
});
|
||||
switchButton.setChecked(false);
|
||||
switchButton.setContentDescription(rule.getDesc());
|
||||
String builder = rule.getName() +
|
||||
"\n" +
|
||||
rule.getDesc() +
|
||||
"\n";
|
||||
switchButton.setText(builder);
|
||||
switchButton.setTextSize(18);
|
||||
|
||||
switchButton.setLayoutParams(params);
|
||||
|
||||
cardView.addView(switchButton);
|
||||
linearLayout.addView(cardView);
|
||||
}
|
||||
} catch (Exception ignored) {
|
||||
|
||||
}
|
||||
|
||||
setContentView(view);
|
||||
|
||||
Toolbar toolbar = findViewById(R.id.custom_toolbar_rules);
|
||||
setSupportActionBar(toolbar);
|
||||
|
||||
if (getSupportActionBar() != null) {
|
||||
getSupportActionBar().setHomeButtonEnabled(true);
|
||||
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
case android.R.id.home:
|
||||
onBackPressed();
|
||||
return true;
|
||||
}
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
public int dpToPixels(int dp) {
|
||||
DisplayMetrics displayMetrics = getApplicationContext().getResources().getDisplayMetrics();
|
||||
return Math.round(dp * (displayMetrics.xdpi / DisplayMetrics.DENSITY_DEFAULT));
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,159 @@
|
|||
/**
|
||||
* Custom scripts activity.
|
||||
* This screen is displayed to change the custom scripts.
|
||||
* <p>
|
||||
* Copyright (C) 2009-2011 Rodrigo Zechin Rosauro
|
||||
* Copyright (C) 2011-2012 Umakanthan Chandran
|
||||
* <p>
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
* <p>
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
* <p>
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* @author Rodrigo Zechin Rosauro, Umakanthan Chandran
|
||||
* @version 1.1
|
||||
*/
|
||||
package dev.ukanth.ufirewall.activity;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.Bundle;
|
||||
import android.text.method.LinkMovementMethod;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.View.OnClickListener;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.google.android.material.textfield.TextInputEditText;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
|
||||
import com.afollestad.materialdialogs.DialogAction;
|
||||
import com.afollestad.materialdialogs.MaterialDialog;
|
||||
|
||||
import dev.ukanth.ufirewall.Api;
|
||||
import dev.ukanth.ufirewall.R;
|
||||
import dev.ukanth.ufirewall.util.G;
|
||||
|
||||
/**
|
||||
* Custom scripts activity.
|
||||
* This screen is displayed to change the custom scripts.
|
||||
*/
|
||||
public class CustomScriptActivity extends AppCompatActivity implements OnClickListener {
|
||||
private TextInputEditText script;
|
||||
private TextInputEditText script2;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
initTheme();
|
||||
setContentView(R.layout.customscript);
|
||||
|
||||
findViewById(R.id.customscript_ok).setOnClickListener(this);
|
||||
findViewById(R.id.customscript_cancel).setOnClickListener(this);
|
||||
((TextView) findViewById(R.id.customscript_link)).setMovementMethod(LinkMovementMethod.getInstance());
|
||||
|
||||
final SharedPreferences prefs = getSharedPreferences(Api.PREFS_NAME, 0);
|
||||
this.script = findViewById(R.id.customscript);
|
||||
this.script.setText(prefs.getString(Api.PREF_CUSTOMSCRIPT, ""));
|
||||
this.script2 = findViewById(R.id.customscript2);
|
||||
this.script2.setText(prefs.getString(Api.PREF_CUSTOMSCRIPT2, ""));
|
||||
|
||||
setTitle(R.string.set_custom_script);
|
||||
|
||||
Toolbar toolbar = findViewById(R.id.custom_toolbar);
|
||||
setSupportActionBar(toolbar);
|
||||
|
||||
if (getSupportActionBar() != null) {
|
||||
getSupportActionBar().setHomeButtonEnabled(true);
|
||||
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void initTheme() {
|
||||
switch(G.getSelectedTheme()) {
|
||||
case "D":
|
||||
setTheme(R.style.AppDarkTheme);
|
||||
break;
|
||||
case "L":
|
||||
setTheme(R.style.AppLightTheme);
|
||||
break;
|
||||
case "B":
|
||||
setTheme(R.style.AppBlackTheme);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
case android.R.id.home:
|
||||
onBackPressed();
|
||||
return true;
|
||||
}
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the activity result to RESULT_OK and terminate this activity.
|
||||
*/
|
||||
private void resultOk() {
|
||||
final Intent response = new Intent(Api.CUSTOM_SCRIPT_MSG);
|
||||
response.putExtra(Api.SCRIPT_EXTRA, script.getText().toString());
|
||||
response.putExtra(Api.SCRIPT2_EXTRA, script2.getText().toString());
|
||||
setResult(RESULT_OK, response);
|
||||
finish();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
if (v.getId() == R.id.customscript_ok) {
|
||||
resultOk();
|
||||
} else {
|
||||
setResult(RESULT_CANCELED);
|
||||
finish();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
final SharedPreferences prefs = getSharedPreferences(Api.PREFS_NAME, 0);
|
||||
if (script.getText().toString().equals(prefs.getString(Api.PREF_CUSTOMSCRIPT, ""))
|
||||
&& script2.getText().toString().equals(prefs.getString(Api.PREF_CUSTOMSCRIPT2, ""))) {
|
||||
// Nothing has been changed, just return
|
||||
super.onBackPressed();
|
||||
return;
|
||||
}
|
||||
new MaterialDialog.Builder(this)
|
||||
.title(R.string.unsaved_changes)
|
||||
.content(R.string.unsaved_changes_message)
|
||||
.positiveText(R.string.apply)
|
||||
.negativeText(R.string.discard)
|
||||
.onPositive(new MaterialDialog.SingleButtonCallback() {
|
||||
@Override
|
||||
public void onClick(@NonNull MaterialDialog dialog, @NonNull DialogAction which) {
|
||||
resultOk();
|
||||
}
|
||||
})
|
||||
.onNegative(new MaterialDialog.SingleButtonCallback() {
|
||||
@Override
|
||||
public void onClick(@NonNull MaterialDialog dialog, @NonNull DialogAction which) {
|
||||
findViewById(R.id.customscript_cancel).performClick();
|
||||
}
|
||||
})
|
||||
.show();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,480 @@
|
|||
/**
|
||||
* Common framework for LogActivity and RulesActivity
|
||||
* <p>
|
||||
* Copyright (C) 2011-2012 Umakanthan Chandran
|
||||
* Copyright (C) 2011-2013 Kevin Cernekee
|
||||
* <p>
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
* <p>
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
* <p>
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* @author Umakanthan Chandran
|
||||
* @version 1.0
|
||||
*/
|
||||
|
||||
package dev.ukanth.ufirewall.activity;
|
||||
|
||||
import android.Manifest;
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
import android.content.pm.PackageManager;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.Environment;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.util.TypedValue;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.SubMenu;
|
||||
import android.view.View;
|
||||
import android.widget.ScrollView;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
import androidx.core.app.ActivityCompat;
|
||||
import androidx.cardview.widget.CardView;
|
||||
import androidx.core.widget.NestedScrollView;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.lang.ref.WeakReference;
|
||||
|
||||
import dev.ukanth.ufirewall.Api;
|
||||
import dev.ukanth.ufirewall.R;
|
||||
import dev.ukanth.ufirewall.log.Log;
|
||||
import dev.ukanth.ufirewall.util.G;
|
||||
|
||||
|
||||
public abstract class DataDumpActivity extends AppCompatActivity {
|
||||
|
||||
public static final String TAG = "AFWall";
|
||||
|
||||
protected static final int MENU_TOGGLE = -3;
|
||||
protected static final int MENU_COPY = 16;
|
||||
protected static final int MENU_EXPORT_LOG = 17;
|
||||
protected static final int MENU_REFRESH = 13;
|
||||
|
||||
protected static final int MENU_ZOOM_IN = 22;
|
||||
protected static final int MENU_ZOOM_OUT = 23;
|
||||
TextView scaleGesture;
|
||||
View mScrollView; // Can be either ScrollView or NestedScrollView
|
||||
|
||||
// Modern layout components
|
||||
private TextView rulesTitle;
|
||||
private TextView rulesStatus;
|
||||
private TextView rulesContent;
|
||||
private TextView interfacesContent;
|
||||
private TextView systemContent;
|
||||
private TextView preferencesContent;
|
||||
private TextView logcatContent;
|
||||
private CardView interfacesCard;
|
||||
private CardView systemCard;
|
||||
private CardView preferencesCard;
|
||||
private CardView logcatCard;
|
||||
private boolean useModernLayout = true;
|
||||
|
||||
protected Menu mainMenu;
|
||||
protected static String dataText;
|
||||
|
||||
// to be filled in by subclasses
|
||||
protected static String sdDumpFile = "iptables.log";
|
||||
|
||||
protected abstract void populateMenu(SubMenu sub);
|
||||
|
||||
protected abstract void populateData(final Context ctx);
|
||||
|
||||
private static final int MY_PERMISSIONS_REQUEST_WRITE_STORAGE = 1;
|
||||
|
||||
private void initModernViews() {
|
||||
rulesTitle = findViewById(R.id.rules_title);
|
||||
rulesStatus = findViewById(R.id.rules_status);
|
||||
rulesContent = findViewById(R.id.rules_content);
|
||||
interfacesContent = findViewById(R.id.interfaces_content);
|
||||
systemContent = findViewById(R.id.system_content);
|
||||
preferencesContent = findViewById(R.id.preferences_content);
|
||||
logcatContent = findViewById(R.id.logcat_content);
|
||||
interfacesCard = findViewById(R.id.interfaces_card);
|
||||
systemCard = findViewById(R.id.system_card);
|
||||
preferencesCard = findViewById(R.id.preferences_card);
|
||||
logcatCard = findViewById(R.id.logcat_card);
|
||||
}
|
||||
|
||||
protected void setData(final String data) {
|
||||
dataText = data;
|
||||
Handler refresh = new Handler(Looper.getMainLooper());
|
||||
refresh.post(() -> {
|
||||
if (useModernLayout) {
|
||||
parseAndDisplayModernData(data);
|
||||
} else {
|
||||
scaleGesture = findViewById(R.id.rules);
|
||||
scaleGesture.setText(data);
|
||||
scaleGesture.setTextSize(TypedValue.COMPLEX_UNIT_PX, G.ruleTextSize());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@SuppressLint("SetTextI18n")
|
||||
private void parseAndDisplayModernData(String data) {
|
||||
// Initialize all sections as empty and hide cards
|
||||
rulesContent.setText("");
|
||||
interfacesContent.setText("");
|
||||
systemContent.setText("");
|
||||
preferencesContent.setText("");
|
||||
logcatContent.setText("");
|
||||
|
||||
interfacesCard.setVisibility(View.GONE);
|
||||
systemCard.setVisibility(View.GONE);
|
||||
preferencesCard.setVisibility(View.GONE);
|
||||
logcatCard.setVisibility(View.GONE);
|
||||
|
||||
// Parse sections more intelligently by looking for section headers
|
||||
String[] lines = data.split("\n");
|
||||
StringBuilder currentSection = new StringBuilder();
|
||||
String currentSectionType = null;
|
||||
|
||||
for (String line : lines) {
|
||||
// Check if this is a section header (starts with =, contains title, ends with =)
|
||||
if (line.matches("^=+$")) {
|
||||
// Skip separator lines
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check if this line is a section title
|
||||
String sectionType = detectSectionType(line.trim());
|
||||
|
||||
if (sectionType != null) {
|
||||
// Process previous section if it exists
|
||||
if (currentSectionType != null && currentSection.length() > 0) {
|
||||
processSectionContent(currentSectionType, currentSection.toString());
|
||||
}
|
||||
|
||||
// Start new section
|
||||
currentSectionType = sectionType;
|
||||
currentSection = new StringBuilder();
|
||||
continue;
|
||||
}
|
||||
|
||||
// Add line to current section
|
||||
if (currentSectionType != null) {
|
||||
currentSection.append(line).append("\n");
|
||||
}
|
||||
}
|
||||
|
||||
// Process the last section
|
||||
if (currentSectionType != null && currentSection.length() > 0) {
|
||||
processSectionContent(currentSectionType, currentSection.toString());
|
||||
}
|
||||
|
||||
// Set font sizes for all content views
|
||||
float textSize = G.ruleTextSize();
|
||||
rulesContent.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize);
|
||||
interfacesContent.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize);
|
||||
systemContent.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize);
|
||||
preferencesContent.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize);
|
||||
logcatContent.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize);
|
||||
|
||||
// Keep the hidden TextView updated for backward compatibility (export, copy functions)
|
||||
TextView hiddenRules = findViewById(R.id.rules);
|
||||
hiddenRules.setText(data);
|
||||
}
|
||||
|
||||
private String detectSectionType(String line) {
|
||||
String trimmed = line.trim();
|
||||
if (trimmed.equals(getString(R.string.ipv4_rules_title))) {
|
||||
return "ipv4_rules";
|
||||
} else if (trimmed.equals(getString(R.string.ipv6_rules_title))) {
|
||||
return "ipv6_rules";
|
||||
} else if (trimmed.contains("Network interfaces")) {
|
||||
return "interfaces";
|
||||
} else if (trimmed.contains("ifconfig")) {
|
||||
return "ifconfig";
|
||||
} else if (trimmed.contains("System info")) {
|
||||
return "system";
|
||||
} else if (trimmed.contains("Preferences")) {
|
||||
return "preferences";
|
||||
} else if (trimmed.contains("Logcat")) {
|
||||
return "logcat";
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private void processSectionContent(String sectionType, String content) {
|
||||
String trimmedContent = content.trim();
|
||||
if (trimmedContent.isEmpty()) return;
|
||||
|
||||
switch (sectionType) {
|
||||
case "ipv4_rules":
|
||||
rulesTitle.setText(getString(R.string.ipv4_rules_title));
|
||||
rulesStatus.setText(getString(R.string.ready));
|
||||
rulesContent.setText(trimmedContent);
|
||||
break;
|
||||
|
||||
case "ipv6_rules":
|
||||
rulesTitle.setText(getString(R.string.ipv6_rules_title));
|
||||
rulesStatus.setText(getString(R.string.ready));
|
||||
rulesContent.setText(trimmedContent);
|
||||
break;
|
||||
|
||||
case "interfaces":
|
||||
case "ifconfig":
|
||||
interfacesCard.setVisibility(View.VISIBLE);
|
||||
String existingInterfaces = interfacesContent.getText().toString();
|
||||
if (!existingInterfaces.isEmpty()) {
|
||||
interfacesContent.setText(existingInterfaces + "\n\n" + trimmedContent);
|
||||
} else {
|
||||
interfacesContent.setText(trimmedContent);
|
||||
}
|
||||
break;
|
||||
|
||||
case "system":
|
||||
systemCard.setVisibility(View.VISIBLE);
|
||||
systemContent.setText(trimmedContent);
|
||||
break;
|
||||
|
||||
case "preferences":
|
||||
preferencesCard.setVisibility(View.VISIBLE);
|
||||
String existingPrefs = preferencesContent.getText().toString();
|
||||
if (!existingPrefs.isEmpty()) {
|
||||
preferencesContent.setText(existingPrefs + "\n\n" + trimmedContent);
|
||||
} else {
|
||||
preferencesContent.setText(trimmedContent);
|
||||
}
|
||||
break;
|
||||
|
||||
case "logcat":
|
||||
logcatCard.setVisibility(View.VISIBLE);
|
||||
logcatContent.setText(trimmedContent);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void updateModernTextSize(float sizeDelta) {
|
||||
float currentSize = G.ruleTextSize();
|
||||
float newSize = currentSize + sizeDelta;
|
||||
|
||||
if (newSize < 8.0f) newSize = 8.0f; // Minimum size
|
||||
if (newSize > 30.0f) newSize = 30.0f; // Maximum size
|
||||
|
||||
G.ruleTextSize((int) newSize);
|
||||
|
||||
if (rulesContent != null) rulesContent.setTextSize(TypedValue.COMPLEX_UNIT_PX, newSize);
|
||||
if (interfacesContent != null) interfacesContent.setTextSize(TypedValue.COMPLEX_UNIT_PX, newSize);
|
||||
if (systemContent != null) systemContent.setTextSize(TypedValue.COMPLEX_UNIT_PX, newSize);
|
||||
if (preferencesContent != null) preferencesContent.setTextSize(TypedValue.COMPLEX_UNIT_PX, newSize);
|
||||
if (logcatContent != null) logcatContent.setTextSize(TypedValue.COMPLEX_UNIT_PX, newSize);
|
||||
}
|
||||
|
||||
private void initTheme() {
|
||||
switch (G.getSelectedTheme()) {
|
||||
case "D" -> setTheme(R.style.AppDarkTheme);
|
||||
case "L" -> setTheme(R.style.AppLightTheme);
|
||||
case "B" -> setTheme(R.style.AppBlackTheme);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
//requestWindowFeature(Window.FEATURE_ACTION_BAR_OVERLAY);
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
initTheme();
|
||||
if (useModernLayout) {
|
||||
setContentView(R.layout.rules_modern);
|
||||
initModernViews();
|
||||
} else {
|
||||
setContentView(R.layout.rules);
|
||||
}
|
||||
|
||||
Toolbar toolbar = findViewById(R.id.rule_toolbar);
|
||||
//toolbar.setTitle(getString(R.string.showrules_title));
|
||||
toolbar.setNavigationOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
finish();
|
||||
}
|
||||
});
|
||||
|
||||
setSupportActionBar(toolbar);
|
||||
|
||||
mScrollView = findViewById(R.id.ruleScrollView);
|
||||
|
||||
// Load partially transparent black background
|
||||
if (getSupportActionBar() != null) {
|
||||
getSupportActionBar().setHomeButtonEnabled(true);
|
||||
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
||||
}
|
||||
|
||||
setData("");
|
||||
populateData(this);
|
||||
|
||||
Api.updateLanguage(getApplicationContext(), G.locale());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(android.view.Menu menu) {
|
||||
// Common options: Copy, Export to SD Card, Refresh
|
||||
SubMenu sub = menu.addSubMenu(0, MENU_TOGGLE, 0, "").setIcon(R.drawable.ic_flow);
|
||||
sub.add(0, MENU_ZOOM_IN, 0, getString(R.string.label_zoomin)).setIcon(R.drawable.zoomin).setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
|
||||
sub.add(0, MENU_ZOOM_OUT, 0, getString(R.string.label_zoomout)).setIcon(R.drawable.zoomout).setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
|
||||
sub.add(0, MENU_COPY, 0, R.string.copy).setIcon(R.drawable.ic_copy);
|
||||
sub.add(0, MENU_EXPORT_LOG, 0, R.string.export_to_sd).setIcon(R.drawable.ic_export);
|
||||
sub.add(0, MENU_REFRESH, 0, R.string.refresh).setIcon(R.drawable.ic_refresh);
|
||||
|
||||
populateMenu(sub);
|
||||
|
||||
sub.getItem().setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS | MenuItem.SHOW_AS_ACTION_WITH_TEXT);
|
||||
|
||||
|
||||
super.onCreateOptionsMenu(menu);
|
||||
mainMenu = menu;
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
float newSize;
|
||||
switch (item.getItemId()) {
|
||||
case MENU_COPY -> {
|
||||
copy();
|
||||
return true;
|
||||
}
|
||||
case MENU_EXPORT_LOG -> {
|
||||
exportToSD();
|
||||
return true;
|
||||
}
|
||||
case MENU_REFRESH -> {
|
||||
populateData(this);
|
||||
return true;
|
||||
}
|
||||
case MENU_ZOOM_IN -> {
|
||||
if (useModernLayout) {
|
||||
updateModernTextSize(2.0f);
|
||||
} else {
|
||||
newSize = scaleGesture.getTextSize() + 2.0f;
|
||||
scaleGesture.setTextSize(TypedValue.COMPLEX_UNIT_PX, newSize);
|
||||
G.ruleTextSize((int) newSize);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
case MENU_ZOOM_OUT -> {
|
||||
if (useModernLayout) {
|
||||
updateModernTextSize(-2.0f);
|
||||
} else {
|
||||
newSize = scaleGesture.getTextSize() - 2.0f;
|
||||
scaleGesture.setTextSize(TypedValue.COMPLEX_UNIT_PX, newSize);
|
||||
G.ruleTextSize((int) newSize);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
default -> {
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static class Task implements Runnable {
|
||||
public String filename = "";
|
||||
private final Context ctx;
|
||||
private final WeakReference<DataDumpActivity> activityReference;
|
||||
private final Handler handler = new Handler(Looper.getMainLooper());
|
||||
|
||||
// only retain a weak reference to the activity
|
||||
Task(DataDumpActivity context) {
|
||||
this.ctx = context;
|
||||
activityReference = new WeakReference<>(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
FileOutputStream output = null;
|
||||
boolean res = false;
|
||||
|
||||
try {
|
||||
File file;
|
||||
if(Build.VERSION.SDK_INT < Build.VERSION_CODES.Q ){
|
||||
File dir = new File(Environment.getExternalStorageDirectory().getAbsolutePath() + "/" );
|
||||
dir.mkdirs();
|
||||
file = new File(dir, sdDumpFile);
|
||||
} else{
|
||||
file = new File(ctx.getExternalFilesDir(null) + "/" + sdDumpFile) ;
|
||||
}
|
||||
output = new FileOutputStream(file);
|
||||
output.write(dataText.getBytes());
|
||||
filename = file.getAbsolutePath();
|
||||
res = true;
|
||||
} catch (IOException e) {
|
||||
Log.e(TAG,e.getMessage(),e);
|
||||
} finally {
|
||||
try {
|
||||
if (output != null) {
|
||||
output.flush();
|
||||
output.close();
|
||||
}
|
||||
} catch (IOException ex) {
|
||||
Log.e(TAG,ex.getMessage(),ex);
|
||||
}
|
||||
}
|
||||
|
||||
final boolean result = res;
|
||||
handler.post(() -> {
|
||||
DataDumpActivity activity = activityReference.get();
|
||||
if (activity == null || activity.isFinishing()) return;
|
||||
|
||||
if (result) {
|
||||
Api.toast(ctx, ctx.getString(R.string.export_rules_success) + filename, Toast.LENGTH_LONG);
|
||||
} else {
|
||||
Api.toast(ctx, ctx.getString(R.string.export_logs_fail), Toast.LENGTH_LONG);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void exportToSD() {
|
||||
|
||||
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q ){
|
||||
// Do some stuff
|
||||
ExecutorService executor = Executors.newSingleThreadExecutor();
|
||||
executor.execute(new Task(this));
|
||||
} else {
|
||||
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)
|
||||
!= PackageManager.PERMISSION_GRANTED) {
|
||||
// permissions have not been granted.
|
||||
ActivityCompat.requestPermissions(DataDumpActivity.this,
|
||||
new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},
|
||||
MY_PERMISSIONS_REQUEST_WRITE_STORAGE);
|
||||
} else{
|
||||
new Task(this).run();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void copy() {
|
||||
try {
|
||||
TextView rulesText = findViewById(R.id.rules);
|
||||
android.content.ClipboardManager clipboard = (android.content.ClipboardManager) getSystemService(CLIPBOARD_SERVICE);
|
||||
android.content.ClipData clip = android.content.ClipData
|
||||
.newPlainText("", rulesText.getText().toString());
|
||||
clipboard.setPrimaryClip(clip);
|
||||
Api.toast(this, this.getString(R.string.copied));
|
||||
} catch (Exception e) {
|
||||
Log.d("AFWall+", "Exception in Clipboard" + e);
|
||||
}
|
||||
Api.toast(this, this.getString(R.string.copied));
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,72 @@
|
|||
package dev.ukanth.ufirewall.activity;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.view.MenuItem;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
import dev.ukanth.ufirewall.BuildConfig;
|
||||
import dev.ukanth.ufirewall.R;
|
||||
import dev.ukanth.ufirewall.util.G;
|
||||
|
||||
public class HelpActivity extends AppCompatActivity {
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
initTheme();
|
||||
|
||||
setContentView(R.layout.help_about);
|
||||
|
||||
Toolbar toolbar = findViewById(R.id.help_toolbar);
|
||||
setSupportActionBar(toolbar);
|
||||
|
||||
if (getSupportActionBar() != null) {
|
||||
getSupportActionBar().setTitle(R.string.help);
|
||||
getSupportActionBar().setHomeButtonEnabled(true);
|
||||
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
||||
}
|
||||
|
||||
setupContent();
|
||||
}
|
||||
|
||||
private void setupContent() {
|
||||
// Setup app title with version
|
||||
String version = BuildConfig.VERSION_NAME;
|
||||
TextView titleText = findViewById(R.id.afwall_title);
|
||||
String versionText = getString(R.string.app_name) + " (v" + version + ")";
|
||||
if(G.isDoKey(this) || BuildConfig.APPLICATION_ID.equals("dev.ukanth.ufirewall.donate")) {
|
||||
versionText = versionText + " (Donate) " + getString(R.string.donate_thanks) + " :)";
|
||||
}
|
||||
titleText.setText(versionText);
|
||||
}
|
||||
|
||||
|
||||
|
||||
private void initTheme() {
|
||||
switch(G.getSelectedTheme()) {
|
||||
case "D":
|
||||
setTheme(R.style.AppDarkTheme);
|
||||
break;
|
||||
case "L":
|
||||
setTheme(R.style.AppLightTheme);
|
||||
break;
|
||||
case "B":
|
||||
setTheme(R.style.AppBlackTheme);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
case android.R.id.home:
|
||||
finish();
|
||||
return true;
|
||||
default:
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
389
app/src/main/java/dev/ukanth/ufirewall/activity/LogActivity.java
Normal file
389
app/src/main/java/dev/ukanth/ufirewall/activity/LogActivity.java
Normal file
|
|
@ -0,0 +1,389 @@
|
|||
/**
|
||||
* Display/purge logs and toggle logging
|
||||
* <p/>
|
||||
* Copyright (C) 2011-2013 Kevin Cernekee
|
||||
* <p/>
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
* <p/>
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
* <p/>
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* @author Kevin Cernekee
|
||||
* @version 1.0
|
||||
*/
|
||||
|
||||
package dev.ukanth.ufirewall.activity;
|
||||
|
||||
import static dev.ukanth.ufirewall.util.G.isDonate;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Bundle;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.SubMenu;
|
||||
import android.view.View;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
|
||||
|
||||
import com.afollestad.materialdialogs.DialogAction;
|
||||
import com.afollestad.materialdialogs.MaterialDialog;
|
||||
import com.raizlabs.android.dbflow.config.FlowManager;
|
||||
import com.raizlabs.android.dbflow.sql.language.SQLite;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import dev.ukanth.ufirewall.Api;
|
||||
import dev.ukanth.ufirewall.R;
|
||||
import dev.ukanth.ufirewall.log.Log;
|
||||
import dev.ukanth.ufirewall.log.LogData;
|
||||
import dev.ukanth.ufirewall.log.LogData_Table;
|
||||
import dev.ukanth.ufirewall.log.LogDatabase;
|
||||
import dev.ukanth.ufirewall.log.LogRecyclerViewAdapter;
|
||||
import dev.ukanth.ufirewall.util.DateComparator;
|
||||
import dev.ukanth.ufirewall.util.G;
|
||||
import dev.ukanth.ufirewall.util.SecurityUtil;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
public class LogActivity extends AppCompatActivity implements SwipeRefreshLayout.OnRefreshListener {
|
||||
|
||||
private RecyclerView recyclerView;
|
||||
private LogRecyclerViewAdapter recyclerViewAdapter;
|
||||
private TextView emptyView;
|
||||
private SwipeRefreshLayout mSwipeLayout;
|
||||
protected Menu mainMenu;
|
||||
|
||||
protected static final int MENU_TOGGLE = -4;
|
||||
protected static final int MENU_CLEAR = 40;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
initTheme();
|
||||
setContentView(R.layout.log_view);
|
||||
|
||||
Toolbar toolbar = findViewById(R.id.rule_toolbar);
|
||||
setTitle(getString(R.string.showlog_title));
|
||||
toolbar.setNavigationOnClickListener(v -> finish());
|
||||
|
||||
setSupportActionBar(toolbar);
|
||||
|
||||
// Load partially transparent black background
|
||||
if (getSupportActionBar() != null) {
|
||||
getSupportActionBar().setHomeButtonEnabled(true);
|
||||
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
||||
}
|
||||
|
||||
Bundle bundle = getIntent().getExtras();
|
||||
if(bundle != null) {
|
||||
Object data = bundle.get("validate");
|
||||
if(data != null){
|
||||
String check = (String) data;
|
||||
if(check.equals("yes")) {
|
||||
new SecurityUtil(LogActivity.this).passCheck();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mSwipeLayout = findViewById(R.id.swipeContainer);
|
||||
mSwipeLayout.setOnRefreshListener(this);
|
||||
|
||||
recyclerView = findViewById(R.id.recyclerview);
|
||||
emptyView = findViewById(R.id.empty_view);
|
||||
|
||||
initializeRecyclerView(getApplicationContext());
|
||||
|
||||
if(G.enableLogService()) {
|
||||
CollectLog collectLog = new CollectLog().setContext(this);
|
||||
collectLog.execute();
|
||||
|
||||
} else {
|
||||
recyclerView.setVisibility(View.GONE);
|
||||
mSwipeLayout.setVisibility(View.GONE);
|
||||
emptyView.setVisibility(View.VISIBLE);
|
||||
}
|
||||
}
|
||||
|
||||
private void initTheme() {
|
||||
switch(G.getSelectedTheme()) {
|
||||
case "D":
|
||||
setTheme(R.style.AppDarkTheme);
|
||||
break;
|
||||
case "L":
|
||||
setTheme(R.style.AppLightTheme);
|
||||
break;
|
||||
case "B":
|
||||
setTheme(R.style.AppBlackTheme);
|
||||
break;
|
||||
}
|
||||
}
|
||||
private void initializeRecyclerView(final Context ctx) {
|
||||
recyclerView.setHasFixedSize(true);
|
||||
recyclerView.setLayoutManager(new LinearLayoutManager(getApplicationContext()));
|
||||
recyclerViewAdapter = new LogRecyclerViewAdapter(getApplicationContext(), logData -> {
|
||||
if(G.isDoKey(ctx) || isDonate()) {
|
||||
Intent intent = new Intent(ctx, LogDetailActivity.class);
|
||||
intent.putExtra("DATA",logData.getUid());
|
||||
startActivity(intent);
|
||||
} else {
|
||||
Api.donateDialog(LogActivity.this,false);
|
||||
}
|
||||
});
|
||||
recyclerView.setAdapter(recyclerViewAdapter);
|
||||
}
|
||||
|
||||
private List<LogData> getLogData() {
|
||||
//load 3 day data
|
||||
long loadInterval = System.currentTimeMillis() - 259200000;
|
||||
|
||||
List<LogData> logData = SQLite.select()
|
||||
.from(LogData.class)
|
||||
.where(LogData_Table.timestamp.greaterThan(loadInterval))
|
||||
.orderBy(LogData_Table.timestamp,true)
|
||||
.queryList();
|
||||
//auto purge old data - > week old data
|
||||
Api.purgeOldLog();
|
||||
return logData;
|
||||
}
|
||||
|
||||
private int getCount() {
|
||||
long l = SQLite.selectCountOf().from(LogData.class).count();
|
||||
return (int) l;
|
||||
}
|
||||
private class CollectLog implements Runnable {
|
||||
private Context context = null;
|
||||
MaterialDialog loadDialog = null;
|
||||
|
||||
public CollectLog() {
|
||||
}
|
||||
|
||||
public CollectLog setContext(Context context) {
|
||||
this.context = context;
|
||||
return this;
|
||||
}
|
||||
|
||||
public void execute() {
|
||||
Handler handler = new Handler(Looper.getMainLooper());
|
||||
handler.post(() -> {
|
||||
onPreExecute();
|
||||
ExecutorService executor = Executors.newSingleThreadExecutor();
|
||||
executor.execute(this);
|
||||
});
|
||||
}
|
||||
|
||||
protected void onPreExecute() {
|
||||
loadDialog = new MaterialDialog.Builder(context).cancelable(false)
|
||||
.title(getString(R.string.working))
|
||||
.cancelable(false)
|
||||
.content(getString(R.string.loading_data))
|
||||
.progress(true, 0).show();
|
||||
}
|
||||
|
||||
protected Boolean doInBackground() {
|
||||
List<LogData> logData = getLogData();
|
||||
try {
|
||||
if (logData != null && logData.size() > 0) {
|
||||
logData = updateMap(logData, this);
|
||||
Collections.sort(logData, new DateComparator());
|
||||
recyclerViewAdapter.updateData(logData);
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.e(Api.TAG, "Exception while retrieving data" + e.getLocalizedMessage());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
protected void onPostExecute(Boolean logPresent) {
|
||||
try {
|
||||
if ((loadDialog != null) && loadDialog.isShowing()) {
|
||||
loadDialog.dismiss();
|
||||
}
|
||||
} catch (IllegalArgumentException e) {
|
||||
// Handle or log or ignore
|
||||
} catch (final Exception e) {
|
||||
// Handle or log or ignore
|
||||
} finally {
|
||||
loadDialog = null;
|
||||
}
|
||||
recyclerView.setAdapter(null);
|
||||
recyclerView.setAdapter(recyclerViewAdapter);
|
||||
|
||||
mSwipeLayout.setRefreshing(false);
|
||||
|
||||
if (logPresent != null && logPresent) {
|
||||
recyclerViewAdapter.notifyDataSetChanged();
|
||||
recyclerView.setVisibility(View.VISIBLE);
|
||||
mSwipeLayout.setVisibility(View.VISIBLE);
|
||||
emptyView.setVisibility(View.GONE);
|
||||
} else {
|
||||
mSwipeLayout.setVisibility(View.GONE);
|
||||
recyclerView.setVisibility(View.GONE);
|
||||
emptyView.setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
||||
recyclerView.getRecycledViewPool().clear();
|
||||
recyclerView.setRecycledViewPool(new RecyclerView.RecycledViewPool());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
Boolean result = doInBackground();
|
||||
Handler handler = new Handler(Looper.getMainLooper());
|
||||
handler.post(() -> onPostExecute(result));
|
||||
}
|
||||
}
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(android.view.Menu menu) {
|
||||
// Common options: Copy, Export to SD Card, Refresh
|
||||
SubMenu sub = menu.addSubMenu(0, MENU_TOGGLE, 0, "").setIcon(R.drawable.ic_flow);
|
||||
sub.add(0, MENU_CLEAR, 0, R.string.clear_log).setIcon(R.drawable.ic_clearlog);
|
||||
//populateMenu(sub);
|
||||
sub.getItem().setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS| MenuItem.SHOW_AS_ACTION_WITH_TEXT);
|
||||
super.onCreateOptionsMenu(menu);
|
||||
mainMenu = menu;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
static <T> List<List<T>> split(List<T> list, final int L) {
|
||||
List<List<T>> parts = new ArrayList<List<T>>();
|
||||
final int N = list.size();
|
||||
for (int i = 0; i < N; i += L) {
|
||||
parts.add(new ArrayList<T>(
|
||||
list.subList(i, Math.min(N, i + L)))
|
||||
);
|
||||
}
|
||||
return parts;
|
||||
}
|
||||
|
||||
|
||||
private List<LogData> updateMap(final List<LogData> logDataList, CollectLog collectLog) {
|
||||
final HashMap<Integer, LogData> logMap = new HashMap<>();
|
||||
final HashMap<Integer, Integer> count = new HashMap<>();
|
||||
final HashMap<Integer, Long> lastBlocked = new HashMap<>();
|
||||
List<LogData> analyticsList = new ArrayList();
|
||||
|
||||
//int counter = 0;
|
||||
final int size = logDataList.size();
|
||||
//List<List<LogData>> parts = split(logDataList, 10);
|
||||
/*for(List listLog: parts) {
|
||||
Thread t = new Thread() {*/
|
||||
LogData tmpData,data;
|
||||
// public void run() {
|
||||
for (int i=0; i<size; i++) {
|
||||
//collectLog.doProgress(counter++);
|
||||
tmpData = logDataList.get(i);
|
||||
data = logDataList.get(i);
|
||||
if (logMap.containsKey(data.getUid())) {
|
||||
if (data.getTimestamp() > lastBlocked.get(data.getUid())) {
|
||||
lastBlocked.put(data.getUid(), data.getTimestamp());
|
||||
tmpData.setTimestamp(data.getTimestamp());
|
||||
} else {
|
||||
tmpData.setTimestamp(lastBlocked.get(data.getUid()));
|
||||
}
|
||||
//data already Present. Update the template here
|
||||
count.put(data.getUid(), count.get(data.getUid()).intValue() + 1);
|
||||
tmpData.setCount(count.get(data.getUid()).intValue());
|
||||
} else {
|
||||
//process template here
|
||||
count.put(data.getUid(), 1);
|
||||
tmpData.setCount(1);
|
||||
lastBlocked.put(data.getUid(), data.getTimestamp());
|
||||
}
|
||||
logMap.put(data.getUid(), tmpData);
|
||||
}
|
||||
//}
|
||||
//};
|
||||
//t.start();
|
||||
//}
|
||||
|
||||
for (Map.Entry<Integer, LogData> entry : logMap.entrySet()) {
|
||||
analyticsList.add(entry.getValue());
|
||||
}
|
||||
return analyticsList;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
case android.R.id.home: {
|
||||
onBackPressed();
|
||||
return true;
|
||||
}
|
||||
case MENU_CLEAR:
|
||||
clearDatabase(LogActivity.this);
|
||||
return true;
|
||||
/*case MENU_EXPORT_LOG:
|
||||
//exportToSD();
|
||||
return true;*/
|
||||
default:
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
}
|
||||
|
||||
private void clearDatabase(final Context ctx) {
|
||||
new MaterialDialog.Builder(this)
|
||||
.title(getApplicationContext().getString(R.string.clear_log) + " ?")
|
||||
.cancelable(true)
|
||||
.onPositive(new MaterialDialog.SingleButtonCallback() {
|
||||
@Override
|
||||
public void onClick(@NonNull MaterialDialog dialog, @NonNull DialogAction which) {
|
||||
//SQLite.delete(LogData_Table.class);
|
||||
FlowManager.getDatabase(LogDatabase.NAME).reset();
|
||||
Toast.makeText(ctx, ctx.getString(R.string.log_cleared), Toast.LENGTH_SHORT).show();
|
||||
dialog.dismiss();
|
||||
(new CollectLog()).setContext(LogActivity.this).execute();
|
||||
}
|
||||
})
|
||||
.onNegative(new MaterialDialog.SingleButtonCallback() {
|
||||
@Override
|
||||
public void onClick(@NonNull MaterialDialog dialog, @NonNull DialogAction which) {
|
||||
dialog.dismiss();
|
||||
}
|
||||
})
|
||||
.positiveText(R.string.Yes)
|
||||
.negativeText(R.string.No)
|
||||
.show();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void onRefresh() {
|
||||
(new CollectLog()).setContext(this).run();
|
||||
}
|
||||
|
||||
/*@Override
|
||||
public boolean onPrepareOptionsMenu(Menu menu) {
|
||||
// setupLogMenuItem(menu, G.enableFirewallLog());
|
||||
return super.onPrepareOptionsMenu(menu);
|
||||
}*/
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,752 @@
|
|||
/**
|
||||
* Display/purge logs and toggle logging
|
||||
* <p/>
|
||||
* Copyright (C) 2011-2013 Kevin Cernekee
|
||||
* <p/>
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
* <p/>
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
* <p/>
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* @author Kevin Cernekee
|
||||
* @version 1.0
|
||||
*/
|
||||
|
||||
package dev.ukanth.ufirewall.activity;
|
||||
|
||||
import android.Manifest;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.Environment;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.SubMenu;
|
||||
import android.view.View;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
import androidx.core.app.ActivityCompat;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
|
||||
|
||||
import com.afollestad.materialdialogs.MaterialDialog;
|
||||
import com.raizlabs.android.dbflow.sql.language.SQLite;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import dev.ukanth.ufirewall.Api;
|
||||
import dev.ukanth.ufirewall.R;
|
||||
import dev.ukanth.ufirewall.log.Log;
|
||||
import dev.ukanth.ufirewall.log.LogData;
|
||||
import dev.ukanth.ufirewall.log.LogData_Table;
|
||||
import dev.ukanth.ufirewall.log.LogDetailRecyclerViewAdapter;
|
||||
import dev.ukanth.ufirewall.log.LogPreference;
|
||||
import dev.ukanth.ufirewall.log.LogPreference_Table;
|
||||
import dev.ukanth.ufirewall.util.DateComparator;
|
||||
import dev.ukanth.ufirewall.util.G;
|
||||
import dev.ukanth.ufirewall.util.LogNetUtil;
|
||||
|
||||
public class LogDetailActivity extends AppCompatActivity implements SwipeRefreshLayout.OnRefreshListener {
|
||||
|
||||
private static final int MY_PERMISSIONS_REQUEST_WRITE_STORAGE = 1;
|
||||
|
||||
private RecyclerView recyclerView;
|
||||
protected static String logDumpFile = "log_dump.log";
|
||||
|
||||
private LogDetailRecyclerViewAdapter recyclerViewAdapter;
|
||||
private TextView emptyView;
|
||||
private SwipeRefreshLayout mSwipeLayout;
|
||||
protected Menu mainMenu;
|
||||
private LogData current_selected_logData;
|
||||
private static List<LogData> logDataList;
|
||||
private static List<LogData> fullLogDataList; // Store full dataset
|
||||
|
||||
// Pagination
|
||||
private static final int PAGE_SIZE = 100; // Load 100 items at a time
|
||||
private int currentPage = 0;
|
||||
private boolean isLoading = false;
|
||||
|
||||
// Summary views
|
||||
private TextView totalBlocks;
|
||||
private TextView uniqueDestinations;
|
||||
private TextView timePeriod;
|
||||
private TextView mostBlockedDestination;
|
||||
private TextView loadingMoreIndicator;
|
||||
|
||||
protected static final int MENU_EXPORT_LOG = 100;
|
||||
|
||||
private static int uid;
|
||||
protected final int MENU_TOGGLE = -4;
|
||||
protected final int MENU_CLEAR = 40;
|
||||
|
||||
final String TAG = "AFWall";
|
||||
|
||||
private void initTheme() {
|
||||
switch (G.getSelectedTheme()) {
|
||||
case "D":
|
||||
setTheme(R.style.AppDarkTheme);
|
||||
break;
|
||||
case "L":
|
||||
setTheme(R.style.AppLightTheme);
|
||||
break;
|
||||
case "B":
|
||||
setTheme(R.style.AppBlackTheme);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
initTheme();
|
||||
setContentView(R.layout.logdetail_view);
|
||||
Toolbar toolbar = findViewById(R.id.rule_toolbar);
|
||||
setTitle(getString(R.string.showlogdetail_title));
|
||||
toolbar.setNavigationOnClickListener(v -> finish());
|
||||
|
||||
setSupportActionBar(toolbar);
|
||||
|
||||
Intent intent = getIntent();
|
||||
uid = intent.getIntExtra("DATA", -1);
|
||||
|
||||
// Load partially transparent black background
|
||||
getSupportActionBar().setHomeButtonEnabled(true);
|
||||
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
||||
|
||||
mSwipeLayout = findViewById(R.id.swipedetailContainer);
|
||||
mSwipeLayout.setOnRefreshListener(this);
|
||||
|
||||
recyclerView = findViewById(R.id.detailrecyclerview);
|
||||
emptyView = findViewById(R.id.emptydetail_view);
|
||||
|
||||
// Initialize summary views
|
||||
totalBlocks = findViewById(R.id.total_blocks);
|
||||
uniqueDestinations = findViewById(R.id.unique_destinations);
|
||||
timePeriod = findViewById(R.id.time_period);
|
||||
mostBlockedDestination = findViewById(R.id.most_blocked_destination);
|
||||
loadingMoreIndicator = findViewById(R.id.loading_more_indicator);
|
||||
|
||||
initializeRecyclerView(getApplicationContext());
|
||||
|
||||
(new CollectDetailLog()).setContext(this).execute();
|
||||
}
|
||||
|
||||
private void initializeRecyclerView(final Context ctx) {
|
||||
recyclerView.hasFixedSize();
|
||||
recyclerView.setLayoutManager(new LinearLayoutManager(getApplicationContext()));
|
||||
recyclerViewAdapter = new LogDetailRecyclerViewAdapter(getApplicationContext(), logData -> {
|
||||
current_selected_logData = logData;
|
||||
recyclerView.showContextMenu();
|
||||
});
|
||||
recyclerView.setOnCreateContextMenuListener((menu, v, menuInfo) -> {
|
||||
menu.setHeaderTitle(R.string.select_the_action);
|
||||
//groupId, itemId, order, title
|
||||
//menu.add(0, v.getId(), 0, R.string.add_ip_rule);
|
||||
menu.add(0, v.getId(), 1, R.string.show_destination_address);
|
||||
menu.add(0, v.getId(), 2, R.string.show_source_address);
|
||||
menu.add(0, v.getId(), 3, R.string.ping_destination);
|
||||
menu.add(0, v.getId(), 4, R.string.ping_source);
|
||||
menu.add(0, v.getId(), 5, R.string.resolve_destination);
|
||||
menu.add(0, v.getId(), 6, R.string.resolve_source);
|
||||
menu.add(0, v.getId(), 9, "Block this destination permanently");
|
||||
menu.add(0, v.getId(), 10, "Whitelist this destination");
|
||||
LogPreference logPreference = SQLite.select()
|
||||
.from(LogPreference.class)
|
||||
.where(LogPreference_Table.uid.eq(uid)).querySingle();
|
||||
|
||||
if (logPreference != null) {
|
||||
if(logPreference.isDisable()) {
|
||||
menu.add(0, v.getId(), 7, R.string.displayBlockNotification_enable);
|
||||
} else {
|
||||
menu.add(0, v.getId(), 8, R.string.displayBlockNotification);
|
||||
}
|
||||
} else {
|
||||
menu.add(0, v.getId(), 8, R.string.displayBlockNotification);
|
||||
}
|
||||
|
||||
|
||||
});
|
||||
recyclerView.setAdapter(recyclerViewAdapter);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onContextItemSelected(MenuItem item) {
|
||||
|
||||
switch (item.getOrder()) {
|
||||
|
||||
case 0: // Destination to clipboard
|
||||
String[] items = {current_selected_logData.getDst(), current_selected_logData.getSrc()};
|
||||
new MaterialDialog.Builder(this)
|
||||
.items(items)
|
||||
.itemsCallbackSingleChoice(-1, (dialog, view, which, text) -> true)
|
||||
.positiveText(R.string.choose)
|
||||
.show();
|
||||
break;
|
||||
case 1: // Destination to clipboard
|
||||
|
||||
new MaterialDialog.Builder(this)
|
||||
.content(current_selected_logData.getDst() + ":" + current_selected_logData.getDpt())
|
||||
.title(R.string.destination_address)
|
||||
.neutralText(R.string.OK)
|
||||
.positiveText(R.string.copy_text)
|
||||
.onPositive((dialog, which) -> {
|
||||
Api.copyToClipboard(LogDetailActivity.this, current_selected_logData.getDst() + ":" + current_selected_logData.getDpt());
|
||||
Api.toast(LogDetailActivity.this, getString(R.string.destination_copied));
|
||||
})
|
||||
.show();
|
||||
|
||||
break;
|
||||
|
||||
case 2: // Source to clipboard
|
||||
new MaterialDialog.Builder(this)
|
||||
.content(current_selected_logData.getSrc() + ":" + current_selected_logData.getSpt())
|
||||
.title(R.string.source_address)
|
||||
.neutralText(R.string.OK)
|
||||
.positiveText(R.string.copy_text)
|
||||
.onPositive((dialog, which) -> {
|
||||
Api.copyToClipboard(LogDetailActivity.this, current_selected_logData.getSrc() + ":" + current_selected_logData.getSpt());
|
||||
Api.toast(LogDetailActivity.this, getString(R.string.source_copied));
|
||||
})
|
||||
.show();
|
||||
break;
|
||||
|
||||
case 3: // Ping Destination
|
||||
new LogNetUtil.NetTask(this).execute(
|
||||
new LogNetUtil.NetParam(LogNetUtil.JobType.PING, current_selected_logData.getDst())
|
||||
);
|
||||
|
||||
break;
|
||||
|
||||
case 4: // Ping Source
|
||||
new LogNetUtil.NetTask(this).execute(
|
||||
new LogNetUtil.NetParam(LogNetUtil.JobType.PING, current_selected_logData.getSrc())
|
||||
);
|
||||
break;
|
||||
|
||||
case 5: // Resolve Destination
|
||||
new LogNetUtil.NetTask(this).execute(
|
||||
new LogNetUtil.NetParam(LogNetUtil.JobType.RESOLVE, current_selected_logData.getDst())
|
||||
);
|
||||
break;
|
||||
|
||||
case 6: // Resolve Source
|
||||
new LogNetUtil.NetTask(this).execute(
|
||||
new LogNetUtil.NetParam(LogNetUtil.JobType.RESOLVE, current_selected_logData.getSrc())
|
||||
);
|
||||
break;
|
||||
case 7:
|
||||
G.updateLogNotification(uid, false);
|
||||
break;
|
||||
case 8:
|
||||
G.updateLogNotification(uid, true);
|
||||
break;
|
||||
case 9: // Block destination permanently
|
||||
showBlockDestinationDialog();
|
||||
break;
|
||||
case 10: // Whitelist destination
|
||||
showWhitelistDestinationDialog();
|
||||
break;
|
||||
|
||||
}
|
||||
return super.onContextItemSelected(item);
|
||||
}
|
||||
|
||||
|
||||
private List<LogData> getLogData(final int uid) {
|
||||
return SQLite.select()
|
||||
.from(LogData.class)
|
||||
.where(LogData_Table.uid.eq(uid))
|
||||
.orderBy(LogData_Table.timestamp, false)
|
||||
.queryList();
|
||||
}
|
||||
|
||||
private List<LogData> getPagedLogData(final int uid, int page, int pageSize) {
|
||||
int offset = page * pageSize;
|
||||
return SQLite.select()
|
||||
.from(LogData.class)
|
||||
.where(LogData_Table.uid.eq(uid))
|
||||
.orderBy(LogData_Table.timestamp, false)
|
||||
.limit(pageSize)
|
||||
.offset(offset)
|
||||
.queryList();
|
||||
}
|
||||
|
||||
private int getCount() {
|
||||
long l = SQLite.selectCountOf().from(LogData.class).where(LogData_Table.uid.eq(uid)).count();
|
||||
return (int) l;
|
||||
}
|
||||
|
||||
|
||||
private class CollectDetailLog extends AsyncTask<Void, Integer, Boolean> {
|
||||
private Context context = null;
|
||||
MaterialDialog loadDialog = null;
|
||||
|
||||
public CollectDetailLog() {
|
||||
}
|
||||
//private boolean suAvailable = false;
|
||||
|
||||
public CollectDetailLog setContext(Context context) {
|
||||
this.context = context;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPreExecute() {
|
||||
loadDialog = new MaterialDialog.Builder(context).cancelable(false).
|
||||
title(getString(R.string.loading_data)).progress(false, getCount(), true).show();
|
||||
doProgress(0);
|
||||
}
|
||||
|
||||
public void doProgress(int value) {
|
||||
publishProgress(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Boolean doInBackground(Void... params) {
|
||||
try {
|
||||
// First, get total count for statistics
|
||||
int totalCount = getCount();
|
||||
publishProgress(totalCount);
|
||||
|
||||
if (totalCount > PAGE_SIZE) {
|
||||
// Large dataset - use pagination
|
||||
fullLogDataList = getLogData(uid); // Get full list for statistics
|
||||
logDataList = getPagedLogData(uid, 0, PAGE_SIZE); // Get first page
|
||||
currentPage = 0;
|
||||
} else {
|
||||
// Small dataset - load all
|
||||
logDataList = getLogData(uid);
|
||||
fullLogDataList = logDataList;
|
||||
}
|
||||
|
||||
if (logDataList != null && logDataList.size() > 0) {
|
||||
Collections.sort(logDataList, new DateComparator());
|
||||
recyclerViewAdapter.updateData(logDataList);
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.e(Api.TAG, "Exception while retrieving data" + e.getLocalizedMessage());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onProgressUpdate(Integer... progress) {
|
||||
|
||||
if (progress[0] == 0 || progress[0] == -1) {
|
||||
//do nothing
|
||||
} else {
|
||||
loadDialog.incrementProgress(progress[0]);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(Boolean logPresent) {
|
||||
super.onPostExecute(logPresent);
|
||||
doProgress(-1);
|
||||
try {
|
||||
if ((loadDialog != null) && loadDialog.isShowing()) {
|
||||
loadDialog.dismiss();
|
||||
}
|
||||
} catch (IllegalArgumentException e) {
|
||||
// Handle or log or ignore
|
||||
} catch (final Exception e) {
|
||||
// Handle or log or ignore
|
||||
} finally {
|
||||
loadDialog = null;
|
||||
}
|
||||
|
||||
mSwipeLayout.setRefreshing(false);
|
||||
|
||||
if (logPresent != null && logPresent) {
|
||||
recyclerView.setVisibility(View.VISIBLE);
|
||||
mSwipeLayout.setVisibility(View.VISIBLE);
|
||||
emptyView.setVisibility(View.GONE);
|
||||
recyclerViewAdapter.notifyDataSetChanged();
|
||||
|
||||
// Update title with log count
|
||||
updateTitleWithLogCount();
|
||||
|
||||
// Update summary statistics using full dataset
|
||||
updateSummaryStatistics();
|
||||
|
||||
// Setup load more functionality for large datasets
|
||||
if (fullLogDataList != null && fullLogDataList.size() > PAGE_SIZE) {
|
||||
setupLoadMoreFunctionality();
|
||||
}
|
||||
} else {
|
||||
mSwipeLayout.setVisibility(View.GONE);
|
||||
recyclerView.setVisibility(View.GONE);
|
||||
emptyView.setVisibility(View.VISIBLE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
// Common options: Copy, Export to SD Card, Refresh
|
||||
SubMenu sub = menu.addSubMenu(0, MENU_TOGGLE, 0, "").setIcon(R.drawable.ic_flow);
|
||||
sub.add(0, MENU_CLEAR, 0, R.string.clear_log).setIcon(R.drawable.ic_clearlog);
|
||||
//sub.add(0, MENU, 0, R.string.clear_log).setIcon(R.drawable.ic_clearlog);
|
||||
//sub.add(0, MENU_EXPORT_LOG, 0, R.string.export_to_sd).setIcon(R.drawable.exportr);
|
||||
//populateMenu(sub);
|
||||
sub.add(1, MENU_EXPORT_LOG, 0, R.string.export_to_sd).setIcon(R.drawable.ic_export);
|
||||
sub.getItem().setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS | MenuItem.SHOW_AS_ACTION_WITH_TEXT);
|
||||
super.onCreateOptionsMenu(menu);
|
||||
mainMenu = menu;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
case android.R.id.home: {
|
||||
onBackPressed();
|
||||
return true;
|
||||
}
|
||||
case MENU_CLEAR:
|
||||
clearDatabase(getApplicationContext());
|
||||
return true;
|
||||
case MENU_EXPORT_LOG:
|
||||
exportToSD();
|
||||
return true;
|
||||
default:
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
}
|
||||
|
||||
private void clearDatabase(final Context ctx) {
|
||||
new MaterialDialog.Builder(this)
|
||||
.title(getApplicationContext().getString(R.string.clear_log) + " ?")
|
||||
.cancelable(true)
|
||||
.onPositive((dialog, which) -> {
|
||||
//SQLite.delete(LogData_Table.class);
|
||||
// FlowManager.getDatabase(LogDatabase.NAME).reset();
|
||||
SQLite.delete(LogData.class)
|
||||
.where(LogData_Table.uid.eq(uid))
|
||||
.async()
|
||||
.execute();
|
||||
Toast.makeText(getApplicationContext(), ctx.getString(R.string.log_cleared), Toast.LENGTH_SHORT).show();
|
||||
dialog.dismiss();
|
||||
})
|
||||
.onNegative((dialog, which) -> dialog.dismiss())
|
||||
.positiveText(R.string.Yes)
|
||||
.negativeText(R.string.No)
|
||||
.show();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void onRefresh() {
|
||||
(new CollectDetailLog()).setContext(this).execute();
|
||||
}
|
||||
|
||||
private void updateTitleWithLogCount() {
|
||||
if (logDataList != null && logDataList.size() > 0) {
|
||||
String appName = "";
|
||||
if (logDataList.get(0).getAppName() != null) {
|
||||
appName = logDataList.get(0).getAppName();
|
||||
}
|
||||
String title = appName + " (" + logDataList.size() + " blocked)";
|
||||
setTitle(title);
|
||||
}
|
||||
}
|
||||
|
||||
private void showBlockDestinationDialog() {
|
||||
if (current_selected_logData == null) return;
|
||||
|
||||
new MaterialDialog.Builder(this)
|
||||
.title("Block Destination")
|
||||
.content("Add a permanent rule to block all connections to " +
|
||||
current_selected_logData.getDst() + ":" + current_selected_logData.getDpt() + "?")
|
||||
.positiveText("Block")
|
||||
.negativeText("Cancel")
|
||||
.onPositive((dialog, which) -> {
|
||||
// Here you would integrate with AFWall's custom rule system
|
||||
// This is a placeholder for the actual implementation
|
||||
Api.toast(this, "Feature requires integration with custom rules system");
|
||||
})
|
||||
.show();
|
||||
}
|
||||
|
||||
private void showWhitelistDestinationDialog() {
|
||||
if (current_selected_logData == null) return;
|
||||
|
||||
new MaterialDialog.Builder(this)
|
||||
.title("Whitelist Destination")
|
||||
.content("Add a permanent rule to allow all connections to " +
|
||||
current_selected_logData.getDst() + ":" + current_selected_logData.getDpt() + "?")
|
||||
.positiveText("Allow")
|
||||
.negativeText("Cancel")
|
||||
.onPositive((dialog, which) -> {
|
||||
// Here you would integrate with AFWall's custom rule system
|
||||
// This is a placeholder for the actual implementation
|
||||
Api.toast(this, "Feature requires integration with custom rules system");
|
||||
})
|
||||
.show();
|
||||
}
|
||||
|
||||
private void updateSummaryStatistics() {
|
||||
if (fullLogDataList == null || fullLogDataList.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Show basic count immediately for better UX (full dataset count)
|
||||
totalBlocks.setText(String.valueOf(fullLogDataList.size()));
|
||||
|
||||
// Process complex statistics in background thread using full dataset
|
||||
new Thread(() -> {
|
||||
// Calculate unique destinations and most blocked
|
||||
java.util.Map<String, Integer> destinationCounts = new java.util.HashMap<>();
|
||||
java.util.Set<String> uniqueDests = new java.util.HashSet<>();
|
||||
|
||||
long oldestTimestamp = Long.MAX_VALUE;
|
||||
long newestTimestamp = Long.MIN_VALUE;
|
||||
|
||||
for (LogData logData : fullLogDataList) {
|
||||
String destination = logData.getDst() + ":" + logData.getDpt();
|
||||
uniqueDests.add(destination);
|
||||
Integer currentCount = destinationCounts.get(destination);
|
||||
destinationCounts.put(destination, (currentCount == null ? 0 : currentCount) + 1);
|
||||
|
||||
// Track time range
|
||||
oldestTimestamp = Math.min(oldestTimestamp, logData.getTimestamp());
|
||||
newestTimestamp = Math.max(newestTimestamp, logData.getTimestamp());
|
||||
}
|
||||
|
||||
final int uniqueCount = uniqueDests.size();
|
||||
final String period = (oldestTimestamp != Long.MAX_VALUE && newestTimestamp != Long.MIN_VALUE) ?
|
||||
formatTimePeriod(newestTimestamp - oldestTimestamp) : "0s";
|
||||
|
||||
// Find most blocked destination
|
||||
String mostBlocked = "No data";
|
||||
int maxCount = 0;
|
||||
for (java.util.Map.Entry<String, Integer> entry : destinationCounts.entrySet()) {
|
||||
if (entry.getValue() > maxCount) {
|
||||
maxCount = entry.getValue();
|
||||
mostBlocked = entry.getKey() + " (" + maxCount + "x)";
|
||||
}
|
||||
}
|
||||
final String finalMostBlocked = mostBlocked;
|
||||
|
||||
// Update UI on main thread
|
||||
runOnUiThread(() -> {
|
||||
uniqueDestinations.setText(String.valueOf(uniqueCount));
|
||||
timePeriod.setText(period);
|
||||
mostBlockedDestination.setText(finalMostBlocked);
|
||||
});
|
||||
}).start();
|
||||
}
|
||||
|
||||
private void setupLoadMoreFunctionality() {
|
||||
// Add scroll listener to load more data when user reaches bottom
|
||||
recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
|
||||
@Override
|
||||
public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
|
||||
super.onScrolled(recyclerView, dx, dy);
|
||||
|
||||
LinearLayoutManager layoutManager = (LinearLayoutManager) recyclerView.getLayoutManager();
|
||||
if (layoutManager != null && !isLoading) {
|
||||
int visibleItemCount = layoutManager.getChildCount();
|
||||
int totalItemCount = layoutManager.getItemCount();
|
||||
int firstVisibleItem = layoutManager.findFirstVisibleItemPosition();
|
||||
|
||||
// Load more when we're near the bottom
|
||||
if ((visibleItemCount + firstVisibleItem) >= totalItemCount - 10) {
|
||||
loadMoreData();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void loadMoreData() {
|
||||
if (isLoading || fullLogDataList == null) return;
|
||||
|
||||
int totalAvailable = fullLogDataList.size();
|
||||
int currentLoaded = (currentPage + 1) * PAGE_SIZE;
|
||||
|
||||
if (currentLoaded >= totalAvailable) {
|
||||
return; // No more data to load
|
||||
}
|
||||
|
||||
isLoading = true;
|
||||
currentPage++;
|
||||
|
||||
// Show loading indicator
|
||||
loadingMoreIndicator.setVisibility(View.VISIBLE);
|
||||
|
||||
new Thread(() -> {
|
||||
try {
|
||||
List<LogData> newData = getPagedLogData(uid, currentPage, PAGE_SIZE);
|
||||
if (newData != null && !newData.isEmpty()) {
|
||||
Collections.sort(newData, new DateComparator());
|
||||
|
||||
runOnUiThread(() -> {
|
||||
// Add new data to existing list
|
||||
logDataList.addAll(newData);
|
||||
recyclerViewAdapter.notifyItemRangeInserted(
|
||||
logDataList.size() - newData.size(),
|
||||
newData.size()
|
||||
);
|
||||
loadingMoreIndicator.setVisibility(View.GONE);
|
||||
isLoading = false;
|
||||
});
|
||||
} else {
|
||||
runOnUiThread(() -> {
|
||||
loadingMoreIndicator.setVisibility(View.GONE);
|
||||
isLoading = false;
|
||||
});
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.e(Api.TAG, "Error loading more data", e);
|
||||
runOnUiThread(() -> {
|
||||
loadingMoreIndicator.setVisibility(View.GONE);
|
||||
isLoading = false;
|
||||
});
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
|
||||
private String formatTimePeriod(long millis) {
|
||||
long seconds = millis / 1000;
|
||||
long minutes = seconds / 60;
|
||||
long hours = minutes / 60;
|
||||
long days = hours / 24;
|
||||
|
||||
if (days > 0) {
|
||||
return days + "d";
|
||||
} else if (hours > 0) {
|
||||
return hours + "h";
|
||||
} else if (minutes > 0) {
|
||||
return minutes + "m";
|
||||
} else {
|
||||
return seconds + "s";
|
||||
}
|
||||
}
|
||||
|
||||
private static class Task extends AsyncTask<Void, Void, Boolean> {
|
||||
public String filename = "";
|
||||
private final Context ctx;
|
||||
|
||||
private final WeakReference<LogDetailActivity> activityReference;
|
||||
|
||||
// only retain a weak reference to the activity
|
||||
Task(LogDetailActivity context) {
|
||||
this.ctx = context;
|
||||
activityReference = new WeakReference<>(context);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Boolean doInBackground(Void... args) {
|
||||
FileOutputStream output = null;
|
||||
boolean res = false;
|
||||
|
||||
try {
|
||||
File file;
|
||||
if(Build.VERSION.SDK_INT < Build.VERSION_CODES.Q ){
|
||||
File dir = new File(Environment.getExternalStorageDirectory().getAbsolutePath() + "/" );
|
||||
dir.mkdirs();
|
||||
file = new File(dir, logDumpFile);
|
||||
} else{
|
||||
file = new File(ctx.getExternalFilesDir(null) + "/" + logDumpFile) ;
|
||||
}
|
||||
output = new FileOutputStream(file);
|
||||
StringBuilder builder = new StringBuilder();
|
||||
builder.append("uid: " + uid);
|
||||
|
||||
for(LogData data: logDataList) {
|
||||
builder.append("src:").append(data.getSrc()).append(",")
|
||||
.append("dst:").append(data.getDst()).append(",")
|
||||
.append("proto:").append(data.getProto()).append(",")
|
||||
.append("sport:").append(data.getSpt()).append(",")
|
||||
.append("dport:").append(data.getDpt());
|
||||
builder.append("\n");
|
||||
}
|
||||
output.write(builder.toString().getBytes());
|
||||
filename = file.getAbsolutePath();
|
||||
res = true;
|
||||
} catch (FileNotFoundException e) {
|
||||
Log.e(G.TAG,e.getMessage(),e);
|
||||
} catch (IOException e) {
|
||||
Log.e(G.TAG,e.getMessage(),e);
|
||||
} finally {
|
||||
try {
|
||||
if (output != null) {
|
||||
output.flush();
|
||||
output.close();
|
||||
}
|
||||
} catch (IOException ex) {
|
||||
Log.e(G.TAG,ex.getMessage(),ex);
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPostExecute(Boolean res) {
|
||||
LogDetailActivity activity = activityReference.get();
|
||||
if (activity == null || activity.isFinishing()) return;
|
||||
|
||||
if (res) {
|
||||
Api.toast(ctx, ctx.getString(R.string.export_rules_success) + filename, Toast.LENGTH_LONG);
|
||||
} else {
|
||||
Api.toast(ctx, ctx.getString(R.string.export_logs_fail), Toast.LENGTH_LONG);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void exportToSD() {
|
||||
|
||||
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q ){
|
||||
// Do some stuff
|
||||
new Task(this).execute();
|
||||
} else {
|
||||
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)
|
||||
!= PackageManager.PERMISSION_GRANTED) {
|
||||
// permissions have not been granted.
|
||||
ActivityCompat.requestPermissions(LogDetailActivity.this,
|
||||
new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},
|
||||
MY_PERMISSIONS_REQUEST_WRITE_STORAGE);
|
||||
} else{
|
||||
new Task(this).execute();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*@Override
|
||||
public boolean onPrepareOptionsMenu(Menu menu) {
|
||||
// setupLogMenuItem(menu, G.enableFirewallLog());
|
||||
return super.onPrepareOptionsMenu(menu);
|
||||
}*/
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,119 @@
|
|||
/**
|
||||
* Display/purge logs and toggle logging
|
||||
* <p/>
|
||||
* Copyright (C) 2011-2013 Kevin Cernekee
|
||||
* <p/>
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
* <p/>
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
* <p/>
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* @author Kevin Cernekee
|
||||
* @version 1.0
|
||||
*/
|
||||
|
||||
package dev.ukanth.ufirewall.activity;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.view.MenuItem;
|
||||
import android.view.SubMenu;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.afollestad.materialdialogs.MaterialDialog;
|
||||
import com.raizlabs.android.dbflow.config.FlowManager;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import dev.ukanth.ufirewall.Api;
|
||||
import dev.ukanth.ufirewall.R;
|
||||
import dev.ukanth.ufirewall.log.LogData;
|
||||
import dev.ukanth.ufirewall.log.LogDatabase;
|
||||
import dev.ukanth.ufirewall.log.LogInfo;
|
||||
import dev.ukanth.ufirewall.util.G;
|
||||
|
||||
public class OldLogActivity extends DataDumpActivity {
|
||||
|
||||
protected static final int MENU_CLEARLOG = 7;
|
||||
protected static final int MENU_SWITCH_NEW = 70;
|
||||
//protected static final int MENU_TOGGLE_LOG = 27;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setTitle(getString(R.string.showlog_title));
|
||||
getSupportActionBar().setHomeButtonEnabled(true);
|
||||
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
||||
sdDumpFile = "iptables.log";
|
||||
}
|
||||
|
||||
protected void parseAndSet(List<LogData> logDataList) {
|
||||
String cooked = LogInfo.parseLog(OldLogActivity.this, logDataList);
|
||||
if (cooked == null) {
|
||||
setData(getString(R.string.log_parse_error));
|
||||
} else {
|
||||
setData(cooked);
|
||||
}
|
||||
}
|
||||
|
||||
protected void populateData(final Context ctx) {
|
||||
parseAndSet(Api.fetchLogs());
|
||||
}
|
||||
|
||||
protected void populateMenu(SubMenu sub) {
|
||||
sub.add(0, MENU_CLEARLOG, 0, R.string.clear_log).setIcon(R.drawable.ic_clearlog);
|
||||
sub.add(0, MENU_SWITCH_NEW, 0, R.string.switch_new).setIcon(R.drawable.ic_log);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
final Context ctx = this;
|
||||
|
||||
switch (item.getItemId()) {
|
||||
|
||||
case android.R.id.home: {
|
||||
onBackPressed();
|
||||
return true;
|
||||
}
|
||||
case MENU_SWITCH_NEW:
|
||||
Intent i = new Intent(this, LogActivity.class);
|
||||
G.oldLogView(false);
|
||||
startActivity(i);
|
||||
finish();
|
||||
return true;
|
||||
case MENU_CLEARLOG:
|
||||
clearDatabase(OldLogActivity.this);
|
||||
return true;
|
||||
}
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
|
||||
private void clearDatabase(final Context ctx) {
|
||||
new MaterialDialog.Builder(this)
|
||||
.title(getApplicationContext().getString(R.string.clear_log) + " ?")
|
||||
.cancelable(true)
|
||||
.onPositive((dialog, which) -> {
|
||||
//SQLite.delete(LogData_Table.class);
|
||||
FlowManager.getDatabase(LogDatabase.NAME).reset();
|
||||
Toast.makeText(ctx, ctx.getString(R.string.log_cleared), Toast.LENGTH_SHORT).show();
|
||||
dialog.dismiss();
|
||||
parseAndSet(Api.fetchLogs());
|
||||
})
|
||||
.onNegative((dialog, which) -> dialog.dismiss())
|
||||
.positiveText(R.string.Yes)
|
||||
.negativeText(R.string.No)
|
||||
.show();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,276 @@
|
|||
package dev.ukanth.ufirewall.activity;
|
||||
|
||||
import static dev.ukanth.ufirewall.util.G.isDonate;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.Bundle;
|
||||
import android.text.InputType;
|
||||
import android.view.ContextMenu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.ListView;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
|
||||
import com.afollestad.materialdialogs.MaterialDialog;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import dev.ukanth.ufirewall.Api;
|
||||
import dev.ukanth.ufirewall.R;
|
||||
import dev.ukanth.ufirewall.log.Log;
|
||||
import dev.ukanth.ufirewall.profiles.ProfileAdapter;
|
||||
import dev.ukanth.ufirewall.profiles.ProfileData;
|
||||
import dev.ukanth.ufirewall.profiles.ProfileHelper;
|
||||
import dev.ukanth.ufirewall.util.G;
|
||||
|
||||
/**
|
||||
* Created by ukanth on 31/7/15.
|
||||
*/
|
||||
public class ProfileActivity extends AppCompatActivity {
|
||||
List<ProfileData> profilesList = new ArrayList<ProfileData>();
|
||||
ProfileAdapter profileAdapter;
|
||||
|
||||
protected static final int MENU_ADD = 100;
|
||||
//protected static final int MENU_CLONE = 101;
|
||||
protected static final int MENU_DELETE = 102;
|
||||
protected static final int MENU_RENAME = 103;
|
||||
protected static final int MENU_CLONE = 104;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.profile_main);
|
||||
|
||||
Toolbar toolbar = findViewById(R.id.profile_toolbar);
|
||||
setSupportActionBar(toolbar);
|
||||
|
||||
getSupportActionBar().setHomeButtonEnabled(true);
|
||||
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
||||
|
||||
initList();
|
||||
|
||||
ListView listView = findViewById(R.id.listProfileView);
|
||||
profileAdapter = new ProfileAdapter(profilesList, this);
|
||||
listView.setAdapter(profileAdapter);
|
||||
// we register for the contextmneu
|
||||
registerForContextMenu(listView);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(android.view.Menu menu) {
|
||||
// Common options: Copy, Export to SD Card, Refresh
|
||||
menu.add(0, MENU_ADD, 0, getString(R.string.profile_add)).setIcon(R.drawable.plus).setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
|
||||
super.onCreateOptionsMenu(menu);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
|
||||
switch (item.getItemId()) {
|
||||
case MENU_ADD:
|
||||
addNewProfile();
|
||||
break;
|
||||
case android.R.id.home:
|
||||
onBackPressed();
|
||||
return true;
|
||||
default:
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void onCreateContextMenu(ContextMenu menu, View v,
|
||||
ContextMenu.ContextMenuInfo menuInfo) {
|
||||
super.onCreateContextMenu(menu, v, menuInfo);
|
||||
AdapterView.AdapterContextMenuInfo aInfo = (AdapterView.AdapterContextMenuInfo) menuInfo;
|
||||
//ProfileData profile = profileAdapter.getItem(aInfo.position);
|
||||
String name = ((TextView) aInfo.targetView.findViewById(R.id.pro_name)).getText().toString();
|
||||
menu.setHeaderTitle(getString(R.string.select) + " " + name);
|
||||
if (G.isProfileMigrated()) {
|
||||
menu.add(0, MENU_CLONE, 0, getString(R.string.clone));
|
||||
menu.add(0, MENU_RENAME, 0, getString(R.string.rename));
|
||||
}
|
||||
menu.add(0, MENU_DELETE, 0, getString(R.string.delete));
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean onContextItemSelected(MenuItem item) {
|
||||
int itemId = item.getItemId();
|
||||
AdapterView.AdapterContextMenuInfo aInfo = (AdapterView.AdapterContextMenuInfo) item.getMenuInfo();
|
||||
String profileName = profilesList.get(aInfo.position).getName();
|
||||
switch (itemId) {
|
||||
case MENU_DELETE:
|
||||
if (!G.isProfileMigrated()) {
|
||||
if (aInfo.position > 3) {
|
||||
boolean deleted = G.removeAdditionalProfile(profileName);
|
||||
if (deleted) {
|
||||
profilesList.remove(aInfo.position);
|
||||
profileAdapter.notifyDataSetChanged();
|
||||
} else {
|
||||
Api.toast(getApplicationContext(), getString(R.string.delete_profile));
|
||||
}
|
||||
} else {
|
||||
//TODO: can't delete default profiles(1,2,3) msg - Use migrate option
|
||||
Api.toast(getApplicationContext(), getString(R.string.profile_notsupport));
|
||||
}
|
||||
} else {
|
||||
if (aInfo.position != 0) {
|
||||
ProfileData data = ProfileHelper.getProfileByName(profileName);
|
||||
if (data != null && ProfileHelper.deleteProfileByName(profileName)
|
||||
&& G.clearSharedPreferences(getApplicationContext(), data.getIdentifier())) {
|
||||
profilesList.remove(aInfo.position);
|
||||
profileAdapter.notifyDataSetChanged();
|
||||
}
|
||||
} else {
|
||||
//can't delete default profile
|
||||
}
|
||||
}
|
||||
break;
|
||||
case MENU_CLONE:
|
||||
if ((G.isDoKey(getApplicationContext()) || isDonate())) {
|
||||
ProfileData data = ProfileHelper.getProfileByName(profileName);
|
||||
if(data != null) {
|
||||
String exitingName = data.getName();
|
||||
if(data != null) {
|
||||
new MaterialDialog.Builder(this)
|
||||
.cancelable(true)
|
||||
.title(R.string.profile_rename)
|
||||
.inputType(InputType.TYPE_CLASS_TEXT)
|
||||
.input(exitingName, exitingName, (dialog, input) -> {
|
||||
String newName = input.toString();
|
||||
//copy data
|
||||
ProfileData data1 = null;
|
||||
try {
|
||||
data1 = data.clone();
|
||||
if (isNotDuplicate(newName)) {
|
||||
String identifier = newName.replaceAll("\\s+", "");
|
||||
data1.removeId();
|
||||
data1.setName(newName);
|
||||
data1.setIdentifier(identifier);
|
||||
data1.save();
|
||||
SharedPreferences fromShared = getSharedPreferences(profileName, Context.MODE_PRIVATE);
|
||||
SharedPreferences.Editor toShared = getSharedPreferences(newName,Context.MODE_PRIVATE).edit();
|
||||
Api.copySharedPreferences(fromShared,toShared);
|
||||
profilesList.add(data1);
|
||||
profileAdapter.notifyDataSetChanged();
|
||||
} else {
|
||||
Api.toast(getApplicationContext(), getString(R.string.profile_duplicate));
|
||||
}
|
||||
} catch (CloneNotSupportedException e) {
|
||||
Log.e(G.TAG, e.getMessage(), e);
|
||||
}
|
||||
|
||||
|
||||
}).show();
|
||||
}
|
||||
} else{
|
||||
Log.i(G.TAG,"Unable to clone. Data from DB is empty");
|
||||
Toast.makeText(getApplicationContext(), getString(R.string.unable_clone), Toast.LENGTH_LONG).show();
|
||||
}
|
||||
|
||||
} else{
|
||||
Api.donateDialog(ProfileActivity.this, true);
|
||||
}
|
||||
break;
|
||||
case MENU_RENAME:
|
||||
ProfileData data2 = ProfileHelper.getProfileByName(profileName);
|
||||
if (data2 != null) {
|
||||
renameProfile(data2, aInfo.position);
|
||||
}
|
||||
break;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
private void initList() {
|
||||
profilesList = new ArrayList<>();
|
||||
// We populate the Profiles
|
||||
profilesList.add(new ProfileData(G.gPrefs.getString("default", getString(R.string.defaultProfile)), ""));
|
||||
|
||||
if (G.isProfileMigrated()) {
|
||||
List<ProfileData> profiles = ProfileHelper.getProfiles();
|
||||
profilesList.addAll(profiles);
|
||||
} else {
|
||||
profilesList.add(new ProfileData(G.gPrefs.getString("profile1", getString(R.string.profile1)), "AFWallProfile1"));
|
||||
profilesList.add(new ProfileData(G.gPrefs.getString("profile2", getString(R.string.profile2)), "AFWallProfile2"));
|
||||
profilesList.add(new ProfileData(G.gPrefs.getString("profile3", getString(R.string.profile3)), "AFWallProfile3"));
|
||||
|
||||
List<String> pList = G.getAdditionalProfiles();
|
||||
for (String profileName : pList) {
|
||||
if (profileName != null && profileName.length() > 0) {
|
||||
profilesList.add(new ProfileData(profileName, profileName));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void renameProfile(final ProfileData data, final int position) {
|
||||
String exitingName = data.getName();
|
||||
new MaterialDialog.Builder(this)
|
||||
.cancelable(true)
|
||||
.title(R.string.profile_rename)
|
||||
.inputType(InputType.TYPE_CLASS_TEXT)
|
||||
.input(exitingName, exitingName, (dialog, input) -> {
|
||||
String profileName = input.toString();
|
||||
if (isNotDuplicate(profileName)) {
|
||||
profilesList.remove(position);
|
||||
data.setName(profileName);
|
||||
data.save();
|
||||
profilesList.add(position, data);
|
||||
profileAdapter.notifyDataSetChanged();
|
||||
} else {
|
||||
Api.toast(getApplicationContext(), getString(R.string.profile_duplicate));
|
||||
}
|
||||
|
||||
}).show();
|
||||
}
|
||||
|
||||
// Handle user click
|
||||
private void addNewProfile() {
|
||||
|
||||
new MaterialDialog.Builder(this)
|
||||
.cancelable(true)
|
||||
.title(R.string.profile_add)
|
||||
.inputType(InputType.TYPE_CLASS_TEXT)
|
||||
.input(R.string.profile_add, R.string.profile_hint, (dialog, input) -> {
|
||||
String profileName = input.toString();
|
||||
if (isNotDuplicate(profileName)) {
|
||||
String identifier = profileName.replaceAll("\\s+", "");
|
||||
ProfileData data = new ProfileData(profileName, identifier);
|
||||
if (G.isProfileMigrated()) {
|
||||
//store to database
|
||||
data.save();
|
||||
profilesList.add(data);
|
||||
profileAdapter.notifyDataSetChanged();
|
||||
} else {
|
||||
Api.toast(getApplicationContext(), getString(R.string.profile_notsupport));
|
||||
}
|
||||
} else {
|
||||
Api.toast(getApplicationContext(), getString(R.string.profile_duplicate));
|
||||
}
|
||||
// We notify the data model is changed
|
||||
}).show();
|
||||
|
||||
}
|
||||
|
||||
private boolean isNotDuplicate(String profileName) {
|
||||
for (ProfileData data : profilesList) {
|
||||
if (data.getName().equals(profileName)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,369 @@
|
|||
/**
|
||||
* Display firewall rules and interface info
|
||||
* <p>
|
||||
* Copyright (C) 2011-2013 Kevin Cernekee
|
||||
* <p>
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
* <p>
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
* <p>
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* @author Kevin Cernekee
|
||||
* @version 1.0
|
||||
*/
|
||||
|
||||
package dev.ukanth.ufirewall.activity;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.PackageManager.NameNotFoundException;
|
||||
import android.net.ConnectivityManager;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Bundle;
|
||||
import android.view.MenuItem;
|
||||
import android.view.SubMenu;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.afollestad.materialdialogs.DialogAction;
|
||||
import com.afollestad.materialdialogs.MaterialDialog;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Map;
|
||||
import java.util.TreeSet;
|
||||
|
||||
import dev.ukanth.ufirewall.Api;
|
||||
import dev.ukanth.ufirewall.InterfaceDetails;
|
||||
import dev.ukanth.ufirewall.InterfaceTracker;
|
||||
import dev.ukanth.ufirewall.R;
|
||||
import dev.ukanth.ufirewall.log.Log;
|
||||
import dev.ukanth.ufirewall.service.RootCommand;
|
||||
import dev.ukanth.ufirewall.util.G;
|
||||
import dev.ukanth.ufirewall.util.SecurityUtil;
|
||||
|
||||
public class RulesActivity extends DataDumpActivity {
|
||||
|
||||
protected static final int MENU_FLUSH_RULES = 12;
|
||||
protected static final int MENU_IPV6_RULES = 19;
|
||||
protected static final int MENU_IPV4_RULES = 20;
|
||||
protected static final int MENU_SEND_REPORT = 25;
|
||||
|
||||
protected boolean showIPv6 = false;
|
||||
protected static StringBuilder result;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setTitle(getString(R.string.showrules_title));
|
||||
|
||||
//coming from shortcut
|
||||
Bundle bundle = getIntent().getExtras();
|
||||
if (bundle != null) {
|
||||
Object data = bundle.get("validate");
|
||||
if (data != null) {
|
||||
String check = (String) data;
|
||||
if (check.equals("yes")) {
|
||||
new SecurityUtil( RulesActivity.this).passCheck();
|
||||
}
|
||||
}
|
||||
}
|
||||
//sdDumpFile = "rules.log";
|
||||
}
|
||||
|
||||
|
||||
protected void populateMenu(SubMenu sub) {
|
||||
if (G.enableIPv6()) {
|
||||
sub.add(0, MENU_IPV6_RULES, 0, R.string.switch_ipv6).setIcon(R.drawable.ic_rules);
|
||||
sub.add(0, MENU_IPV4_RULES, 0, R.string.switch_ipv4).setIcon(R.drawable.ic_rules);
|
||||
}
|
||||
sub.add(0, MENU_FLUSH_RULES, 0, R.string.flush).setIcon(R.drawable.ic_clearlog);
|
||||
sub.add(0, MENU_SEND_REPORT, 0, R.string.send_report).setIcon(R.drawable.ic_mail);
|
||||
}
|
||||
|
||||
private void writeHeading(StringBuilder res, boolean initialNewline, String title) {
|
||||
StringBuilder eq = new StringBuilder();
|
||||
|
||||
for (int i = 0; i < title.length(); i++) {
|
||||
eq.append('=');
|
||||
}
|
||||
|
||||
if (initialNewline) {
|
||||
res.append("\n");
|
||||
}
|
||||
res.append(eq).append("\n").append(title).append("\n").append(eq).append("\n\n");
|
||||
}
|
||||
|
||||
protected void appendPreferences(final Context ctx) {
|
||||
// Fifth section: "Preferences"
|
||||
writeHeading(result, true, "Preferences");
|
||||
|
||||
try {
|
||||
Map<String, ?> prefs = G.gPrefs.getAll();
|
||||
for (String s : new TreeSet<String>(prefs.keySet())) {
|
||||
Object entry = prefs.get(s);
|
||||
result.append(s).append(": ").append(entry.toString()).append("\n");
|
||||
}
|
||||
//append profile mode & Status
|
||||
result.append("Profile Mode : ").append(G.pPrefs.getString(Api.PREF_MODE, "")).append("\n");
|
||||
result.append("Status : ").append(Api.isEnabled(ctx) ? "Enabled" : "Disabled").append("\n");
|
||||
} catch (NullPointerException e) {
|
||||
result.append("Error retrieving preferences\n");
|
||||
}
|
||||
|
||||
// Sixth section: "Logcat"
|
||||
writeHeading(result, true, "Logcat");
|
||||
result.append(Log.getLog());
|
||||
|
||||
// finished: post result to the user
|
||||
setData(result.toString());
|
||||
}
|
||||
|
||||
protected String getFileInfo(String filename) {
|
||||
File f = new File(filename);
|
||||
if (f.exists() && f.isFile()) {
|
||||
return filename + ": " +
|
||||
f.length() + " bytes\n";
|
||||
} else {
|
||||
return filename + ": not present\n";
|
||||
}
|
||||
}
|
||||
|
||||
protected String getSuInfo(PackageManager pm) {
|
||||
String[] suPackages = {
|
||||
"com.koushikdutta.superuser",
|
||||
"com.noshufou.android.su",
|
||||
"com.noshufou.android.su.elite",
|
||||
"com.koushikdutta.superuser",
|
||||
"com.gorserapp.superuser",
|
||||
"me.phh.superuser",
|
||||
"com.bitcubate.superuser.pro",
|
||||
"com.kingroot.kinguser",
|
||||
"com.kingroot.master",
|
||||
"com.kingouser.com",
|
||||
"com.m0narx.su",
|
||||
"com.miui.uac",
|
||||
"eu.chainfire.supersu",
|
||||
"eu.chainfire.supersu.pro",
|
||||
"com.topjohnwu.magisk"
|
||||
};
|
||||
String found = "none found";
|
||||
|
||||
for (String s : suPackages) {
|
||||
try {
|
||||
PackageInfo info = pm.getPackageInfo(s, 0);
|
||||
found = s + " v" + info.versionName;
|
||||
break;
|
||||
} catch (NameNotFoundException e) {
|
||||
}
|
||||
}
|
||||
|
||||
return found;
|
||||
}
|
||||
|
||||
protected void appendSystemInfo(final Context ctx) {
|
||||
// Fourth section: "System info"
|
||||
writeHeading(result, true, "System info");
|
||||
|
||||
InterfaceDetails cfg = InterfaceTracker.getCurrentCfg(ctx, false);
|
||||
|
||||
result.append("Android version: ").append(android.os.Build.VERSION.RELEASE).append("\n");
|
||||
result.append("Manufacturer: ").append(android.os.Build.MANUFACTURER).append("\n");
|
||||
result.append("Model: ").append(android.os.Build.MODEL).append("\n");
|
||||
result.append("Build: ").append(android.os.Build.DISPLAY).append("\n");
|
||||
|
||||
if (cfg.netType == ConnectivityManager.TYPE_MOBILE) {
|
||||
result.append("Active interface: mobile\n");
|
||||
} else if (cfg.netType == ConnectivityManager.TYPE_WIFI) {
|
||||
result.append("Active interface: wifi\n");
|
||||
} else {
|
||||
result.append("Active interface: unknown\n");
|
||||
}
|
||||
result.append("Wifi Tether status: ").append(cfg.tetherWifiStatusKnown ? (cfg.isWifiTethered ? "yes" : "no") : "unknown").append("\n");
|
||||
result.append("Bluetooth Tether status: ").append(cfg.tetherBluetoothStatusKnown ? (cfg.isBluetoothTethered ? "yes" : "no") : "unknown").append("\n");
|
||||
result.append("Usb Tether status: ").append(cfg.tetherUsbStatusKnown ? (cfg.isUsbTethered ? "yes" : "no") : "unknown").append("\n");
|
||||
result.append("Roam status: ").append(cfg.isRoaming ? "yes" : "no").append("\n");
|
||||
result.append("IPv4 subnet: ").append(cfg.lanMaskV4).append("\n");
|
||||
result.append("IPv6 subnet: ").append(cfg.lanMaskV6).append("\n");
|
||||
|
||||
// filesystem calls can block, so run in another thread
|
||||
new AsyncTask<Void, Void, String>() {
|
||||
@Override
|
||||
public String doInBackground(Void... args) {
|
||||
StringBuilder ret = new StringBuilder();
|
||||
|
||||
ret.append(getFileInfo("/system/bin/su"));
|
||||
ret.append(getFileInfo("/system/xbin/su"));
|
||||
ret.append(getFileInfo("/data/magisk/magisk"));
|
||||
ret.append(getFileInfo("/system/app/Superuser.apk"));
|
||||
|
||||
PackageManager pm = ctx.getPackageManager();
|
||||
ret.append("Superuser: ").append(getSuInfo(pm));
|
||||
ret.append("\n");
|
||||
|
||||
return ret.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPostExecute(String suInfo) {
|
||||
result.append(suInfo);
|
||||
updateLoadingState(getString(R.string.finalizing));
|
||||
appendPreferences(ctx);
|
||||
}
|
||||
}.execute();
|
||||
|
||||
}
|
||||
|
||||
protected void appendIfconfig(final Context ctx) {
|
||||
// Third section: "ifconfig" (for interface info obtained through busybox)
|
||||
writeHeading(result, true, "ifconfig");
|
||||
updateLoadingState(getString(R.string.loading_system_info));
|
||||
Api.runIfconfig(ctx, new RootCommand()
|
||||
.setLogging(true)
|
||||
.setCallback(new RootCommand.Callback() {
|
||||
public void cbFunc(RootCommand state) {
|
||||
result.append(state.res);
|
||||
appendSystemInfo(ctx);
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
protected void appendNetworkInterfaces(final Context ctx) {
|
||||
// Second section: "Network Interfaces" (for interface info obtained through Android APIs)
|
||||
writeHeading(result, true, "Network interfaces");
|
||||
Api.runNetworkInterface(ctx, new RootCommand()
|
||||
.setLogging(true)
|
||||
.setCallback(new RootCommand.Callback() {
|
||||
public void cbFunc(RootCommand state) {
|
||||
String iface = state.res.toString();
|
||||
result.append(iface);
|
||||
appendIfconfig(ctx);
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
protected void populateData(final Context ctx) {
|
||||
result = new StringBuilder();
|
||||
|
||||
// Update loading state for modern layout
|
||||
updateLoadingState(getString(R.string.loading));
|
||||
|
||||
// First section: "IPxx Rules"
|
||||
writeHeading(result, false, showIPv6 ? getString(R.string.ipv6_rules_title) : getString(R.string.ipv4_rules_title));
|
||||
if (showIPv6) {
|
||||
sdDumpFile = "IPv6rules.log";
|
||||
} else {
|
||||
sdDumpFile = "IPv4rules.log";
|
||||
}
|
||||
Api.fetchIptablesRules(ctx, showIPv6, new RootCommand()
|
||||
.setLogging(true)
|
||||
.setReopenShell(true)
|
||||
.setFailureToast(R.string.error_fetch)
|
||||
.setCallback(new RootCommand.Callback() {
|
||||
public void cbFunc(RootCommand state) {
|
||||
result.append(state.res);
|
||||
updateLoadingState(getString(R.string.loading_network_info));
|
||||
appendNetworkInterfaces(ctx);
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
private void updateLoadingState(String status) {
|
||||
runOnUiThread(() -> {
|
||||
TextView rulesStatus = findViewById(R.id.rules_status);
|
||||
TextView rulesTitle = findViewById(R.id.rules_title);
|
||||
if (rulesStatus != null) {
|
||||
rulesStatus.setText(status);
|
||||
}
|
||||
if (rulesTitle != null) {
|
||||
String title = showIPv6 ? getString(R.string.ipv6_rules_title) : getString(R.string.ipv4_rules_title);
|
||||
rulesTitle.setText(title);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
final Context ctx = this;
|
||||
|
||||
switch (item.getItemId()) {
|
||||
|
||||
case android.R.id.home: {
|
||||
onBackPressed();
|
||||
return true;
|
||||
}
|
||||
case MENU_FLUSH_RULES:
|
||||
flushAllRules(ctx);
|
||||
return true;
|
||||
case MENU_IPV6_RULES:
|
||||
showIPv6 = true;
|
||||
updateLoadingState(getString(R.string.loading));
|
||||
populateData(this);
|
||||
return true;
|
||||
case MENU_IPV4_RULES:
|
||||
showIPv6 = false;
|
||||
updateLoadingState(getString(R.string.loading));
|
||||
populateData(this);
|
||||
return true;
|
||||
case MENU_SEND_REPORT:
|
||||
String ver;
|
||||
try {
|
||||
ver = ctx.getPackageManager().getPackageInfo(ctx.getPackageName(), 0).versionName;
|
||||
} catch (NameNotFoundException e) {
|
||||
ver = "???";
|
||||
}
|
||||
String body = dataText + "\n\n" + getString(R.string.enter_problem) + "\n\n";
|
||||
final Intent emailIntent = new Intent(android.content.Intent.ACTION_SEND);
|
||||
|
||||
emailIntent.setType("plain/text");
|
||||
emailIntent.putExtra(android.content.Intent.EXTRA_EMAIL, new String[]{"afwall-report@googlegroups.com"});
|
||||
emailIntent.putExtra(android.content.Intent.EXTRA_SUBJECT, "AFWall+ problem report - v" + ver);
|
||||
emailIntent.putExtra(android.content.Intent.EXTRA_TEXT, body);
|
||||
startActivity(Intent.createChooser(emailIntent, getString(R.string.send_mail)));
|
||||
|
||||
// this shouldn't be necessary, but the default Android email client overrides
|
||||
// "body=" from the URI. See MessageCompose.initFromIntent()
|
||||
//email.putExtra(Intent.EXTRA_TEXT, body);
|
||||
|
||||
//startActivity(Intent.createChooser(email, getString(R.string.send_mail)));
|
||||
return true;
|
||||
}
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
private void flushAllRules(final Context ctx) {
|
||||
|
||||
new MaterialDialog.Builder(this)
|
||||
.title(R.string.confirmation)
|
||||
.content(R.string.flushRulesConfirm)
|
||||
.positiveText(R.string.Yes)
|
||||
.negativeText(R.string.No)
|
||||
.onPositive((dialog, which) -> {
|
||||
Api.flushAllRules(ctx, new RootCommand()
|
||||
.setReopenShell(true)
|
||||
.setSuccessToast(R.string.flushed)
|
||||
.setFailureToast(R.string.error_purge)
|
||||
.setCallback(new RootCommand.Callback() {
|
||||
public void cbFunc(RootCommand state) {
|
||||
populateData(ctx);
|
||||
}
|
||||
}));
|
||||
dialog.dismiss();
|
||||
})
|
||||
|
||||
.onNegative((dialog, which) -> dialog.dismiss())
|
||||
.show();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
package dev.ukanth.ufirewall.activity;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
|
||||
import dev.ukanth.ufirewall.MainActivity;
|
||||
|
||||
public class StartActivity extends BaseActivity {
|
||||
|
||||
/*
|
||||
* This activity only existed, so the user can toggle between
|
||||
* classic and material icon.
|
||||
*/
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
Intent intent = new Intent(this, MainActivity.class);
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
|
||||
if (getIntent().getExtras() != null) {
|
||||
intent.putExtras(getIntent().getExtras());
|
||||
}
|
||||
startActivity(intent);
|
||||
finish();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
package dev.ukanth.ufirewall.admin;
|
||||
|
||||
import android.app.admin.DeviceAdminReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.widget.Toast;
|
||||
|
||||
import dev.ukanth.ufirewall.R;
|
||||
import dev.ukanth.ufirewall.log.Log;
|
||||
import dev.ukanth.ufirewall.util.G;
|
||||
|
||||
|
||||
/**
|
||||
* This is the component that is responsible for actual device administration.
|
||||
* It becomes the receiver when a policy is applied. It is important that we
|
||||
* subclass DeviceAdminReceiver class here and to implement its only required
|
||||
* method onEnabled().
|
||||
*/
|
||||
public class AdminDeviceReceiver extends DeviceAdminReceiver {
|
||||
static final String TAG = "AdminDeviceReceiver";
|
||||
|
||||
@Override
|
||||
public void onEnabled(Context context, Intent intent) {
|
||||
super.onEnabled(context, intent);
|
||||
G.enableAdmin(true);
|
||||
Toast.makeText(context, R.string.device_admin_enabled ,Toast.LENGTH_LONG).show();
|
||||
Log.d(TAG, "onEnabled");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDisabled(Context context, Intent intent) {
|
||||
super.onDisabled(context, intent);
|
||||
G.enableAdmin(false);
|
||||
Toast.makeText(context, R.string.device_admin_disabled,Toast.LENGTH_LONG).show();
|
||||
Log.d(TAG, "onDisabled");
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,83 @@
|
|||
/**
|
||||
* Detect the connectivity changes (for roaming and LAN subnet changes)
|
||||
* <p>
|
||||
* Copyright (C) 2011-2012 Umakanthan Chandran
|
||||
* <p>
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
* <p>
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
* <p>
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* @author Umakanthan Chandran
|
||||
* @version 1.0
|
||||
*/
|
||||
package dev.ukanth.ufirewall.broadcast;
|
||||
|
||||
import static android.net.ConnectivityManager.CONNECTIVITY_ACTION;
|
||||
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
|
||||
import dev.ukanth.ufirewall.Api;
|
||||
import dev.ukanth.ufirewall.InterfaceTracker;
|
||||
import dev.ukanth.ufirewall.log.Log;
|
||||
import dev.ukanth.ufirewall.util.BootRuleManager;
|
||||
import dev.ukanth.ufirewall.util.G;
|
||||
|
||||
public class ConnectivityChangeReceiver extends BroadcastReceiver {
|
||||
|
||||
public static final String TAG = "AFWall";
|
||||
|
||||
// These are marked "@hide" in WifiManager.java
|
||||
public static final String WIFI_AP_STATE_CHANGED_ACTION = "android.net.wifi.WIFI_AP_STATE_CHANGED";
|
||||
public static final String TETHER_STATE_CHANGED_ACTION = "android.net.conn.TETHER_STATE_CHANGED";
|
||||
public static final String EXTRA_WIFI_AP_STATE = "wifi_state";
|
||||
public static final String EXTRA_PREVIOUS_WIFI_AP_STATE = "previous_wifi_state";
|
||||
|
||||
|
||||
@Override
|
||||
public void onReceive(final Context context, Intent intent) {
|
||||
|
||||
int status = Api.getConnectivityStatus(context);
|
||||
if (status > 0) {
|
||||
|
||||
// NOTE: this gets called for wifi/3G/tether/roam changes but not VPN connect/disconnect
|
||||
// This will prevent applying rules when the user disable the option in preferences. This is for low end devices
|
||||
if (intent.getAction().equals(WIFI_AP_STATE_CHANGED_ACTION)) {
|
||||
int newState = intent.getIntExtra(EXTRA_WIFI_AP_STATE, -1);
|
||||
int oldState = intent.getIntExtra(EXTRA_PREVIOUS_WIFI_AP_STATE, -1);
|
||||
Log.d(TAG, "OS reported AP state change: " + oldState + " -> " + newState);
|
||||
// Note: AP state changes are logged but don't trigger rule application during boot
|
||||
}
|
||||
|
||||
if (Api.isEnabled(context) && G.activeRules()) {
|
||||
String action = intent.getAction();
|
||||
String reason = action.equals(CONNECTIVITY_ACTION) ?
|
||||
InterfaceTracker.CONNECTIVITY_CHANGE : InterfaceTracker.TETHER_STATE_CHANGED;
|
||||
|
||||
// Check with BootRuleManager if we should process this network change
|
||||
if (!BootRuleManager.shouldProcessNetworkChange(context, reason)) {
|
||||
Log.d(TAG, "Network change ignored during boot process: " + reason);
|
||||
return;
|
||||
}
|
||||
|
||||
if (action.equals(CONNECTIVITY_ACTION)) {
|
||||
Log.i(TAG, "Network change captured.");
|
||||
InterfaceTracker.applyRulesOnChange(context, InterfaceTracker.CONNECTIVITY_CHANGE);
|
||||
} else if (action.equals(TETHER_STATE_CHANGED_ACTION)) {
|
||||
Log.i(TAG, "Tether change captured.");
|
||||
InterfaceTracker.applyRulesOnChange(context, InterfaceTracker.TETHER_STATE_CHANGED);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,70 @@
|
|||
package dev.ukanth.ufirewall.broadcast;
|
||||
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.Messenger;
|
||||
|
||||
import dev.ukanth.ufirewall.Api;
|
||||
import dev.ukanth.ufirewall.InterfaceTracker;
|
||||
import dev.ukanth.ufirewall.MainActivity;
|
||||
import dev.ukanth.ufirewall.log.Log;
|
||||
import dev.ukanth.ufirewall.service.FirewallService;
|
||||
import dev.ukanth.ufirewall.service.LogService;
|
||||
import dev.ukanth.ufirewall.util.BootRuleManager;
|
||||
import dev.ukanth.ufirewall.util.G;
|
||||
|
||||
public class OnBootReceiver extends BroadcastReceiver {
|
||||
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
if (Intent.ACTION_BOOT_COMPLETED.equals(intent.getAction())) {
|
||||
Messenger messenger = null;
|
||||
if (intent != null) {
|
||||
Bundle extras = intent.getExtras();
|
||||
if (extras != null) {
|
||||
messenger = (Messenger) extras.get("messenger");
|
||||
}
|
||||
}
|
||||
|
||||
if (messenger == null) {
|
||||
PackageManager pm = context.getPackageManager();
|
||||
pm.setComponentEnabledSetting(new ComponentName(context, MainActivity.class),
|
||||
PackageManager.COMPONENT_ENABLED_STATE_ENABLED, PackageManager.DONT_KILL_APP);
|
||||
}
|
||||
|
||||
Log.i("AFWall", "Startin boot service");
|
||||
if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
Log.i("AFWall", "Starting firewall service onboot");
|
||||
context.startForegroundService(new Intent(context, FirewallService.class));
|
||||
} else {
|
||||
context.startService(new Intent(context, FirewallService.class));
|
||||
}
|
||||
|
||||
// Use BootRuleManager for robust rule application
|
||||
BootRuleManager.initializeBootRuleApplication(context);
|
||||
|
||||
//register private DNS change listener
|
||||
|
||||
|
||||
if (G.enableLogService()) {
|
||||
Log.i("AFWall", "Starting log service onboot");
|
||||
try {
|
||||
context.startService(new Intent(context, LogService.class));
|
||||
} catch (Exception e) {
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
G.registerPrivateLink();
|
||||
}catch (Exception e){
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,192 @@
|
|||
/**
|
||||
* Broadcast receiver responsible for removing rules that affect uninstalled apps.
|
||||
* <p>
|
||||
* Copyright (C) 2009-2011 Rodrigo Zechin Rosauro
|
||||
* Copyright (C) 2011-2012 Umakanthan Chandran
|
||||
* <p>
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
* <p>
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
* <p>
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* @author Rodrigo Zechin Rosauro, Umakanthan Chandran
|
||||
* @version 1.1
|
||||
*/
|
||||
package dev.ukanth.ufirewall.broadcast;
|
||||
|
||||
import static dev.ukanth.ufirewall.util.G.isDonate;
|
||||
|
||||
import android.Manifest;
|
||||
import android.app.Notification;
|
||||
import android.app.NotificationChannel;
|
||||
import android.app.NotificationManager;
|
||||
import android.app.PendingIntent;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.PackageManager.NameNotFoundException;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.preference.PreferenceManager;
|
||||
|
||||
import androidx.core.app.NotificationCompat;
|
||||
|
||||
import java.util.HashSet;
|
||||
|
||||
import dev.ukanth.ufirewall.Api;
|
||||
import dev.ukanth.ufirewall.MainActivity;
|
||||
import dev.ukanth.ufirewall.R;
|
||||
import dev.ukanth.ufirewall.log.Log;
|
||||
import dev.ukanth.ufirewall.service.RootCommand;
|
||||
import dev.ukanth.ufirewall.util.G;
|
||||
import dev.ukanth.ufirewall.util.UidResolver;
|
||||
|
||||
/**
|
||||
* Broadcast receiver responsible for removing rules that affect uninstalled
|
||||
* apps.
|
||||
*/
|
||||
public class PackageBroadcast extends BroadcastReceiver {
|
||||
|
||||
public static final String TAG = "AFWall";
|
||||
|
||||
@Override
|
||||
public void onReceive(final Context context, final Intent intent) {
|
||||
|
||||
Uri inputUri = Uri.parse(intent.getDataString());
|
||||
|
||||
if (!inputUri.getScheme().equals("package")) {
|
||||
Log.d(TAG, "Intent scheme was not 'package'");
|
||||
return;
|
||||
}
|
||||
|
||||
if (Intent.ACTION_PACKAGE_REMOVED.equals(intent.getAction())) {
|
||||
// Ignore application updates
|
||||
final boolean replacing = intent.getBooleanExtra(
|
||||
Intent.EXTRA_REPLACING, false);
|
||||
if (!replacing) {
|
||||
// Update the Firewall if necessary
|
||||
final int uid = intent.getIntExtra(Intent.EXTRA_UID, -123);
|
||||
String packageName = context.getPackageManager().getNameForUid(uid);
|
||||
//if it contains sharedID -- dont remove based on uid
|
||||
if(packageName != null && packageName.contains("sharedID")) {
|
||||
//ignore since the another app with same ID exists
|
||||
} else {
|
||||
Api.applicationRemoved(context, uid, new RootCommand()
|
||||
.setFailureToast(R.string.error_apply)
|
||||
.setCallback(new RootCommand.Callback() {
|
||||
@Override
|
||||
public void cbFunc(RootCommand state) {
|
||||
if (state.exitCode == 0) {
|
||||
Api.removeCacheLabel(intent.getData().getSchemeSpecificPart(), context);
|
||||
Api.removeAllUnusedCacheLabel(context);
|
||||
// Force app list reload next time
|
||||
Api.applications = null;
|
||||
// Invalidate UID resolver cache for this UID
|
||||
UidResolver.invalidateUid(uid);
|
||||
Log.d(TAG, "Package removed, invalidated UID cache for: " + uid);
|
||||
}
|
||||
}
|
||||
}));
|
||||
}
|
||||
}
|
||||
} else if (Intent.ACTION_PACKAGE_ADDED.equals(intent.getAction())) {
|
||||
final boolean updateApp = intent.getBooleanExtra(Intent.EXTRA_REPLACING, false);
|
||||
|
||||
if (updateApp) {
|
||||
// dont do anything
|
||||
//1 check the package already added in firewall
|
||||
} else {
|
||||
// Force app list reload next time
|
||||
Api.applications = null;
|
||||
|
||||
// Clear UID resolver cache since new package may get a UID we've seen before
|
||||
UidResolver.clearCache();
|
||||
Log.d(TAG, "Package added, cleared UID resolver cache");
|
||||
|
||||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
|
||||
boolean isNotify = prefs.getBoolean("notifyAppInstall", true);
|
||||
if (isNotify && Api.isEnabled(context)) {
|
||||
String added_package = intent.getData().getSchemeSpecificPart();
|
||||
final PackageManager packager = context.getPackageManager();
|
||||
String label = null;
|
||||
try {
|
||||
ApplicationInfo applicationInfo = packager.getApplicationInfo(added_package, 0);
|
||||
label = packager.getApplicationLabel(applicationInfo).toString();
|
||||
if (PackageManager.PERMISSION_GRANTED == packager.checkPermission(Manifest.permission.INTERNET, added_package)) {
|
||||
addNotification(context,label);
|
||||
}
|
||||
if (Api.recentlyInstalled == null) {
|
||||
Api.recentlyInstalled = new HashSet<>();
|
||||
}
|
||||
Api.recentlyInstalled.add(applicationInfo.packageName);
|
||||
//sets default permissions
|
||||
if ((G.isDoKey(context) || isDonate())) {
|
||||
Api.setDefaultPermission(applicationInfo);
|
||||
}
|
||||
} catch (NameNotFoundException e) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void addNotification(Context context, String label) {
|
||||
final int NOTIFICATION_ID = 100;
|
||||
String NOTIFICATION_CHANNEL_ID = "firewall.app.notification";
|
||||
String channelName = context.getString(R.string.app_notification);
|
||||
|
||||
//cancel existing notification
|
||||
NotificationManager manager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
|
||||
manager.cancel(NOTIFICATION_ID);
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
NotificationChannel chan = new NotificationChannel(NOTIFICATION_CHANNEL_ID, channelName, NotificationManager.IMPORTANCE_DEFAULT);
|
||||
chan.setShowBadge(false);
|
||||
chan.setSound(null,null);
|
||||
chan.enableLights(false);
|
||||
chan.enableVibration(false);
|
||||
chan.setLockscreenVisibility(Notification.VISIBILITY_PRIVATE);
|
||||
assert manager != null;
|
||||
manager.createNotificationChannel(chan);
|
||||
}
|
||||
|
||||
|
||||
Intent appIntent = new Intent(context, MainActivity.class);
|
||||
appIntent.setAction(Intent.ACTION_MAIN);
|
||||
appIntent.addCategory(Intent.CATEGORY_LAUNCHER);
|
||||
appIntent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP | Intent.FLAG_ACTIVITY_CLEAR_TOP);
|
||||
|
||||
PendingIntent notifyPendingIntent = PendingIntent.getActivity(context, 0, appIntent, PendingIntent.FLAG_IMMUTABLE);
|
||||
NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(context, NOTIFICATION_CHANNEL_ID);
|
||||
notificationBuilder.setContentIntent(notifyPendingIntent);
|
||||
|
||||
String notificationText = context.getString(R.string.notification_new);
|
||||
if (label != null) {
|
||||
notificationText = label + "-" + context.getString(R.string.notification_new_package);
|
||||
}
|
||||
|
||||
Notification notification = notificationBuilder.setOngoing(false)
|
||||
.setPriority(NotificationManager.IMPORTANCE_DEFAULT)
|
||||
.setCategory(Notification.CATEGORY_SERVICE)
|
||||
.setSound(null)
|
||||
.setSmallIcon(R.drawable.notification_quest)
|
||||
.setContentTitle(context.getString(R.string.notification_title))
|
||||
.setTicker(context.getString(R.string.notification_title))
|
||||
.setContentText(notificationText)
|
||||
.build();
|
||||
|
||||
manager.notify(NOTIFICATION_ID, notification);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,75 @@
|
|||
package dev.ukanth.ufirewall.customrules;
|
||||
|
||||
import com.raizlabs.android.dbflow.annotation.Column;
|
||||
import com.raizlabs.android.dbflow.annotation.PrimaryKey;
|
||||
import com.raizlabs.android.dbflow.annotation.Table;
|
||||
import com.raizlabs.android.dbflow.structure.BaseModel;
|
||||
|
||||
/**
|
||||
* Created by ukanth on 24/9/17.
|
||||
*/
|
||||
|
||||
@Table(database = CustomRuleDatabase.class)
|
||||
public class CustomRule extends BaseModel {
|
||||
|
||||
@Column
|
||||
@PrimaryKey(autoincrement = true)
|
||||
long id;
|
||||
|
||||
public long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
@Column
|
||||
private String name;
|
||||
|
||||
@Column
|
||||
private String rule;
|
||||
|
||||
@Column
|
||||
private long timestamp;
|
||||
|
||||
@Column
|
||||
private boolean active;
|
||||
|
||||
public CustomRule() {
|
||||
}
|
||||
|
||||
public CustomRule(String name, String rule) {
|
||||
this.name = name;
|
||||
this.rule = rule;
|
||||
this.timestamp = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public String getRule() {
|
||||
return rule;
|
||||
}
|
||||
|
||||
public void setRule(String rule) {
|
||||
this.rule = rule;
|
||||
}
|
||||
|
||||
public long getTimestamp() {
|
||||
return timestamp;
|
||||
}
|
||||
|
||||
public void setTimestamp(long timestamp) {
|
||||
this.timestamp = timestamp;
|
||||
}
|
||||
|
||||
public boolean isActive() {
|
||||
return active;
|
||||
}
|
||||
|
||||
public void setActive(boolean active) {
|
||||
this.active = active;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
package dev.ukanth.ufirewall.customrules;
|
||||
|
||||
import com.raizlabs.android.dbflow.annotation.Database;
|
||||
|
||||
/**
|
||||
* Created by ukanth on 24/9/17.
|
||||
*/
|
||||
|
||||
@Database(name = CustomRuleDatabase.NAME, version = CustomRuleDatabase.VERSION)
|
||||
public class CustomRuleDatabase {
|
||||
public static final String NAME = "rules";
|
||||
public static final int VERSION = 1;
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
package dev.ukanth.ufirewall.events;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
/**
|
||||
* Created by ukanth on 21/8/16.
|
||||
*/
|
||||
|
||||
public class LogChangeEvent {
|
||||
public final Context ctx;
|
||||
public final String message;
|
||||
|
||||
public LogChangeEvent(String message, Context ctx) {
|
||||
this.message = message;
|
||||
this.ctx = ctx;
|
||||
}
|
||||
}
|
||||
21
app/src/main/java/dev/ukanth/ufirewall/events/LogEvent.java
Normal file
21
app/src/main/java/dev/ukanth/ufirewall/events/LogEvent.java
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
package dev.ukanth.ufirewall.events;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import dev.ukanth.ufirewall.log.LogInfo;
|
||||
|
||||
/**
|
||||
* Created by ukanth on 21/8/16.
|
||||
*/
|
||||
|
||||
public class LogEvent {
|
||||
public final LogInfo logInfo;
|
||||
public final Context ctx;
|
||||
|
||||
public LogEvent(LogInfo logInfo, Context ctx) {
|
||||
this.logInfo = logInfo;
|
||||
this.ctx = ctx;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
package dev.ukanth.ufirewall.events;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
/**
|
||||
* Created by ukanth on 21/8/16.
|
||||
*/
|
||||
|
||||
public class RulesEvent {
|
||||
public final Context ctx;
|
||||
public final String message;
|
||||
|
||||
public RulesEvent(String message,Context ctx) {
|
||||
this.message = message;
|
||||
this.ctx = ctx;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
package dev.ukanth.ufirewall.events;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import io.reactivex.rxjava3.disposables.Disposable;
|
||||
import io.reactivex.rxjava3.functions.Consumer;
|
||||
import io.reactivex.rxjava3.subjects.PublishSubject;
|
||||
|
||||
/**
|
||||
* Created by ukanth on 25/9/17.
|
||||
*/
|
||||
|
||||
public class RxCommandEvent {
|
||||
private static final PublishSubject<Object> sSubject = PublishSubject.create();
|
||||
|
||||
private RxCommandEvent() {
|
||||
// hidden constructor
|
||||
}
|
||||
|
||||
public static Disposable subscribe(@NonNull Consumer<Object> action) {
|
||||
return sSubject.subscribe(action);
|
||||
}
|
||||
|
||||
public static void publish(@NonNull Object message) {
|
||||
sSubject.onNext(message);
|
||||
}
|
||||
}
|
||||
31
app/src/main/java/dev/ukanth/ufirewall/events/RxEvent.java
Normal file
31
app/src/main/java/dev/ukanth/ufirewall/events/RxEvent.java
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
package dev.ukanth.ufirewall.events;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import dev.ukanth.ufirewall.log.Log;
|
||||
import io.reactivex.rxjava3.disposables.Disposable;
|
||||
import io.reactivex.rxjava3.functions.Consumer;
|
||||
import io.reactivex.rxjava3.subjects.PublishSubject;
|
||||
|
||||
/**
|
||||
* Created by ukanth on 25/9/17.
|
||||
*/
|
||||
|
||||
public class RxEvent {
|
||||
private final PublishSubject<Object> sSubject = PublishSubject.create();
|
||||
|
||||
public RxEvent() {
|
||||
// hidden constructor
|
||||
}
|
||||
|
||||
public Disposable subscribe(@NonNull Consumer<Object> action) {
|
||||
return sSubject.subscribe(action, throwable -> {
|
||||
Log.i("AFWall", throwable.getLocalizedMessage());
|
||||
});
|
||||
}
|
||||
|
||||
public void publish(@NonNull Object message) {
|
||||
sSubject.onNext(message);
|
||||
}
|
||||
|
||||
}
|
||||
133
app/src/main/java/dev/ukanth/ufirewall/log/Log.java
Normal file
133
app/src/main/java/dev/ukanth/ufirewall/log/Log.java
Normal file
|
|
@ -0,0 +1,133 @@
|
|||
/**
|
||||
* Circular log buffer.
|
||||
* This provides timestamped logcat output for the "Rules" page, to aid
|
||||
* in diagnosing failures.
|
||||
*
|
||||
* Copyright (C) 2013 Kevin Cernekee
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* @author Kevin Cernekee
|
||||
* @version 1.0
|
||||
*/
|
||||
|
||||
package dev.ukanth.ufirewall.log;
|
||||
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.LinkedList;
|
||||
|
||||
public class Log {
|
||||
|
||||
private static final int MAX_ENTRIES = 512;
|
||||
|
||||
public static final int LOG_DEBUG = 0;
|
||||
public static final int LOG_VERBOSE = 1;
|
||||
public static final int LOG_INFO = 2;
|
||||
public static final int LOG_WARNING = 3;
|
||||
public static final int LOG_ERROR = 4;
|
||||
public static final int LOG_WTF = 5;
|
||||
|
||||
public static class LogEntry {
|
||||
Date timestamp;
|
||||
int level;
|
||||
String msg = "";
|
||||
}
|
||||
|
||||
private static final LinkedList<LogEntry> circ = new LinkedList<LogEntry>();
|
||||
|
||||
private static synchronized void circLog(int level, String msg) {
|
||||
LogEntry e = new LogEntry();
|
||||
e.timestamp = new Date();
|
||||
e.level = level;
|
||||
e.msg = msg;
|
||||
|
||||
if (circ.size() >= MAX_ENTRIES) {
|
||||
circ.removeFirst();
|
||||
}
|
||||
circ.addLast(e);
|
||||
}
|
||||
|
||||
public static synchronized String getLog() {
|
||||
StringBuilder ret = new StringBuilder();
|
||||
|
||||
for (int i = 0; i < circ.size(); i++) {
|
||||
LogEntry e = circ.get(i);
|
||||
String timestamp = new SimpleDateFormat("HH:mm:ss").format(e.timestamp);
|
||||
ret.append(timestamp).append(" ").append(e.msg).append("\n");
|
||||
}
|
||||
|
||||
return ret.toString();
|
||||
}
|
||||
|
||||
public static int d(String tag, String msg) {
|
||||
circLog(LOG_DEBUG, msg);
|
||||
return android.util.Log.d(tag, msg);
|
||||
}
|
||||
|
||||
public static int d(String tag, String msg, Exception e) {
|
||||
circLog(LOG_DEBUG, msg);
|
||||
return android.util.Log.d(tag, msg, e);
|
||||
}
|
||||
|
||||
public static int v(String tag, String msg) {
|
||||
circLog(LOG_VERBOSE, msg);
|
||||
return android.util.Log.v(tag, msg);
|
||||
}
|
||||
|
||||
public static int v(String tag, String msg, Exception e) {
|
||||
circLog(LOG_VERBOSE, msg);
|
||||
return android.util.Log.v(tag, msg, e);
|
||||
}
|
||||
|
||||
public static int i(String tag, String msg) {
|
||||
circLog(LOG_INFO, msg);
|
||||
return android.util.Log.i(tag, msg);
|
||||
}
|
||||
|
||||
public static int i(String tag, String msg, Exception e) {
|
||||
circLog(LOG_INFO, msg);
|
||||
return android.util.Log.i(tag, msg, e);
|
||||
}
|
||||
|
||||
public static int w(String tag, String msg) {
|
||||
circLog(LOG_WARNING, msg);
|
||||
return android.util.Log.w(tag, msg);
|
||||
}
|
||||
|
||||
public static int w(String tag, String msg, Exception e) {
|
||||
circLog(LOG_WARNING, msg);
|
||||
return android.util.Log.w(tag, msg, e);
|
||||
}
|
||||
|
||||
public static int e(String tag, String msg) {
|
||||
circLog(LOG_ERROR, msg);
|
||||
return android.util.Log.e(tag, msg);
|
||||
}
|
||||
|
||||
public static int e(String tag, String msg, Exception e) {
|
||||
circLog(LOG_ERROR, msg);
|
||||
return android.util.Log.e(tag, msg, e);
|
||||
}
|
||||
|
||||
public static int wtf(String tag, String msg) {
|
||||
circLog(LOG_WTF, msg);
|
||||
return android.util.Log.wtf(tag, msg);
|
||||
}
|
||||
|
||||
public static int wtf(String tag, String msg, Exception e) {
|
||||
circLog(LOG_WTF, msg);
|
||||
return android.util.Log.wtf(tag, msg, e);
|
||||
}
|
||||
}
|
||||
164
app/src/main/java/dev/ukanth/ufirewall/log/LogData.java
Normal file
164
app/src/main/java/dev/ukanth/ufirewall/log/LogData.java
Normal file
|
|
@ -0,0 +1,164 @@
|
|||
package dev.ukanth.ufirewall.log;
|
||||
|
||||
import com.raizlabs.android.dbflow.annotation.Column;
|
||||
import com.raizlabs.android.dbflow.annotation.PrimaryKey;
|
||||
import com.raizlabs.android.dbflow.annotation.Table;
|
||||
import com.raizlabs.android.dbflow.structure.BaseModel;
|
||||
|
||||
/**
|
||||
* Created by ukanth on 17/1/16.
|
||||
*/
|
||||
|
||||
@Table(database = LogDatabase.class,cachingEnabled = true)
|
||||
//,indexGroups = { @IndexGroup(number = 1, name = "uidIndex"),})
|
||||
public class LogData extends BaseModel {
|
||||
@Column
|
||||
@PrimaryKey(autoincrement = true)
|
||||
long id;
|
||||
|
||||
@Column
|
||||
private int uid;
|
||||
|
||||
@Column
|
||||
private String appName;
|
||||
|
||||
@Column
|
||||
private String in;
|
||||
@Column
|
||||
private String out;
|
||||
@Column
|
||||
private String proto;
|
||||
@Column
|
||||
private int spt;
|
||||
@Column
|
||||
private String dst;
|
||||
@Column
|
||||
private int len;
|
||||
@Column
|
||||
private String src;
|
||||
@Column
|
||||
private int dpt;
|
||||
@Column
|
||||
private long timestamp;
|
||||
|
||||
public String getHostname() {
|
||||
return hostname;
|
||||
}
|
||||
|
||||
public void setHostname(String hostname) {
|
||||
this.hostname = hostname;
|
||||
}
|
||||
|
||||
public int getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public void setType(int type) {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
@Column
|
||||
private String hostname;
|
||||
|
||||
@Column
|
||||
private int type;
|
||||
|
||||
public long getCount() {
|
||||
return count;
|
||||
}
|
||||
|
||||
public void setCount(long count) {
|
||||
this.count = count;
|
||||
}
|
||||
|
||||
private long count;
|
||||
|
||||
public int getUid() {
|
||||
return uid;
|
||||
}
|
||||
|
||||
public void setUid(int uid) {
|
||||
this.uid = uid;
|
||||
}
|
||||
|
||||
public String getAppName() {
|
||||
return appName;
|
||||
}
|
||||
|
||||
public void setAppName(String appName) {
|
||||
this.appName = appName;
|
||||
}
|
||||
|
||||
public String getIn() {
|
||||
return in;
|
||||
}
|
||||
|
||||
public void setIn(String in) {
|
||||
this.in = in;
|
||||
}
|
||||
|
||||
public String getOut() {
|
||||
return out;
|
||||
}
|
||||
|
||||
public void setOut(String out) {
|
||||
this.out = out;
|
||||
}
|
||||
|
||||
public String getProto() {
|
||||
return proto;
|
||||
}
|
||||
|
||||
public void setProto(String proto) {
|
||||
this.proto = proto;
|
||||
}
|
||||
|
||||
public int getSpt() {
|
||||
return spt;
|
||||
}
|
||||
|
||||
public void setSpt(int spt) {
|
||||
this.spt = spt;
|
||||
}
|
||||
|
||||
public String getDst() {
|
||||
return dst;
|
||||
}
|
||||
|
||||
public void setDst(String dst) {
|
||||
this.dst = dst;
|
||||
}
|
||||
|
||||
public int getLen() {
|
||||
return len;
|
||||
}
|
||||
|
||||
public void setLen(int len) {
|
||||
this.len = len;
|
||||
}
|
||||
|
||||
public String getSrc() {
|
||||
return src;
|
||||
}
|
||||
|
||||
public void setSrc(String src) {
|
||||
this.src = src;
|
||||
}
|
||||
|
||||
public int getDpt() {
|
||||
return dpt;
|
||||
}
|
||||
|
||||
public void setDpt(int dpt) {
|
||||
this.dpt = dpt;
|
||||
}
|
||||
|
||||
public long getTimestamp() {
|
||||
return timestamp;
|
||||
}
|
||||
|
||||
public void setTimestamp(long timestamp) {
|
||||
this.timestamp = timestamp;
|
||||
}
|
||||
|
||||
}
|
||||
33
app/src/main/java/dev/ukanth/ufirewall/log/LogDatabase.java
Normal file
33
app/src/main/java/dev/ukanth/ufirewall/log/LogDatabase.java
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
package dev.ukanth.ufirewall.log;
|
||||
|
||||
import com.raizlabs.android.dbflow.annotation.Database;
|
||||
import com.raizlabs.android.dbflow.annotation.Migration;
|
||||
import com.raizlabs.android.dbflow.sql.SQLiteType;
|
||||
import com.raizlabs.android.dbflow.sql.migration.AlterTableMigration;
|
||||
|
||||
/**
|
||||
* Created by ukanth on 17/1/16.
|
||||
*/
|
||||
|
||||
@Database(name = LogDatabase.NAME, version = LogDatabase.VERSION)
|
||||
public class LogDatabase {
|
||||
|
||||
public static final String NAME = "Logs";
|
||||
|
||||
public static final int VERSION = 2;
|
||||
|
||||
@Migration(version = 2, database = LogDatabase.class)
|
||||
public static class Migration2 extends AlterTableMigration<LogData> {
|
||||
|
||||
|
||||
public Migration2(Class<LogData> table) {
|
||||
super(table);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPreMigrate() {
|
||||
addColumn(SQLiteType.TEXT, "hostname");
|
||||
addColumn(SQLiteType.INTEGER, "type");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,273 @@
|
|||
package dev.ukanth.ufirewall.log;
|
||||
|
||||
import android.content.Context;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.HashMap;
|
||||
|
||||
import dev.ukanth.ufirewall.R;
|
||||
|
||||
/**
|
||||
* Created by ukanth on 25/7/16.
|
||||
*/
|
||||
public class LogDetailRecyclerViewAdapter extends RecyclerView.Adapter<LogDetailRecyclerViewAdapter.ViewHolder> {
|
||||
|
||||
|
||||
private final List<LogData> logData;
|
||||
private final Context context;
|
||||
private LogData data;
|
||||
private final RecyclerItemClickListener recyclerItemClickListener;
|
||||
|
||||
// Track repeated connection attempts for better UX
|
||||
private final Map<String, Integer> connectionAttempts = new ConcurrentHashMap<>();
|
||||
|
||||
// Cache for expensive operations
|
||||
private final Map<Integer, String> serviceCache = new HashMap<>();
|
||||
private final Map<String, String> interfaceNameCache = new HashMap<>();
|
||||
|
||||
|
||||
public LogDetailRecyclerViewAdapter(final Context context, RecyclerItemClickListener recyclerItemClickListener) {
|
||||
this.context = context;
|
||||
logData = new ArrayList<>();
|
||||
this.recyclerItemClickListener = recyclerItemClickListener;
|
||||
}
|
||||
|
||||
public void updateData(List<LogData> logDataList) {
|
||||
logData.clear();
|
||||
logData.addAll(logDataList);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
|
||||
View mView = LayoutInflater.from(parent.getContext()).inflate(R.layout.logdetail_recycle_item, parent, false);
|
||||
return new ViewHolder(mView);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(ViewHolder holder, int position) {
|
||||
data = logData.get(position);
|
||||
if (data != null) {
|
||||
holder.bind(logData.get(position), recyclerItemClickListener);
|
||||
|
||||
// Format timestamp with interface info (cached)
|
||||
String timeAndInterface = pretty(data.getTimestamp());
|
||||
if (data.getOut() != null && !data.getOut().isEmpty()) {
|
||||
String interfaceType = getCachedInterfaceDisplayName(data.getOut());
|
||||
timeAndInterface += " via " + interfaceType;
|
||||
}
|
||||
holder.deniedTime.setText(timeAndInterface);
|
||||
|
||||
// Set connection type icon with better logic
|
||||
setConnectionIcon(holder.icon, data.getOut());
|
||||
|
||||
// Format destination with better readability
|
||||
String destination = data.getDst() + ":" + data.getDpt();
|
||||
holder.dataDest.setText(destination);
|
||||
|
||||
// Format source with better readability
|
||||
String source = data.getSrc() + ":" + data.getSpt();
|
||||
holder.dataSrc.setText(source);
|
||||
|
||||
// Format protocol with more context (cached)
|
||||
String protocol = data.getProto();
|
||||
if (protocol != null) {
|
||||
protocol = protocol.toUpperCase();
|
||||
// Add service context for common ports (cached)
|
||||
String serviceInfo = getCachedServiceInfo(data.getDpt(), protocol);
|
||||
if (!serviceInfo.isEmpty()) {
|
||||
protocol += " (" + serviceInfo + ")";
|
||||
}
|
||||
}
|
||||
holder.dataProto.setText(protocol != null ? protocol : "Unknown");
|
||||
|
||||
// Format hostname with better handling
|
||||
if (data.getHostname() != null && !data.getHostname().trim().isEmpty() && !data.getHostname().equals(data.getDst())) {
|
||||
holder.dataHost.setText(data.getHostname());
|
||||
holder.dataHost.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
holder.dataHost.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
// Show packet size if available
|
||||
if (data.getLen() > 0 && holder.packetSize != null) {
|
||||
holder.packetSize.setText(formatBytes(data.getLen()));
|
||||
holder.packetSize.setVisibility(View.VISIBLE);
|
||||
} else if (holder.packetSize != null) {
|
||||
holder.packetSize.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
// Hide block count for now to improve performance - can be re-enabled later
|
||||
if (holder.blockCount != null) {
|
||||
holder.blockCount.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void setConnectionIcon(ImageView icon, String outInterface) {
|
||||
if (outInterface == null || outInterface.isEmpty()) {
|
||||
icon.setImageDrawable(context.getResources().getDrawable(R.drawable.ic_help));
|
||||
return;
|
||||
}
|
||||
|
||||
if (outInterface.contains("lan") || outInterface.startsWith("eth") ||
|
||||
outInterface.startsWith("ra") || outInterface.startsWith("bnep") ||
|
||||
outInterface.contains("wlan") || outInterface.contains("wifi")) {
|
||||
icon.setImageDrawable(context.getResources().getDrawable(R.drawable.ic_wifi));
|
||||
} else if (outInterface.contains("mobile") || outInterface.contains("rmnet") ||
|
||||
outInterface.contains("ccmni") || outInterface.contains("pdp")) {
|
||||
icon.setImageDrawable(context.getResources().getDrawable(R.drawable.ic_mobiledata));
|
||||
} else if (outInterface.contains("tun") || outInterface.contains("ppp")) {
|
||||
icon.setImageDrawable(context.getResources().getDrawable(R.drawable.ic_lan)); // Use lan icon for VPN as fallback
|
||||
} else {
|
||||
icon.setImageDrawable(context.getResources().getDrawable(R.drawable.ic_help)); // Use help icon as fallback
|
||||
}
|
||||
}
|
||||
|
||||
private String getCachedInterfaceDisplayName(String outInterface) {
|
||||
if (outInterface == null || outInterface.isEmpty()) {
|
||||
return "Unknown";
|
||||
}
|
||||
|
||||
// Check cache first
|
||||
if (interfaceNameCache.containsKey(outInterface)) {
|
||||
return interfaceNameCache.get(outInterface);
|
||||
}
|
||||
|
||||
String displayName = getInterfaceDisplayName(outInterface);
|
||||
interfaceNameCache.put(outInterface, displayName);
|
||||
return displayName;
|
||||
}
|
||||
|
||||
private String getInterfaceDisplayName(String outInterface) {
|
||||
if (outInterface == null || outInterface.isEmpty()) {
|
||||
return "Unknown";
|
||||
}
|
||||
|
||||
if (outInterface.contains("lan") || outInterface.startsWith("eth") ||
|
||||
outInterface.startsWith("ra") || outInterface.startsWith("bnep") ||
|
||||
outInterface.contains("wlan") || outInterface.contains("wifi")) {
|
||||
return "Wi-Fi";
|
||||
} else if (outInterface.contains("mobile") || outInterface.contains("rmnet") ||
|
||||
outInterface.contains("ccmni") || outInterface.contains("pdp")) {
|
||||
return "Mobile Data";
|
||||
} else if (outInterface.contains("tun") || outInterface.contains("ppp")) {
|
||||
return "VPN";
|
||||
} else {
|
||||
return outInterface;
|
||||
}
|
||||
}
|
||||
|
||||
private String getCachedServiceInfo(int port, String protocol) {
|
||||
int key = (protocol != null ? protocol.hashCode() : 0) * 100000 + port;
|
||||
|
||||
// Check cache first
|
||||
if (serviceCache.containsKey(key)) {
|
||||
return serviceCache.get(key);
|
||||
}
|
||||
|
||||
String serviceInfo = getServiceInfo(port, protocol);
|
||||
serviceCache.put(key, serviceInfo);
|
||||
return serviceInfo;
|
||||
}
|
||||
|
||||
private String getServiceInfo(int port, String protocol) {
|
||||
if ("TCP".equals(protocol)) {
|
||||
switch (port) {
|
||||
case 80: return "HTTP";
|
||||
case 443: return "HTTPS";
|
||||
case 21: return "FTP";
|
||||
case 22: return "SSH";
|
||||
case 23: return "Telnet";
|
||||
case 25: return "SMTP";
|
||||
case 53: return "DNS";
|
||||
case 110: return "POP3";
|
||||
case 143: return "IMAP";
|
||||
case 993: return "IMAPS";
|
||||
case 995: return "POP3S";
|
||||
case 1723: return "PPTP";
|
||||
case 3389: return "RDP";
|
||||
case 5060: return "SIP";
|
||||
case 8080: return "HTTP Alt";
|
||||
}
|
||||
} else if ("UDP".equals(protocol)) {
|
||||
switch (port) {
|
||||
case 53: return "DNS";
|
||||
case 67: return "DHCP Server";
|
||||
case 68: return "DHCP Client";
|
||||
case 123: return "NTP";
|
||||
case 500: return "IPSec";
|
||||
case 1194: return "OpenVPN";
|
||||
case 5060: return "SIP";
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
private String formatBytes(int bytes) {
|
||||
if (bytes < 1024) {
|
||||
return bytes + "B";
|
||||
} else if (bytes < 1024 * 1024) {
|
||||
return String.format("%.1fKB", bytes / 1024.0);
|
||||
} else {
|
||||
return String.format("%.1fMB", bytes / (1024.0 * 1024.0));
|
||||
}
|
||||
}
|
||||
|
||||
public static String pretty(Long timestamp) {
|
||||
return android.text.format.DateFormat.format("dd-MM-yyyy hh:mm:ss", new java.util.Date(timestamp)).toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return logData.size();
|
||||
}
|
||||
|
||||
|
||||
public static class ViewHolder extends RecyclerView.ViewHolder {
|
||||
|
||||
final ImageView icon;
|
||||
final TextView deniedTime;
|
||||
final TextView dataDest;
|
||||
final TextView dataSrc;
|
||||
final TextView dataProto;
|
||||
final TextView dataHost;
|
||||
final TextView packetSize;
|
||||
final TextView blockCount;
|
||||
|
||||
public ViewHolder(View itemView) {
|
||||
super(itemView);
|
||||
icon = itemView.findViewById(R.id.data_icon);
|
||||
deniedTime = itemView.findViewById(R.id.denied_time);
|
||||
dataDest = itemView.findViewById(R.id.data_dest);
|
||||
dataSrc = itemView.findViewById(R.id.data_src);
|
||||
dataProto = itemView.findViewById(R.id.data_proto);
|
||||
dataHost = itemView.findViewById(R.id.data_host);
|
||||
packetSize = itemView.findViewById(R.id.packet_size);
|
||||
blockCount = itemView.findViewById(R.id.block_count);
|
||||
}
|
||||
|
||||
public void bind(final LogData item, final RecyclerItemClickListener listener) {
|
||||
itemView.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
listener.onItemClick(item);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public List<LogData> getLogData() {
|
||||
return logData;
|
||||
}
|
||||
|
||||
}
|
||||
345
app/src/main/java/dev/ukanth/ufirewall/log/LogInfo.java
Normal file
345
app/src/main/java/dev/ukanth/ufirewall/log/LogInfo.java
Normal file
|
|
@ -0,0 +1,345 @@
|
|||
/**
|
||||
* Capture Logs from dmesg and return the formatted string
|
||||
* <p>
|
||||
* <p>
|
||||
* Copyright (C) 2014 Umakanthan Chandran
|
||||
* <p>
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
* <p>
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
* <p>
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* @author Umakanthan Chandran
|
||||
* @version 1.0
|
||||
*/
|
||||
|
||||
package dev.ukanth.ufirewall.log;
|
||||
|
||||
import android.content.Context;
|
||||
import android.net.ConnectivityManager;
|
||||
import android.util.Log;
|
||||
import android.util.SparseArray;
|
||||
|
||||
import org.ocpsoft.prettytime.PrettyTime;
|
||||
import org.ocpsoft.prettytime.TimeUnit;
|
||||
import org.ocpsoft.prettytime.units.JustNow;
|
||||
|
||||
import java.net.InetAddress;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
import dev.ukanth.ufirewall.Api;
|
||||
import dev.ukanth.ufirewall.Api.PackageInfoData;
|
||||
import dev.ukanth.ufirewall.InterfaceTracker;
|
||||
import dev.ukanth.ufirewall.R;
|
||||
import dev.ukanth.ufirewall.util.G;
|
||||
import dev.ukanth.ufirewall.util.UidResolver;
|
||||
import dev.ukanth.ufirewall.util.UidCorrelator;
|
||||
|
||||
public class LogInfo {
|
||||
public String uidString;
|
||||
public String appName;
|
||||
public int uid;
|
||||
public String in;
|
||||
public String out;
|
||||
public String proto;
|
||||
public int spt;
|
||||
public String dst;
|
||||
public int len;
|
||||
public String src;
|
||||
public int dpt;
|
||||
public String host = "";
|
||||
public int type;
|
||||
public long timestamp;
|
||||
int totalBlocked;
|
||||
|
||||
private static PrettyTime prettyTime;
|
||||
|
||||
public static String pretty(Date date) {
|
||||
if (prettyTime == null) {
|
||||
prettyTime = new PrettyTime(new Locale(G.locale()));
|
||||
for (TimeUnit t : prettyTime.getUnits()) {
|
||||
if (t instanceof JustNow) {
|
||||
prettyTime.removeUnit(t);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
prettyTime.setReference(date);
|
||||
return prettyTime.format(new Date(0));
|
||||
}
|
||||
|
||||
private final HashMap<String, Integer> dstBlocked; // Number of packets blocked per destination IP address
|
||||
|
||||
public LogInfo() {
|
||||
this.dstBlocked = new HashMap<String, Integer>();
|
||||
}
|
||||
|
||||
|
||||
public static String parseLog(Context ctx, List<LogData> listLogData) {
|
||||
|
||||
//final BufferedReader r = new BufferedReader(new StringReader(dmesg.toString()));
|
||||
StringBuilder res = new StringBuilder();
|
||||
Integer appid;
|
||||
final SparseArray<LogInfo> map = new SparseArray<LogInfo>();
|
||||
LogInfo loginfo = null;
|
||||
|
||||
try {
|
||||
for (LogData logData : listLogData) {
|
||||
appid = logData.getUid();
|
||||
|
||||
loginfo = map.get(appid);
|
||||
if (loginfo == null) {
|
||||
loginfo = new LogInfo();
|
||||
}
|
||||
|
||||
loginfo.dst = logData.getDst();
|
||||
loginfo.dpt = logData.getDpt();
|
||||
loginfo.spt = logData.getSpt();
|
||||
loginfo.proto = logData.getProto();
|
||||
loginfo.len = logData.getLen();
|
||||
loginfo.src = logData.getSrc();
|
||||
loginfo.out = logData.getOut();
|
||||
map.put(appid, loginfo);
|
||||
loginfo.totalBlocked += 1;
|
||||
String unique = "[" + loginfo.proto + "]" + loginfo.dst + ":" + loginfo.dpt;
|
||||
if (loginfo.dstBlocked.containsKey(unique)) {
|
||||
loginfo.dstBlocked.put(unique, loginfo.dstBlocked.get(unique) + 1);
|
||||
} else {
|
||||
loginfo.dstBlocked.put(unique, 1);
|
||||
}
|
||||
}
|
||||
final List<PackageInfoData> apps = Api.getApps(ctx, null);
|
||||
Integer id;
|
||||
String appName = "";
|
||||
int appId = -1;
|
||||
int totalBlocked;
|
||||
for (int i = 0; i < map.size(); i++) {
|
||||
StringBuilder address = new StringBuilder();
|
||||
id = map.keyAt(i);
|
||||
appName = ""; // Reset for each iteration
|
||||
appId = -1;
|
||||
|
||||
if (id != -1) {
|
||||
// First, try to find in cached app list
|
||||
boolean foundInApps = false;
|
||||
for (PackageInfoData app : apps) {
|
||||
if (app.uid == id) {
|
||||
appId = id;
|
||||
appName = app.names.get(0);
|
||||
foundInApps = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// If not found in apps, use comprehensive UID resolver
|
||||
if (!foundInApps) {
|
||||
appId = id;
|
||||
appName = UidResolver.resolveUid(ctx, id);
|
||||
}
|
||||
} else {
|
||||
appName = ctx.getString(R.string.unknown_item);
|
||||
}
|
||||
loginfo = map.valueAt(i);
|
||||
totalBlocked = loginfo.totalBlocked;
|
||||
if (loginfo.dstBlocked.size() > 0) {
|
||||
for (String unique : loginfo.dstBlocked.keySet()) {
|
||||
address.append(unique).append("(").append(loginfo.dstBlocked.get(unique)).append(")");
|
||||
address.append("\n");
|
||||
}
|
||||
}
|
||||
res.append("AppID :\t");
|
||||
res.append(appId);
|
||||
res.append("\n");
|
||||
res.append(ctx.getString(R.string.LogAppName));
|
||||
res.append(":\t");
|
||||
res.append(appName);
|
||||
res.append("\n");
|
||||
res.append(ctx.getString(R.string.LogPackBlock));
|
||||
res.append(":\t");
|
||||
res.append(totalBlocked);
|
||||
res.append("\n");
|
||||
res.append(address.toString());
|
||||
res.append("\n\t---------\n");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
if (res.length() == 0) {
|
||||
res.append(ctx.getString(R.string.no_log));
|
||||
}
|
||||
return res.toString();
|
||||
}
|
||||
|
||||
|
||||
public static LogInfo parseLogs(String result, final Context ctx, String pattern, int type) {
|
||||
StringBuilder address;
|
||||
int start, end;
|
||||
Integer uid = -100;
|
||||
Integer strUid;
|
||||
String out, src, dst, proto, spt, dpt, len;
|
||||
LogInfo logInfo = new LogInfo();
|
||||
|
||||
HashMap<Integer, String> appNameMap = new HashMap<Integer, String>();
|
||||
final List<PackageInfoData> apps = Api.getApps(ctx, null);
|
||||
|
||||
int pos = 0;
|
||||
try {
|
||||
while ((pos = result.indexOf(pattern, pos)) > -1) {
|
||||
if (result.indexOf(pattern) == -1)
|
||||
continue;
|
||||
|
||||
if (((start = result.indexOf("UID=")) != -1)
|
||||
&& ((end = result.indexOf(" ", start)) != -1)) {
|
||||
strUid = Integer.parseInt(result.substring(start + 4, end));
|
||||
if (strUid != null) {
|
||||
uid = strUid;
|
||||
logInfo.uid = strUid;
|
||||
}
|
||||
}
|
||||
//logInfo = new LogInfo();
|
||||
if (((start = result.indexOf("DST=")) != -1)
|
||||
&& ((end = result.indexOf(" ", start)) != -1)) {
|
||||
dst = result.substring(start + 4, end);
|
||||
logInfo.dst = dst;
|
||||
}
|
||||
|
||||
if (((start = result.indexOf("DPT=")) != -1)
|
||||
&& ((end = result.indexOf(" ", start)) != -1)) {
|
||||
dpt = result.substring(start + 4, end);
|
||||
logInfo.dpt = Integer.parseInt(dpt);
|
||||
}
|
||||
|
||||
if (((start = result.indexOf("SPT=")) != -1)
|
||||
&& ((end = result.indexOf(" ", start)) != -1)) {
|
||||
spt = result.substring(start + 4, end);
|
||||
logInfo.spt = Integer.parseInt(spt);
|
||||
}
|
||||
|
||||
if (((start = result.indexOf("PROTO=")) != -1)
|
||||
&& ((end = result.indexOf(" ", start)) != -1)) {
|
||||
proto = result.substring(start + 6, end);
|
||||
logInfo.proto = proto;
|
||||
}
|
||||
|
||||
if (((start = result.indexOf("LEN=")) != -1)
|
||||
&& ((end = result.indexOf(" ", start)) != -1)) {
|
||||
len = result.substring(start + 4, end);
|
||||
logInfo.len = Integer.parseInt(len);
|
||||
}
|
||||
|
||||
if (((start = result.indexOf("SRC=")) != -1)
|
||||
&& ((end = result.indexOf(" ", start)) != -1)) {
|
||||
src = result.substring(start + 4, end);
|
||||
logInfo.src = src;
|
||||
}
|
||||
|
||||
if (((start = result.indexOf("OUT=")) != -1)
|
||||
&& ((end = result.indexOf(" ", start)) != -1)) {
|
||||
out = result.substring(start + 4, end);
|
||||
if(out.isEmpty()) {
|
||||
logInfo.out = (InterfaceTracker.getCurrentCfg(ctx,false).netType == ConnectivityManager.TYPE_WIFI ? "eth" : "mobile");
|
||||
} else {
|
||||
logInfo.out = out;
|
||||
}
|
||||
}
|
||||
|
||||
if (uid == android.os.Process.myUid()) {
|
||||
return null;
|
||||
}
|
||||
String appName = "";
|
||||
if(logInfo.proto != null && logInfo.proto.toLowerCase().startsWith("icmp")) {
|
||||
//appName = "ICMP";
|
||||
return null;
|
||||
//logInfo.uid = 0;
|
||||
} else if(uid == -100) {
|
||||
// Attempt enhanced UID correlation before giving up
|
||||
int correlatedUid = UidCorrelator.correlateUid(
|
||||
logInfo.src, logInfo.dst, logInfo.dpt, logInfo.spt,
|
||||
logInfo.proto, System.currentTimeMillis());
|
||||
|
||||
if (correlatedUid != -100) {
|
||||
// Successfully correlated! Update UID and continue with normal processing
|
||||
uid = correlatedUid;
|
||||
logInfo.uid = correlatedUid;
|
||||
Log.d(Api.TAG, "Enhanced correlation resolved UID " + correlatedUid +
|
||||
" for connection to " + logInfo.dst + ":" + logInfo.dpt);
|
||||
} else {
|
||||
// Still unknown after correlation attempt
|
||||
appName = ctx.getString(R.string.unknown_item);
|
||||
logInfo.uid = uid;
|
||||
}
|
||||
}
|
||||
|
||||
// Process the UID (whether original, correlated, or unknown)
|
||||
if (uid != -100) {
|
||||
if (uid < 2000) {
|
||||
appName = Api.getSpecialAppName(uid);
|
||||
if(uid == 1000) {
|
||||
appName = ctx.getString(R.string.android_system);
|
||||
}
|
||||
} else {
|
||||
//system level packages
|
||||
try {
|
||||
if (!appNameMap.containsKey(uid)) {
|
||||
appName = ctx.getPackageManager().getNameForUid(uid);
|
||||
for (PackageInfoData app : apps) {
|
||||
if (app.uid == uid) {
|
||||
appName = app.names.get(0);
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
appName = appNameMap.get(uid);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
//could be kernel
|
||||
Log.e(Api.TAG, "Exception in LogInfo when trying to find name for uid " + uid + "");
|
||||
logInfo.uid = uid;
|
||||
appName = ctx.getString(R.string.unknown_item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
logInfo.appName = appName;
|
||||
address = new StringBuilder();
|
||||
//address.append(ctx.getString(R.string.blocked));
|
||||
//address.append(" ");
|
||||
|
||||
address.append(appName);
|
||||
address.append("(").append(uid).append(") ");
|
||||
address.append(logInfo.dst);
|
||||
address.append(":");
|
||||
address.append(logInfo.dpt);
|
||||
logInfo.type = type;
|
||||
if (G.showHost()) {
|
||||
try {
|
||||
String add = InetAddress.getByName(logInfo.dst).getHostName();
|
||||
if (add != null) {
|
||||
logInfo.host = add;
|
||||
address.append("(").append(add).append(") ");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
}
|
||||
}
|
||||
address.append("\n");
|
||||
logInfo.timestamp = System.currentTimeMillis();
|
||||
logInfo.uidString = address.toString();
|
||||
return logInfo;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.e(Api.TAG, "Exception in LogService", e);
|
||||
}
|
||||
return logInfo;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,83 @@
|
|||
package dev.ukanth.ufirewall.log;
|
||||
|
||||
import com.raizlabs.android.dbflow.annotation.Column;
|
||||
import com.raizlabs.android.dbflow.annotation.PrimaryKey;
|
||||
import com.raizlabs.android.dbflow.annotation.Table;
|
||||
import com.raizlabs.android.dbflow.structure.BaseModel;
|
||||
|
||||
/**
|
||||
* Created by ukanth on 17/1/16.
|
||||
*/
|
||||
|
||||
@Table(database = LogPreferenceDB.class)
|
||||
public class LogPreference extends BaseModel {
|
||||
@Column
|
||||
@PrimaryKey
|
||||
private int uid;
|
||||
|
||||
@Column
|
||||
private String appName;
|
||||
|
||||
@Column
|
||||
private long skipInterval;
|
||||
|
||||
@Column
|
||||
private boolean skip;
|
||||
|
||||
@Column
|
||||
private long timestamp;
|
||||
|
||||
public boolean isDisable() {
|
||||
return disable;
|
||||
}
|
||||
|
||||
public void setDisable(boolean disable) {
|
||||
this.disable = disable;
|
||||
}
|
||||
|
||||
@Column
|
||||
private boolean disable;
|
||||
|
||||
|
||||
public long getSkipInterval() {
|
||||
return skipInterval;
|
||||
}
|
||||
|
||||
public void setSkipInterval(long skipInterval) {
|
||||
this.skipInterval = skipInterval;
|
||||
}
|
||||
|
||||
public boolean isSkip() {
|
||||
return skip;
|
||||
}
|
||||
|
||||
public void setSkip(boolean skip) {
|
||||
this.skip = skip;
|
||||
}
|
||||
|
||||
|
||||
public int getUid() {
|
||||
return uid;
|
||||
}
|
||||
|
||||
public void setUid(int uid) {
|
||||
this.uid = uid;
|
||||
}
|
||||
|
||||
public String getAppName() {
|
||||
return appName;
|
||||
}
|
||||
|
||||
public void setAppName(String appName) {
|
||||
this.appName = appName;
|
||||
}
|
||||
|
||||
public long getTimestamp() {
|
||||
return timestamp;
|
||||
}
|
||||
|
||||
public void setTimestamp(long timestamp) {
|
||||
this.timestamp = timestamp;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
package dev.ukanth.ufirewall.log;
|
||||
|
||||
import com.raizlabs.android.dbflow.annotation.Database;
|
||||
|
||||
/**
|
||||
* Created by ukanth on 17/1/16.
|
||||
*/
|
||||
|
||||
@Database(name = LogPreferenceDB.NAME, version = LogPreferenceDB.VERSION)
|
||||
public class LogPreferenceDB {
|
||||
|
||||
public static final String NAME = "LogPreference";
|
||||
|
||||
public static final int VERSION = 1;
|
||||
}
|
||||
|
|
@ -0,0 +1,127 @@
|
|||
package dev.ukanth.ufirewall.log;
|
||||
|
||||
import static dev.ukanth.ufirewall.Api.TAG;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.pm.PackageInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import org.ocpsoft.prettytime.PrettyTime;
|
||||
import org.ocpsoft.prettytime.TimeUnit;
|
||||
import org.ocpsoft.prettytime.units.JustNow;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
import dev.ukanth.ufirewall.Api;
|
||||
import dev.ukanth.ufirewall.R;
|
||||
import dev.ukanth.ufirewall.util.G;
|
||||
|
||||
/**
|
||||
* Created by ukanth on 25/7/16.
|
||||
*/
|
||||
public class LogRecyclerViewAdapter extends RecyclerView.Adapter<LogRecyclerViewAdapter.ViewHolder> {
|
||||
|
||||
|
||||
private final List<LogData> logData;
|
||||
private final Context context;
|
||||
private LogData data;
|
||||
private PackageInfo info;
|
||||
private static PrettyTime prettyTime;
|
||||
private final RecyclerItemClickListener recyclerItemClickListener;
|
||||
private View mView;
|
||||
|
||||
public LogRecyclerViewAdapter(final Context context, RecyclerItemClickListener recyclerItemClickListener) {
|
||||
this.context = context;
|
||||
logData = new ArrayList<>();
|
||||
this.recyclerItemClickListener = recyclerItemClickListener;
|
||||
}
|
||||
|
||||
public void updateData(List<LogData> logDataList) {
|
||||
logData.clear();
|
||||
logData.addAll(logDataList);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
|
||||
mView = LayoutInflater.from(parent.getContext()).inflate(R.layout.log_recycle_item, parent, false);
|
||||
return new ViewHolder(mView);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(ViewHolder holder, int position) {
|
||||
data = logData.get(position);
|
||||
holder.bind(logData.get(position),recyclerItemClickListener);
|
||||
try {
|
||||
Drawable applicationIcon = Api.getApplicationIcon(context, data.getUid());
|
||||
holder.icon.setBackground(applicationIcon);
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, e.getMessage(), e);
|
||||
}
|
||||
|
||||
try {
|
||||
//if(data.getTimestamp() != null && !data.getTimestamp().isEmpty()) {
|
||||
holder.lastDenied.setText(pretty(new Date(System.currentTimeMillis() - data.getTimestamp())));
|
||||
//}
|
||||
} catch (Exception e) {
|
||||
holder.lastDenied.setText("-");
|
||||
}
|
||||
holder.appName.setText(data.getAppName() != null ? data.getAppName() + "(" + data.getUid() + ")" : context.getString(R.string.log_deletedapp));
|
||||
if (data.getCount() > 1) {
|
||||
holder.dataDenied.setText(context.getString(R.string.log_denied) + " " + data.getCount() + " " + context.getString(R.string.log_times));
|
||||
} else {
|
||||
holder.dataDenied.setText(context.getString(R.string.log_denied) + " " + data.getCount() + " " + context.getString(R.string.log_time));
|
||||
}
|
||||
holder.icon.invalidate();
|
||||
}
|
||||
|
||||
public static String pretty(Date date) {
|
||||
if (prettyTime == null) {
|
||||
prettyTime = new PrettyTime(new Locale(G.locale()));
|
||||
for (TimeUnit t : prettyTime.getUnits()) {
|
||||
if (t instanceof JustNow) {
|
||||
prettyTime.removeUnit(t);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
prettyTime.setReference(date);
|
||||
return prettyTime.format(new Date(0));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return logData.size();
|
||||
}
|
||||
|
||||
|
||||
public static class ViewHolder extends RecyclerView.ViewHolder {
|
||||
|
||||
final ImageView icon;
|
||||
final TextView appName;
|
||||
final TextView lastDenied;
|
||||
final TextView dataDenied;
|
||||
|
||||
public ViewHolder(View itemView) {
|
||||
super(itemView);
|
||||
icon = itemView.findViewById(R.id.app_icon);
|
||||
appName = itemView.findViewById(R.id.app_name);
|
||||
lastDenied = itemView.findViewById(R.id.last_denied);
|
||||
dataDenied = itemView.findViewById(R.id.data_denied);
|
||||
}
|
||||
|
||||
public void bind(final LogData item, final RecyclerItemClickListener listener) {
|
||||
itemView.setOnClickListener(v -> listener.onItemClick(item));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
package dev.ukanth.ufirewall.log;
|
||||
|
||||
/**
|
||||
* Created by ukanth on 10/8/16.
|
||||
*/
|
||||
public interface RecyclerItemClickListener {
|
||||
void onItemClick(LogData logData);
|
||||
}
|
||||
|
||||
163
app/src/main/java/dev/ukanth/ufirewall/log/ShellCommand.java
Normal file
163
app/src/main/java/dev/ukanth/ufirewall/log/ShellCommand.java
Normal file
|
|
@ -0,0 +1,163 @@
|
|||
/**
|
||||
*
|
||||
* Shell Command for stream blocked packets information from klogripper (/proc/kmsg)
|
||||
*
|
||||
* Copyright (C) 2014 Umakanthan Chandran
|
||||
*
|
||||
* Originally copied from NetworkLog (C) 2012 Pragmatic Software (MPL2.0)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* @author Umakanthan Chandran
|
||||
* @version 1.0
|
||||
*/
|
||||
|
||||
package dev.ukanth.ufirewall.log;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.InputStreamReader;
|
||||
|
||||
public class ShellCommand {
|
||||
Runtime rt;
|
||||
String[] command;
|
||||
String tag = "";
|
||||
Process process;
|
||||
BufferedReader stdout;
|
||||
public String error;
|
||||
public int exitval;
|
||||
|
||||
public ShellCommand(String[] command, String tag) {
|
||||
this(command);
|
||||
this.tag = tag;
|
||||
}
|
||||
|
||||
public ShellCommand(String[] command) {
|
||||
this.command = command;
|
||||
rt = Runtime.getRuntime();
|
||||
}
|
||||
|
||||
public void start(boolean waitForExit) {
|
||||
exitval = -1;
|
||||
error = null;
|
||||
|
||||
try {
|
||||
process = new ProcessBuilder().command(command).redirectErrorStream(true).start();
|
||||
stdout = new BufferedReader(new InputStreamReader(process.getInputStream()));
|
||||
} catch (Exception e) {
|
||||
error = e.getCause().getMessage();
|
||||
return;
|
||||
}
|
||||
|
||||
if (waitForExit) {
|
||||
waitForExit();
|
||||
}
|
||||
}
|
||||
|
||||
public void waitForExit() {
|
||||
while (!checkForExit()) {
|
||||
if (stdoutAvailable()) {
|
||||
Log.d("AFWALL", "ShellCommand waitForExit [" + tag
|
||||
+ "] discarding read: " + readStdout());
|
||||
} else {
|
||||
try {
|
||||
Thread.sleep(100);
|
||||
} catch (Exception e) {
|
||||
Log.d("AFWall", "waitForExit", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void finish() {
|
||||
try {
|
||||
if (stdout != null) {
|
||||
stdout.close();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.e("AFWall", "Exception finishing [" + tag + "]", e);
|
||||
}
|
||||
|
||||
if(process !=null) {
|
||||
process.destroy();
|
||||
}
|
||||
process = null;
|
||||
}
|
||||
|
||||
public boolean checkForExit() {
|
||||
try {
|
||||
if(process != null) {
|
||||
exitval = process.exitValue();
|
||||
} else {
|
||||
finish();
|
||||
}
|
||||
} catch (IllegalThreadStateException e) {
|
||||
return false;
|
||||
}
|
||||
|
||||
finish();
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean stdoutAvailable() {
|
||||
try {
|
||||
return stdout.ready();
|
||||
} catch (java.io.IOException e) {
|
||||
Log.e("AFWall", "stdoutAvailable error", e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public String readStdoutBlocking() {
|
||||
String line;
|
||||
if (stdout == null) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
line = stdout.readLine();
|
||||
} catch (Exception e) {
|
||||
Log.e("AFWall", "readStdoutBlocking error", e);
|
||||
return null;
|
||||
}
|
||||
if (line == null) {
|
||||
return null;
|
||||
} else {
|
||||
return line + "\n";
|
||||
}
|
||||
}
|
||||
|
||||
public String readStdout() {
|
||||
|
||||
if (stdout == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
if (stdout.ready()) {
|
||||
String line = stdout.readLine();
|
||||
if (line == null) {
|
||||
return null;
|
||||
} else {
|
||||
return line + "\n";
|
||||
}
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.e("AFWall", "readStdout error", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,77 @@
|
|||
/*
|
||||
* Copyright 2012 two forty four a.m. LLC <http://www.twofortyfouram.com>
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in
|
||||
* compliance with the License. You may obtain a copy of the License at
|
||||
* <http://www.apache.org/licenses/LICENSE-2.0>
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License is
|
||||
* distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and limitations under the License.
|
||||
*/
|
||||
|
||||
package dev.ukanth.ufirewall.plugin;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
|
||||
/**
|
||||
* Helper class to scrub Bundles of invalid extras. This is a workaround for an Android bug:
|
||||
* <http://code.google.com/p/android/issues/detail?id=16006>.
|
||||
*/
|
||||
public final class BundleScrubber
|
||||
{
|
||||
|
||||
/**
|
||||
* Scrubs Intents for private serializable subclasses in the Intent extras. If the Intent's extras contain
|
||||
* a private serializable subclass, the Bundle is cleared. The Bundle will not be set to null. If the
|
||||
* Bundle is null, has no extras, or the extras do not contain a private serializable subclass, the Bundle
|
||||
* is not mutated.
|
||||
*
|
||||
* @param intent {@code Intent} to scrub. This parameter may be mutated if scrubbing is necessary. This
|
||||
* parameter may be null.
|
||||
* @return true if the Intent was scrubbed, false if the Intent was not modified.
|
||||
*/
|
||||
public static boolean scrub(final Intent intent)
|
||||
{
|
||||
if (null == intent)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return scrub(intent.getExtras());
|
||||
}
|
||||
|
||||
/**
|
||||
* Scrubs Bundles for private serializable subclasses in the extras. If the Bundle's extras contain a
|
||||
* private serializable subclass, the Bundle is cleared. If the Bundle is null, has no extras, or the
|
||||
* extras do not contain a private serializable subclass, the Bundle is not mutated.
|
||||
*
|
||||
* @param bundle {@code Bundle} to scrub. This parameter may be mutated if scrubbing is necessary. This
|
||||
* parameter may be null.
|
||||
* @return true if the Bundle was scrubbed, false if the Bundle was not modified.
|
||||
*/
|
||||
public static boolean scrub(final Bundle bundle)
|
||||
{
|
||||
if (null == bundle)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
* Note: This is a hack to work around a private serializable classloader attack
|
||||
*/
|
||||
try
|
||||
{
|
||||
// if a private serializable exists, this will throw an exception
|
||||
bundle.containsKey(null);
|
||||
}
|
||||
catch (final Exception e)
|
||||
{
|
||||
bundle.clear();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
301
app/src/main/java/dev/ukanth/ufirewall/plugin/FireReceiver.java
Normal file
301
app/src/main/java/dev/ukanth/ufirewall/plugin/FireReceiver.java
Normal file
|
|
@ -0,0 +1,301 @@
|
|||
/*
|
||||
* Copyright 2012 two forty four a.m. LLC <http://www.twofortyfouram.com>
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in
|
||||
* compliance with the License. You may obtain a copy of the License at
|
||||
* <http://www.apache.org/licenses/LICENSE-2.0>
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License is
|
||||
* distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and limitations under the License.
|
||||
*/
|
||||
|
||||
package dev.ukanth.ufirewall.plugin;
|
||||
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.Message;
|
||||
import android.widget.Toast;
|
||||
|
||||
import dev.ukanth.ufirewall.Api;
|
||||
import dev.ukanth.ufirewall.R;
|
||||
import dev.ukanth.ufirewall.profiles.ProfileData;
|
||||
import dev.ukanth.ufirewall.profiles.ProfileHelper;
|
||||
import dev.ukanth.ufirewall.service.RootCommand;
|
||||
import dev.ukanth.ufirewall.util.G;
|
||||
|
||||
/**
|
||||
* This is the "fire" BroadcastReceiver for a Locale Plug-in setting.
|
||||
*/
|
||||
public final class FireReceiver extends BroadcastReceiver {
|
||||
public static final String TAG = "AFWall";
|
||||
|
||||
/**
|
||||
* @param context {@inheritDoc}.
|
||||
* @param intent the incoming {@link com.twofortyfouram.locale.Intent#ACTION_FIRE_SETTING} Intent. This
|
||||
* should contain the {@link com.twofortyfouram.locale.Intent#EXTRA_BUNDLE} that was saved by
|
||||
* {@link } and later broadcast by Locale.
|
||||
*/
|
||||
@Override
|
||||
public void onReceive(final Context context, final Intent intent) {
|
||||
/*
|
||||
* Always be sure to be strict on input parameters! A malicious third-party app could always send an
|
||||
* empty or otherwise malformed Intent. And since Locale applies settings in the background, the
|
||||
* plug-in definitely shouldn't crash in the background.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Locale guarantees that the Intent action will be ACTION_FIRE_SETTING
|
||||
*/
|
||||
if (!com.twofortyfouram.locale.Intent.ACTION_FIRE_SETTING.equals(intent.getAction())) {
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* A hack to prevent a private serializable classloader attack
|
||||
*/
|
||||
BundleScrubber.scrub(intent);
|
||||
BundleScrubber.scrub(intent.getBundleExtra(com.twofortyfouram.locale.Intent.EXTRA_BUNDLE));
|
||||
final Bundle bundle = intent.getBundleExtra(com.twofortyfouram.locale.Intent.EXTRA_BUNDLE);
|
||||
|
||||
/*
|
||||
* Final verification of the plug-in Bundle before firing the setting.
|
||||
*/
|
||||
if (PluginBundleManager.isBundleValid(bundle)) {
|
||||
String index = bundle.getString(PluginBundleManager.BUNDLE_EXTRA_STRING_MESSAGE);
|
||||
String name = null;
|
||||
if (index.contains("::")) {
|
||||
String[] msg = index.split("::");
|
||||
index = msg[0];
|
||||
name = msg[1];
|
||||
}
|
||||
final boolean multimode = G.enableMultiProfile();
|
||||
final boolean disableToasts = G.disableTaskerToast();
|
||||
if (!G.isProfileMigrated()) {
|
||||
if (index != null) {
|
||||
//int id = Integer.parseInt(index);
|
||||
switch (index) {
|
||||
case "0":
|
||||
Api.applySavedIptablesRules(context, false, new RootCommand()
|
||||
.setFailureToast(R.string.error_apply)
|
||||
.setCallback(new RootCommand.Callback() {
|
||||
@Override
|
||||
public void cbFunc(RootCommand state) {
|
||||
Message msg = new Message();
|
||||
if (state.exitCode == 0) {
|
||||
msg.arg1 = R.string.rules_applied;
|
||||
Api.setEnabled(context, true, false);
|
||||
} else {
|
||||
// error details are already in logcat
|
||||
msg.arg1 = R.string.error_apply;
|
||||
}
|
||||
sendMessage(msg);
|
||||
}
|
||||
}));
|
||||
break;
|
||||
case "1":
|
||||
if (G.protectionLevel().equals("p0")) {
|
||||
Api.purgeIptables(context, true, new RootCommand()
|
||||
.setReopenShell(true)
|
||||
.setCallback(new RootCommand.Callback() {
|
||||
public void cbFunc(RootCommand state) {
|
||||
Message msg = new Message();
|
||||
msg.arg1 = R.string.toast_disabled;
|
||||
sendMessage(msg);
|
||||
Api.setEnabled(context, false, false);
|
||||
}
|
||||
}));
|
||||
} else {
|
||||
Message msg = new Message();
|
||||
msg.arg1 = R.string.widget_disable_fail;
|
||||
sendMessage(msg);
|
||||
}
|
||||
break;
|
||||
case "2":
|
||||
if (multimode) {
|
||||
G.setProfile(true, "AFWallPrefs");
|
||||
}
|
||||
break;
|
||||
case "3":
|
||||
if (multimode) {
|
||||
G.setProfile(true, "AFWallProfile1");
|
||||
}
|
||||
break;
|
||||
case "4":
|
||||
if (multimode) {
|
||||
G.setProfile(true, "AFWallProfile2");
|
||||
}
|
||||
break;
|
||||
case "5":
|
||||
if (multimode) {
|
||||
G.setProfile(true, "AFWallProfile3");
|
||||
}
|
||||
break;
|
||||
default:
|
||||
if (multimode) {
|
||||
G.setProfile(true, name);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (Integer.parseInt(index) > 1) {
|
||||
if (multimode) {
|
||||
if (Api.isEnabled(context)) {
|
||||
if (!disableToasts) {
|
||||
Toast.makeText(context, R.string.tasker_apply, Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
Api.applySavedIptablesRules(context, false, new RootCommand()
|
||||
.setFailureToast(R.string.error_apply)
|
||||
.setCallback(new RootCommand.Callback() {
|
||||
@Override
|
||||
public void cbFunc(RootCommand state) {
|
||||
Message msg = new Message();
|
||||
if (state.exitCode == 0) {
|
||||
msg.arg1 = R.string.tasker_profile_applied;
|
||||
if (!disableToasts)
|
||||
sendMessage(msg);
|
||||
} else {
|
||||
// error details are already in logcat
|
||||
msg.arg1 = R.string.error_apply;
|
||||
}
|
||||
sendMessage(msg);
|
||||
}
|
||||
}));
|
||||
} else {
|
||||
Message msg = new Message();
|
||||
msg.arg1 = R.string.tasker_disabled;
|
||||
sendMessage(msg);
|
||||
}
|
||||
} else {
|
||||
Message msg = new Message();
|
||||
msg.arg1 = R.string.tasker_muliprofile;
|
||||
sendMessage(msg);
|
||||
}
|
||||
G.reloadPrefs();
|
||||
/*if (G.activeNotification()) {
|
||||
Api.showNotification(Api.isEnabled(context), context);
|
||||
}*/
|
||||
Api.updateNotification(Api.isEnabled(context), context);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (index != null) {
|
||||
//int id = Integer.parseInt(index);
|
||||
switch (index) {
|
||||
case "0":
|
||||
Api.applySavedIptablesRules(context, false, new RootCommand()
|
||||
.setFailureToast(R.string.error_apply)
|
||||
.setCallback(new RootCommand.Callback() {
|
||||
@Override
|
||||
public void cbFunc(RootCommand state) {
|
||||
Message msg = new Message();
|
||||
if (state.exitCode == 0) {
|
||||
msg.arg1 = R.string.rules_applied;
|
||||
Api.setEnabled(context, true, false);
|
||||
} else {
|
||||
// error details are already in logcat
|
||||
msg.arg1 = R.string.error_apply;
|
||||
}
|
||||
sendMessage(msg);
|
||||
}
|
||||
}));
|
||||
break;
|
||||
case "1":
|
||||
if (G.protectionLevel().equals("p0")) {
|
||||
Api.purgeIptables(context, true, new RootCommand()
|
||||
.setReopenShell(true)
|
||||
.setCallback(new RootCommand.Callback() {
|
||||
public void cbFunc(RootCommand state) {
|
||||
Message msg = new Message();
|
||||
msg.arg1 = R.string.toast_disabled;
|
||||
sendMessage(msg);
|
||||
Api.setEnabled(context, false, false);
|
||||
}
|
||||
}));
|
||||
/* } else {
|
||||
msg.arg1 = R.string.toast_error_disabling;
|
||||
sendMessage(msg);
|
||||
}*/
|
||||
} else {
|
||||
Message msg = new Message();
|
||||
msg.arg1 = R.string.widget_disable_fail;
|
||||
sendMessage(msg);
|
||||
}
|
||||
break;
|
||||
case "2":
|
||||
if (multimode) {
|
||||
G.setProfile(true, "AFWallPrefs");
|
||||
}
|
||||
break;
|
||||
default:
|
||||
if (multimode) {
|
||||
ProfileData data = ProfileHelper.getProfileByName(name);
|
||||
if (data != null) {
|
||||
G.setProfile(true, data.getIdentifier());
|
||||
}
|
||||
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (Integer.parseInt(index) > 1) {
|
||||
if (multimode) {
|
||||
if (Api.isEnabled(context)) {
|
||||
if (!disableToasts) {
|
||||
Toast.makeText(context, R.string.tasker_apply, Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
Api.applySavedIptablesRules(context, false, new RootCommand()
|
||||
.setFailureToast(R.string.error_apply)
|
||||
.setCallback(new RootCommand.Callback() {
|
||||
@Override
|
||||
public void cbFunc(RootCommand state) {
|
||||
Message msg = new Message();
|
||||
if (state.exitCode == 0) {
|
||||
msg.arg1 = R.string.tasker_profile_applied;
|
||||
if (!disableToasts) sendMessage(msg);
|
||||
} else {
|
||||
// error details are already in logcat
|
||||
msg.arg1 = R.string.error_apply;
|
||||
}
|
||||
}
|
||||
}));
|
||||
} else {
|
||||
Message msg = new Message();
|
||||
msg.arg1 = R.string.tasker_disabled;
|
||||
sendMessage(msg);
|
||||
}
|
||||
} else {
|
||||
Message msg = new Message();
|
||||
msg.arg1 = R.string.tasker_muliprofile;
|
||||
sendMessage(msg);
|
||||
}
|
||||
G.reloadPrefs();
|
||||
/* if (G.activeNotification()) {
|
||||
Api.showNotification(Api.isEnabled(context), context);
|
||||
}*/
|
||||
Api.updateNotification(Api.isEnabled(context), context);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void sendMessage(Message msg) {
|
||||
try {
|
||||
new Handler() {
|
||||
public void handleMessage(Message msg) {
|
||||
if (msg.arg1 != 0)
|
||||
Toast.makeText(G.getContext(), msg.arg1, Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
}.sendMessage(msg);
|
||||
}catch (Exception e) {
|
||||
//unable to send toast. but don't crash
|
||||
}
|
||||
}
|
||||
}
|
||||
235
app/src/main/java/dev/ukanth/ufirewall/plugin/LocaleEdit.java
Normal file
235
app/src/main/java/dev/ukanth/ufirewall/plugin/LocaleEdit.java
Normal file
|
|
@ -0,0 +1,235 @@
|
|||
package dev.ukanth.ufirewall.plugin;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.pm.PackageManager.NameNotFoundException;
|
||||
import android.os.Bundle;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.widget.RadioButton;
|
||||
import android.widget.RadioGroup;
|
||||
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import dev.ukanth.ufirewall.R;
|
||||
import dev.ukanth.ufirewall.profiles.ProfileData;
|
||||
import dev.ukanth.ufirewall.profiles.ProfileHelper;
|
||||
import dev.ukanth.ufirewall.util.G;
|
||||
|
||||
public class LocaleEdit extends AppCompatActivity {
|
||||
//public static final String LOCALE_BRIGHTNESS = "dev.ukanth.ufirewall.plugin.LocaleEdit.ACTIVE_PROFLE";
|
||||
|
||||
private boolean mIsCancelled = false;
|
||||
|
||||
private final int CUSTOM_PROFILE_ID = 100;
|
||||
|
||||
protected void onCreate(Bundle paramBundle) {
|
||||
super.onCreate(paramBundle);
|
||||
|
||||
BundleScrubber.scrub(getIntent());
|
||||
BundleScrubber.scrub(getIntent().getBundleExtra(
|
||||
com.twofortyfouram.locale.Intent.EXTRA_BUNDLE));
|
||||
|
||||
setContentView(R.layout.tasker_profile);
|
||||
|
||||
Toolbar toolbar = findViewById(R.id.tasker_toolbar);
|
||||
|
||||
setSupportActionBar(toolbar);
|
||||
|
||||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getApplicationContext());
|
||||
|
||||
RadioButton tasker_enable = findViewById(R.id.tasker_enable);
|
||||
RadioButton tasker_disable = findViewById(R.id.tasker_disable);
|
||||
RadioButton button1 = findViewById(R.id.defaultProfile);
|
||||
|
||||
String name = prefs.getString("default", getString(R.string.defaultProfile));
|
||||
button1.setText(name != null && name.length() == 0 ? getString(R.string.defaultProfile) : name);
|
||||
|
||||
|
||||
if (!G.isProfileMigrated()) {
|
||||
|
||||
RadioGroup profiles = findViewById(R.id.radioProfiles);
|
||||
|
||||
RadioButton button2 = findViewById(R.id.profile1);
|
||||
RadioButton button3 = findViewById(R.id.profile2);
|
||||
RadioButton button4 = findViewById(R.id.profile3);
|
||||
|
||||
List<String> profilesList = G.getAdditionalProfiles();
|
||||
//int textColor = Color.parseColor("#000000");
|
||||
|
||||
int counter = CUSTOM_PROFILE_ID;
|
||||
for (String profile : profilesList) {
|
||||
RadioButton rdbtn = new RadioButton(this);
|
||||
rdbtn.setId(counter++);
|
||||
rdbtn.setText(profile);
|
||||
profiles.addView(rdbtn);
|
||||
}
|
||||
|
||||
name = prefs.getString("profile1", getString(R.string.profile1));
|
||||
button2.setText(name != null && name.length() == 0 ? getString(R.string.profile1) : name);
|
||||
name = prefs.getString("profile2", getString(R.string.profile2));
|
||||
button3.setText(name != null && name.length() == 0 ? getString(R.string.profile2) : name);
|
||||
name = prefs.getString("profile3", getString(R.string.profile3));
|
||||
button4.setText(name != null && name.length() == 0 ? getString(R.string.profile3) : name);
|
||||
|
||||
setupTitleApi11();
|
||||
|
||||
if (null == paramBundle) {
|
||||
final Bundle forwardedBundle = getIntent().getBundleExtra(
|
||||
com.twofortyfouram.locale.Intent.EXTRA_BUNDLE);
|
||||
if (PluginBundleManager.isBundleValid(forwardedBundle)) {
|
||||
String index = forwardedBundle.getString(PluginBundleManager.BUNDLE_EXTRA_STRING_MESSAGE);
|
||||
if (index.contains("::")) {
|
||||
index = index.split("::")[0];
|
||||
}
|
||||
if (index != null) {
|
||||
switch (index) {
|
||||
case "0":
|
||||
tasker_enable.setChecked(true);
|
||||
break;
|
||||
case "1":
|
||||
tasker_disable.setChecked(true);
|
||||
break;
|
||||
case "2":
|
||||
button1.setChecked(true);
|
||||
break;
|
||||
case "3":
|
||||
button2.setChecked(true);
|
||||
break;
|
||||
case "4":
|
||||
button3.setChecked(true);
|
||||
break;
|
||||
case "5":
|
||||
button4.setChecked(true);
|
||||
break;
|
||||
default:
|
||||
int diff = CUSTOM_PROFILE_ID + (Integer.parseInt(index) - 6);
|
||||
RadioButton btn = findViewById(diff);
|
||||
if (btn != null) {
|
||||
btn.setChecked(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
//TODO: lets do it on new way
|
||||
RadioGroup profiles = findViewById(R.id.radioProfiles);
|
||||
//remove the existing profiles
|
||||
RadioButton button2 = findViewById(R.id.profile1);
|
||||
RadioButton button3 = findViewById(R.id.profile2);
|
||||
RadioButton button4 = findViewById(R.id.profile3);
|
||||
profiles.removeView(button2);
|
||||
profiles.removeView(button3);
|
||||
profiles.removeView(button4);
|
||||
|
||||
for (ProfileData data : ProfileHelper.getProfiles()) {
|
||||
if (data != null) {
|
||||
String profile = data.getName();
|
||||
Long id = data.getId();
|
||||
RadioButton rdbtn = new RadioButton(this);
|
||||
rdbtn.setId(id.intValue());
|
||||
rdbtn.setText(profile);
|
||||
profiles.addView(rdbtn);
|
||||
}
|
||||
}
|
||||
|
||||
if (null == paramBundle) {
|
||||
final Bundle forwardedBundle = getIntent().getBundleExtra(
|
||||
com.twofortyfouram.locale.Intent.EXTRA_BUNDLE);
|
||||
if (PluginBundleManager.isBundleValid(forwardedBundle)) {
|
||||
String index = forwardedBundle.getString(PluginBundleManager.BUNDLE_EXTRA_STRING_MESSAGE);
|
||||
if (index.contains("::")) {
|
||||
index = index.split("::")[0];
|
||||
}
|
||||
if (index != null) {
|
||||
switch (index) {
|
||||
case "0":
|
||||
tasker_enable.setChecked(true);
|
||||
break;
|
||||
case "1":
|
||||
tasker_disable.setChecked(true);
|
||||
break;
|
||||
case "2":
|
||||
button1.setChecked(true);
|
||||
break;
|
||||
default:
|
||||
int diff = Integer.parseInt(index);
|
||||
RadioButton btn = findViewById(diff);
|
||||
if (btn != null) {
|
||||
btn.setChecked(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void setupTitleApi11() {
|
||||
CharSequence callingApplicationLabel = null;
|
||||
try {
|
||||
callingApplicationLabel = getPackageManager().getApplicationLabel(
|
||||
getPackageManager().getApplicationInfo(getCallingPackage(),
|
||||
0));
|
||||
} catch (final NameNotFoundException e) {
|
||||
}
|
||||
if (null != callingApplicationLabel) {
|
||||
setTitle(callingApplicationLabel);
|
||||
}
|
||||
}
|
||||
|
||||
protected void onPause() {
|
||||
super.onPause();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(final MenuItem item) {
|
||||
int id = item.getItemId();
|
||||
if(id == android.R.id.home || id == R.id.twofortyfouram_locale_menu_save ) {
|
||||
finish();
|
||||
return true;
|
||||
} else if (id == R.id.twofortyfouram_locale_menu_dontsave) {
|
||||
mIsCancelled = true;
|
||||
finish();
|
||||
return true;
|
||||
}
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(final Menu menu) {
|
||||
super.onCreateOptionsMenu(menu);
|
||||
getMenuInflater().inflate(R.menu.twofortyfouram_locale_help_save_dontsave, menu);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void finish() {
|
||||
if (mIsCancelled) {
|
||||
setResult(RESULT_CANCELED);
|
||||
} else {
|
||||
RadioGroup group = findViewById(R.id.radioProfiles);
|
||||
int selectedId = group.getCheckedRadioButtonId();
|
||||
RadioButton radioButton = findViewById(selectedId);
|
||||
//int id = Integer.parseInt(radioButton.getHint().toString());
|
||||
String action = radioButton.getText().toString();
|
||||
final Intent resultIntent = new Intent();
|
||||
if (!G.isProfileMigrated()) {
|
||||
int idx = group.indexOfChild(radioButton);
|
||||
resultIntent.putExtra(com.twofortyfouram.locale.Intent.EXTRA_BUNDLE, PluginBundleManager.generateBundle(getApplicationContext(), idx + "::" + action));
|
||||
} else {
|
||||
int idx = group.indexOfChild(radioButton);
|
||||
resultIntent.putExtra(com.twofortyfouram.locale.Intent.EXTRA_BUNDLE, PluginBundleManager.generateBundle(getApplicationContext(), idx + "::" + action));
|
||||
}
|
||||
resultIntent.putExtra(com.twofortyfouram.locale.Intent.EXTRA_STRING_BLURB, action);
|
||||
setResult(RESULT_OK, resultIntent);
|
||||
}
|
||||
super.finish();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,92 @@
|
|||
/*
|
||||
* Copyright 2012 two forty four a.m. LLC <http://www.twofortyfouram.com>
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in
|
||||
* compliance with the License. You may obtain a copy of the License at
|
||||
* <http://www.apache.org/licenses/LICENSE-2.0>
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License is
|
||||
* distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and limitations under the License.
|
||||
*/
|
||||
|
||||
package dev.ukanth.ufirewall.plugin;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.text.TextUtils;
|
||||
|
||||
/**
|
||||
* Class for managing the {@link com.twofortyfouram.locale.Intent#EXTRA_BUNDLE} for this plug-in.
|
||||
*/
|
||||
public final class PluginBundleManager
|
||||
{
|
||||
/**
|
||||
* Type: {@code String}.
|
||||
* <p>
|
||||
* String message to display in a Toast message.
|
||||
*/
|
||||
public static final String BUNDLE_EXTRA_STRING_MESSAGE = "dev.ukanth.ufirewall.plugin.APPLY_PROFILE"; //$NON-NLS-1$
|
||||
|
||||
/**
|
||||
* Type: {@code int}
|
||||
* <p>
|
||||
* versionCode of the plug-in that saved the Bundle.
|
||||
*/
|
||||
/*
|
||||
* This extra is not strictly required, however it makes backward and forward compatibility significantly
|
||||
* easier. For example, suppose a bug is found in how some version of the plug-in stored its Bundle. By
|
||||
* having the version, the plug-in can better detect when such bugs occur.
|
||||
*/
|
||||
public static final String BUNDLE_EXTRA_INT_VERSION_CODE = "dev.ukanth.ufirewall.plugin.extra.INT_VERSION_CODE"; //$NON-NLS-1$
|
||||
|
||||
/**
|
||||
* Method to verify the content of the bundle are correct.
|
||||
* <p>
|
||||
* This method will not mutate {@code bundle}.
|
||||
*
|
||||
* @param bundle bundle to verify. May be null, which will always return false.
|
||||
* @return true if the Bundle is valid, false if the bundle is invalid.
|
||||
*/
|
||||
public static boolean isBundleValid(final Bundle bundle)
|
||||
{
|
||||
if (null == bundle)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
* Make sure the expected extras exist
|
||||
*/
|
||||
if (!bundle.containsKey(BUNDLE_EXTRA_STRING_MESSAGE))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
/*
|
||||
* Make sure the extra isn't null or empty
|
||||
*/
|
||||
return !TextUtils.isEmpty(bundle.getString(BUNDLE_EXTRA_STRING_MESSAGE));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param context Application context.
|
||||
* @param message The toast message to be displayed by the plug-in. Cannot be null.
|
||||
* @return A plug-in bundle.
|
||||
*/
|
||||
public static Bundle generateBundle(final Context context, final String message)
|
||||
{
|
||||
final Bundle result = new Bundle();
|
||||
result.putString(BUNDLE_EXTRA_STRING_MESSAGE, message);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Private constructor prevents instantiation
|
||||
*
|
||||
* @throws UnsupportedOperationException because this class cannot be instantiated.
|
||||
*/
|
||||
private PluginBundleManager()
|
||||
{
|
||||
throw new UnsupportedOperationException("This class is non-instantiable"); //$NON-NLS-1$
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
package dev.ukanth.ufirewall.preferences;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.preference.PreferenceFragment;
|
||||
|
||||
import dev.ukanth.ufirewall.R;
|
||||
|
||||
public class CustomBinaryPreferenceFragment extends PreferenceFragment {
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
// Load the preferences from an XML resource
|
||||
addPreferencesFromResource(R.xml.ui_custom_preferences);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,59 @@
|
|||
package dev.ukanth.ufirewall.preferences;
|
||||
|
||||
import com.raizlabs.android.dbflow.annotation.Column;
|
||||
import com.raizlabs.android.dbflow.annotation.PrimaryKey;
|
||||
import com.raizlabs.android.dbflow.annotation.Table;
|
||||
import com.raizlabs.android.dbflow.structure.BaseModel;
|
||||
|
||||
/**
|
||||
* Created by ukanth on 17/1/16.
|
||||
*/
|
||||
|
||||
@Table(database = DefaultConnectionPrefDB.class)
|
||||
public class DefaultConnectionPref extends BaseModel {
|
||||
@Column
|
||||
@PrimaryKey
|
||||
private int uid;
|
||||
|
||||
public int getUid() {
|
||||
return uid;
|
||||
}
|
||||
|
||||
public void setUid(int uid) {
|
||||
this.uid = uid;
|
||||
}
|
||||
|
||||
public String getConnectionType() {
|
||||
return connectionType;
|
||||
}
|
||||
|
||||
public void setConnectionType(String connectionType) {
|
||||
this.connectionType = connectionType;
|
||||
}
|
||||
|
||||
public boolean isState() {
|
||||
return state;
|
||||
}
|
||||
|
||||
public void setState(boolean state) {
|
||||
this.state = state;
|
||||
}
|
||||
|
||||
@Column
|
||||
private String connectionType;
|
||||
|
||||
@Column
|
||||
private boolean state;
|
||||
|
||||
public int getModeType() {
|
||||
return modeType;
|
||||
}
|
||||
|
||||
public void setModeType(int modeType) {
|
||||
this.modeType = modeType;
|
||||
}
|
||||
|
||||
@Column
|
||||
private int modeType;
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
package dev.ukanth.ufirewall.preferences;
|
||||
|
||||
import com.raizlabs.android.dbflow.annotation.Database;
|
||||
/**
|
||||
* Created by ukanth on 17/1/16.
|
||||
*/
|
||||
|
||||
@Database(name = DefaultConnectionPrefDB.NAME, version = DefaultConnectionPrefDB.VERSION)
|
||||
public class DefaultConnectionPrefDB {
|
||||
|
||||
public static final String NAME = "DefaultConnectionPref";
|
||||
|
||||
public static final int VERSION = 1;
|
||||
}
|
||||
|
|
@ -0,0 +1,260 @@
|
|||
package dev.ukanth.ufirewall.preferences;
|
||||
|
||||
import static dev.ukanth.ufirewall.Api.getFixLeakPath;
|
||||
import static dev.ukanth.ufirewall.Api.mountDir;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.PackageManager.NameNotFoundException;
|
||||
import android.os.Bundle;
|
||||
import android.preference.CheckBoxPreference;
|
||||
import android.preference.ListPreference;
|
||||
import android.preference.Preference;
|
||||
import android.preference.PreferenceFragment;
|
||||
|
||||
import com.stericson.roottools.RootTools;
|
||||
import com.topjohnwu.superuser.Shell;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import dev.ukanth.ufirewall.Api;
|
||||
import dev.ukanth.ufirewall.R;
|
||||
import dev.ukanth.ufirewall.service.RootCommand;
|
||||
import dev.ukanth.ufirewall.util.G;
|
||||
|
||||
public class ExpPreferenceFragment extends PreferenceFragment implements
|
||||
OnSharedPreferenceChangeListener {
|
||||
|
||||
private final String[] initDirs = {
|
||||
"/magisk/.core/service.d/",
|
||||
"/sbin/.core/img/.core/service.d/",
|
||||
"/sbin/.magisk/img/.core/service.d/",
|
||||
"/magisk/phh/su.d/",
|
||||
"/data/adb/post-fs-data.d/",
|
||||
"/data/adb/service.d/",
|
||||
"/sbin/.core/img/phh/su.d/",
|
||||
"/su/su.d/",
|
||||
"/system/su.d/",
|
||||
"/system/etc/init.d/",
|
||||
"/etc/init.d/",
|
||||
"/sbin/supersu/su.d",
|
||||
"/data/adb/su/su.d"};
|
||||
|
||||
private final String initScript = "afwallstart";
|
||||
|
||||
@SuppressLint("NewApi")
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
// Load the preferences from an XML resource
|
||||
addPreferencesFromResource(R.xml.experimental_preferences);
|
||||
setupInitDir(findPreference("initPath"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
super.onDestroy();
|
||||
}
|
||||
|
||||
private void setupInitDir(Preference initd) {
|
||||
ListPreference listPreference = (ListPreference) initd;
|
||||
final Context ctx = getActivity().getApplicationContext();
|
||||
listPreference.setOnPreferenceChangeListener((preference, newValue) -> {
|
||||
String selected = newValue.toString();
|
||||
// fix leak enabled - but user trying to change the path
|
||||
if (!selected.equals(G.initPath()) && G.fixLeak()) {
|
||||
deleteFiles(ctx, false);
|
||||
G.initPath(selected);
|
||||
updateFixLeakScript(true);
|
||||
return true;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
Activity activity = getActivity();
|
||||
new Thread(() -> {
|
||||
List<String> listSupportedDir = new ArrayList<>();
|
||||
//going through the list of known initDirectories
|
||||
for (String dir : initDirs) {
|
||||
//path exists
|
||||
if (RootTools.exists(dir, true)) {
|
||||
listSupportedDir.add(dir);
|
||||
}
|
||||
}
|
||||
//some path exists
|
||||
if (listSupportedDir.size() > 0) {
|
||||
String[] entries = listSupportedDir.toArray(new String[0]);
|
||||
activity.runOnUiThread(() -> {
|
||||
listPreference.setEntries(entries);
|
||||
listPreference.setEntryValues(entries);
|
||||
});
|
||||
}
|
||||
}).start();
|
||||
|
||||
if (G.initPath() != null && !G.initPath().isEmpty()) {
|
||||
listPreference.setValue(G.initPath());
|
||||
} else {
|
||||
CheckBoxPreference fixLeakPref = (CheckBoxPreference) findPreference("fixLeak");
|
||||
fixLeakPref.setEnabled(false);
|
||||
}
|
||||
setupFixLeak(findPreference("fixLeak"), this.getActivity().getApplicationContext());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
getPreferenceManager().getSharedPreferences()
|
||||
.registerOnSharedPreferenceChangeListener(this);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPause() {
|
||||
getPreferenceManager().getSharedPreferences()
|
||||
.unregisterOnSharedPreferenceChangeListener(this);
|
||||
super.onPause();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences,
|
||||
String key) {
|
||||
if (key.equals("fixLeak")) {
|
||||
boolean enabled = G.fixLeak();
|
||||
|
||||
Activity activity = getActivity();
|
||||
new Thread(() -> {
|
||||
if (enabled != isFixLeakInstalled()) {
|
||||
activity.runOnUiThread(() -> updateFixLeakScript(enabled));
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
|
||||
if (key.equals("initPath")) {
|
||||
if (G.initPath() != null) {
|
||||
CheckBoxPreference fixPath = (CheckBoxPreference) findPreference("fixLeak");
|
||||
fixPath.setEnabled(true);
|
||||
}
|
||||
}
|
||||
|
||||
if (key.equals("multiUser")) {
|
||||
if (!Api.supportsMultipleUsers(this.getActivity().getApplicationContext())) {
|
||||
CheckBoxPreference multiUserPref = (CheckBoxPreference) findPreference(key);
|
||||
multiUserPref.setChecked(false);
|
||||
} else {
|
||||
Api.setUserOwner(this.getActivity().getApplicationContext());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void setupFixLeak(Preference pref, Context ctx) {
|
||||
if (pref == null) {
|
||||
return;
|
||||
}
|
||||
CheckBoxPreference fixLeakPref = (CheckBoxPreference) pref;
|
||||
|
||||
if (fixLeakPref.isEnabled()) {
|
||||
// gray out the fixLeak preference if the ROM doesn't support init.d
|
||||
updateLeakCheckbox();
|
||||
fixLeakPref.setEnabled(getFixLeakPath(initScript) != null && !isPackageInstalled("com.androguide.universal.init.d", ctx));
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isPackageInstalled(String packagename, Context ctx) {
|
||||
PackageManager pm = ctx.getPackageManager();
|
||||
try {
|
||||
pm.getPackageInfo(packagename, PackageManager.GET_ACTIVITIES);
|
||||
return true;
|
||||
} catch (NameNotFoundException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests whether the fix leak script is installed.
|
||||
*
|
||||
* You should call this from an I/O thread, because current api level does not allow usage of futures.
|
||||
*
|
||||
* @return {@code true} if the fix leak script exists.
|
||||
*/
|
||||
private boolean isFixLeakInstalled() {
|
||||
String path = getFixLeakPath(initScript);
|
||||
return path != null && RootTools.exists(path);
|
||||
}
|
||||
|
||||
private void updateFixLeakScript(final boolean enabled) {
|
||||
Activity activity = getActivity();
|
||||
if (activity != null && isAdded()) {
|
||||
final Context ctx = activity.getApplicationContext();
|
||||
final String srcPath = new File(ctx.getDir("bin", 0), initScript)
|
||||
.getAbsolutePath();
|
||||
new Thread(() -> {
|
||||
String path = G.initPath();
|
||||
if (path != null) {
|
||||
if (enabled) {
|
||||
File f = new File(path);
|
||||
boolean mountable = mountDir(ctx, getFixLeakPath(initScript), "RW");
|
||||
if (mountable) {
|
||||
//make sure it's executable
|
||||
Shell.Result result = Shell.cmd("chmod 755 " + f.getAbsolutePath()).exec();
|
||||
if(result.isSuccess() && RootTools.copyFile(srcPath, (f.getAbsolutePath() + "/" + initScript),
|
||||
true, false)) {
|
||||
Api.sendToastBroadcast(ctx, ctx.getString(R.string.success_initd));
|
||||
mountDir(ctx, getFixLeakPath(initScript), "RO");
|
||||
activity.runOnUiThread(() -> updateLeakCheckbox());
|
||||
}
|
||||
} else {
|
||||
Api.sendToastBroadcast(ctx, ctx.getString(R.string.mount_initd_error));
|
||||
}
|
||||
} else {
|
||||
deleteFiles(ctx, true);
|
||||
}
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
}
|
||||
|
||||
private void updateLeakCheckbox() {
|
||||
Activity activity = getActivity();
|
||||
CheckBoxPreference fixLeakPref = (CheckBoxPreference) findPreference("fixLeak");
|
||||
new Thread(() -> {
|
||||
boolean isFixLeakInstalled = isFixLeakInstalled();
|
||||
activity.runOnUiThread(() -> fixLeakPref.setChecked(isFixLeakInstalled));
|
||||
}).start();
|
||||
}
|
||||
|
||||
|
||||
private void deleteFiles(final Context ctx, final boolean updateCheckbox) {
|
||||
String path = G.initPath();
|
||||
if(path != null) {
|
||||
new Thread(() -> {
|
||||
if (RootTools.exists(path, true)) {
|
||||
final String filePath = path + "/" + initScript;
|
||||
boolean mountable = mountDir(ctx, getFixLeakPath(initScript), "RW");
|
||||
if (mountable) {
|
||||
Shell.Result result = Shell.cmd("rm -f " + filePath).exec();
|
||||
if(result.isSuccess()){
|
||||
Api.sendToastBroadcast(ctx, ctx.getString(R.string.remove_initd));
|
||||
} else{
|
||||
Api.sendToastBroadcast(ctx, ctx.getString(R.string.delete_initd_error));
|
||||
}
|
||||
if (updateCheckbox) {
|
||||
getActivity().runOnUiThread(() -> updateLeakCheckbox());
|
||||
}
|
||||
mountDir(ctx, getFixLeakPath(initScript), "RO");
|
||||
} else {
|
||||
Api.sendToastBroadcast(ctx, ctx.getString(R.string.mount_initd_error));
|
||||
}
|
||||
}
|
||||
}).start();
|
||||
} else {
|
||||
Api.sendToastBroadcast(ctx, ctx.getString(R.string.delete_initd_error));
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,62 @@
|
|||
package dev.ukanth.ufirewall.preferences;
|
||||
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.Bundle;
|
||||
import android.preference.CheckBoxPreference;
|
||||
import android.preference.Preference;
|
||||
import android.preference.PreferenceFragment;
|
||||
import android.util.Log;
|
||||
|
||||
import dev.ukanth.ufirewall.Api;
|
||||
import dev.ukanth.ufirewall.R;
|
||||
import dev.ukanth.ufirewall.util.G;
|
||||
|
||||
public class LanguagePreferenceFragment extends PreferenceFragment implements
|
||||
SharedPreferences.OnSharedPreferenceChangeListener {
|
||||
|
||||
private static CheckBoxPreference checkBoxPreference;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
// Load the preferences from an XML resource
|
||||
addPreferencesFromResource(R.xml.language_preferences);
|
||||
checkXposed(findPreference("fixDownloadManagerLeak"));
|
||||
//checkXposed(findPreference("lockScreenNotification"),this.getActivity().getApplicationContext());
|
||||
}
|
||||
|
||||
public static void checkXposed(Preference pref) {
|
||||
if (pref == null) {
|
||||
return;
|
||||
}
|
||||
checkBoxPreference = (CheckBoxPreference) pref;
|
||||
// gray out the fixDownloadManagerLeak preference if xposed module is not activated
|
||||
Log.i(Api.TAG, "Checking Xposed:" + G.isXposedEnabled() + "");
|
||||
checkBoxPreference.setEnabled(G.isXposedEnabled());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
getPreferenceManager().getSharedPreferences()
|
||||
.registerOnSharedPreferenceChangeListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPause() {
|
||||
getPreferenceManager().getSharedPreferences()
|
||||
.unregisterOnSharedPreferenceChangeListener(this);
|
||||
super.onPause();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
super.onDestroy();
|
||||
checkBoxPreference = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,255 @@
|
|||
package dev.ukanth.ufirewall.preferences;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.preference.CheckBoxPreference;
|
||||
import android.preference.ListPreference;
|
||||
import android.preference.Preference;
|
||||
import android.preference.PreferenceFragment;
|
||||
import android.preference.PreferenceGroup;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.afollestad.materialdialogs.DialogAction;
|
||||
import com.afollestad.materialdialogs.MaterialDialog;
|
||||
|
||||
import dev.ukanth.ufirewall.Api;
|
||||
import dev.ukanth.ufirewall.R;
|
||||
import dev.ukanth.ufirewall.log.Log;
|
||||
import dev.ukanth.ufirewall.service.LogService;
|
||||
import dev.ukanth.ufirewall.service.RootCommand;
|
||||
import dev.ukanth.ufirewall.util.G;
|
||||
|
||||
public class LogPreferenceFragment extends PreferenceFragment {
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
// Load the preferences from an XML resource
|
||||
try {
|
||||
//fix for the mess
|
||||
//G.logPingTimeout(G.logPingTimeout());
|
||||
addPreferencesFromResource(R.xml.log_preferences);
|
||||
// populateLogMessage(findPreference("logDmesg"));
|
||||
// populateAppList(findPreference("block_filter"));
|
||||
setupLogHostname(findPreference("showHostName"));
|
||||
populateLogTarget(findPreference("logTarget"));
|
||||
} catch (ClassCastException c) {
|
||||
Log.i(Api.TAG, c.getMessage());
|
||||
Api.toast(getActivity(), getString(R.string.exception_pref));
|
||||
}
|
||||
}
|
||||
|
||||
private void populateLogTarget(Preference logTarget) {
|
||||
if (logTarget == null) {
|
||||
return;
|
||||
}
|
||||
ListPreference listPreference = (ListPreference) logTarget;
|
||||
if(G.logTargets() != null && G.logTargets().length() > 0) {
|
||||
String [] items = G.logTargets().split(",");
|
||||
if(items != null && items.length > 0) {
|
||||
if (listPreference != null) {
|
||||
listPreference.setEntries(items);
|
||||
listPreference.setEntryValues(items);
|
||||
|
||||
// Add custom listener to intercept preference changes
|
||||
listPreference.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
||||
@Override
|
||||
public boolean onPreferenceChange(Preference preference, Object newValue) {
|
||||
String newLogTarget = (String) newValue;
|
||||
String oldLogTarget = G.logTarget();
|
||||
|
||||
// If it's the same target, allow change immediately
|
||||
if (newLogTarget.equals(oldLogTarget)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Show confirmation dialog and prevent automatic change
|
||||
showLogTargetChangeDialog(oldLogTarget, newLogTarget, listPreference);
|
||||
return false; // Prevent automatic preference change
|
||||
}
|
||||
});
|
||||
}
|
||||
//if there is only one entry
|
||||
if(items.length == 1) {
|
||||
G.logTarget(items[0]);
|
||||
}
|
||||
} else {
|
||||
//no LOG targets
|
||||
((PreferenceGroup) findPreference("logExperimental")).removePreference(listPreference);
|
||||
}
|
||||
} else{
|
||||
//no LOG targets
|
||||
((PreferenceGroup) findPreference("logExperimental")).removePreference(listPreference);
|
||||
}
|
||||
}
|
||||
|
||||
private void setupLogHostname(Preference showHostName) {
|
||||
CheckBoxPreference showHost = (CheckBoxPreference) showHostName;
|
||||
if (G.isDoKey(getActivity()) || G.isDonate()) {
|
||||
showHost.setEnabled(true);
|
||||
}
|
||||
/* if(!Api.isAFWallAllowed((Context) getActivity())){
|
||||
showHost.setChecked(false);
|
||||
}*/
|
||||
}
|
||||
|
||||
/*private void populateLogMessage(Preference logDmesg) {
|
||||
if (logDmesg == null) {
|
||||
return;
|
||||
}
|
||||
ArrayList<String> ar = new ArrayList<String>();
|
||||
ArrayList<String> val = new ArrayList<String>();
|
||||
ar.add("System");
|
||||
val.add("OS");
|
||||
|
||||
ListPreference listPreference = (ListPreference) logDmesg;
|
||||
if (RootTools.isBusyboxAvailable() || !Api.getBusyBoxPath(ctx,false).isEmpty()) {
|
||||
ar.add("Busybox");
|
||||
val.add("BX");
|
||||
}
|
||||
|
||||
if (listPreference != null) {
|
||||
listPreference.setEntries(ar.toArray(new String[0]));
|
||||
listPreference.setEntryValues(val.toArray(new String[0]));
|
||||
}
|
||||
}
|
||||
|
||||
public static int[] convertIntegers(List<Integer> integers) {
|
||||
int[] ret = new int[integers.size()];
|
||||
Iterator<Integer> iterator = integers.iterator();
|
||||
for (int i = 0; i < ret.length; i++) {
|
||||
ret[i] = iterator.next().intValue();
|
||||
}
|
||||
return ret;
|
||||
}*/
|
||||
|
||||
/*private void populateAppList(Preference list) {
|
||||
final ArrayList<CharSequence> entriesList = new ArrayList<CharSequence>();
|
||||
final ArrayList<Integer> entryValuesList = new ArrayList<Integer>();
|
||||
|
||||
List<Api.PackageInfoData> apps = new ArrayList<>();
|
||||
//List<Api.PackageInfoData> apps = Api.getSpecialData(true);
|
||||
|
||||
Api.PackageInfoData info = new Api.PackageInfoData();
|
||||
info.uid = 1020;
|
||||
info.pkgName = "dev.afwall.special.mDNS";
|
||||
info.names = new ArrayList<String>();
|
||||
info.names.add("mDNS");
|
||||
info.appinfo = new ApplicationInfo();
|
||||
//TODO: better way to handle this
|
||||
//manually add mDNS for now
|
||||
if (!apps.contains(info)) {
|
||||
apps.add(info);
|
||||
}
|
||||
|
||||
for (int i = 0; i < apps.size(); i++) {
|
||||
entriesList.add(apps.get(i).toStringWithUID());
|
||||
entryValuesList.add(apps.get(i).uid);
|
||||
}
|
||||
|
||||
list.setOnPreferenceClickListener(preference -> {
|
||||
//open browser or intent here
|
||||
|
||||
MaterialDialog dialog = new MaterialDialog.Builder(getActivity())
|
||||
.title(R.string.filters_apps_title)
|
||||
.itemsIds(convertIntegers(entryValuesList))
|
||||
.items(entriesList)
|
||||
.itemsCallbackMultiChoice(null, (dialog1, which, text) -> {
|
||||
List<Integer> blockedList = new ArrayList<Integer>();
|
||||
for (int i : which) {
|
||||
blockedList.add(entryValuesList.get(i));
|
||||
}
|
||||
G.storeBlockedApps(blockedList);
|
||||
return true;
|
||||
})
|
||||
.positiveText(R.string.OK)
|
||||
.negativeText(R.string.close)
|
||||
.show();
|
||||
|
||||
if (G.readBlockedApps().size() > 0) {
|
||||
dialog.setSelectedIndices(selectItems(entryValuesList));
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
private Integer[] selectItems(ArrayList<Integer> entryValuesList) {
|
||||
List<Integer> items = new ArrayList<>();
|
||||
for (Integer in : G.readBlockedApps()) {
|
||||
if (entryValuesList.contains(in)) {
|
||||
items.add(entryValuesList.indexOf(in));
|
||||
}
|
||||
}
|
||||
return items.toArray(new Integer[0]);
|
||||
}*/
|
||||
|
||||
private void showLogTargetChangeDialog(String oldLogTarget, String newLogTarget, ListPreference listPreference) {
|
||||
new MaterialDialog.Builder(getActivity())
|
||||
.title(R.string.log_target_change_title)
|
||||
.content(getString(R.string.log_target_change_message, oldLogTarget, newLogTarget))
|
||||
.positiveText(R.string.Yes)
|
||||
.negativeText(R.string.Cancel)
|
||||
.onPositive(new MaterialDialog.SingleButtonCallback() {
|
||||
@Override
|
||||
public void onClick(MaterialDialog dialog, DialogAction which) {
|
||||
// Apply the log target change
|
||||
applyLogTargetChange(newLogTarget, listPreference);
|
||||
}
|
||||
})
|
||||
.onNegative(new MaterialDialog.SingleButtonCallback() {
|
||||
@Override
|
||||
public void onClick(MaterialDialog dialog, DialogAction which) {
|
||||
// Do nothing - preference change was already prevented by returning false
|
||||
Log.d("LogPreferenceFragment", "Log target change cancelled by user");
|
||||
}
|
||||
})
|
||||
.show();
|
||||
}
|
||||
|
||||
private void applyLogTargetChange(String newLogTarget, ListPreference listPreference) {
|
||||
Context ctx = getActivity();
|
||||
|
||||
// Set the new log target in preferences
|
||||
G.logTarget(newLogTarget);
|
||||
|
||||
// Update the ListPreference to show the new value
|
||||
listPreference.setValue(newLogTarget);
|
||||
|
||||
// Update log rules
|
||||
Api.updateLogRules(ctx, new RootCommand()
|
||||
.setReopenShell(true)
|
||||
.setSuccessToast(R.string.log_target_success)
|
||||
.setFailureToast(R.string.log_target_fail));
|
||||
|
||||
// Change log target without restarting service
|
||||
changeLogTargetInService(ctx, newLogTarget);
|
||||
}
|
||||
|
||||
/**
|
||||
* Change log target in the running service without restarting it
|
||||
*/
|
||||
private void changeLogTargetInService(Context ctx, String newLogTarget) {
|
||||
Log.i("LogPreferenceFragment", "Changing log target to: " + newLogTarget);
|
||||
|
||||
if (G.enableLogService()) {
|
||||
// Send log target change request to the running service
|
||||
Intent changeIntent = new Intent(ctx, LogService.class);
|
||||
changeIntent.setAction(LogService.ACTION_CHANGE_LOG_TARGET);
|
||||
changeIntent.putExtra(LogService.EXTRA_NEW_LOG_TARGET, newLogTarget);
|
||||
ctx.startService(changeIntent);
|
||||
|
||||
// Show success message after a delay to allow the change to process
|
||||
Handler handler = new Handler(Looper.getMainLooper());
|
||||
handler.postDelayed(() -> {
|
||||
Toast.makeText(ctx, getString(R.string.log_target_changed_success, newLogTarget), Toast.LENGTH_LONG).show();
|
||||
}, 2000);
|
||||
} else {
|
||||
// Service is not running, just update the preference
|
||||
Log.i("LogPreferenceFragment", "Log service disabled, only updating preference");
|
||||
Toast.makeText(ctx, getString(R.string.log_target_changed_success, newLogTarget), Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,80 @@
|
|||
package dev.ukanth.ufirewall.preferences;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.preference.Preference;
|
||||
import android.preference.PreferenceCategory;
|
||||
import android.preference.PreferenceFragment;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.channels.FileChannel;
|
||||
|
||||
import dev.ukanth.ufirewall.Api;
|
||||
import dev.ukanth.ufirewall.R;
|
||||
import dev.ukanth.ufirewall.activity.ProfileActivity;
|
||||
import dev.ukanth.ufirewall.profiles.ProfileHelper;
|
||||
import dev.ukanth.ufirewall.util.G;
|
||||
|
||||
public class MultiProfilePreferenceFragment extends PreferenceFragment {
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
// Load the preferences from an XML resource
|
||||
addPreferencesFromResource(R.xml.profiles_preferences);
|
||||
Preference button = findPreference("manage_profiles");
|
||||
button.setOnPreferenceClickListener(preference -> {
|
||||
//code for what you want it to do
|
||||
startActivity(new Intent(getActivity(), ProfileActivity.class));
|
||||
return true;
|
||||
});
|
||||
|
||||
final PreferenceCategory mCategory = (PreferenceCategory) findPreference("promigrate");
|
||||
final PreferenceCategory mCategory2 = (PreferenceCategory) findPreference("oldprofile_pref");
|
||||
final Preference migrate = findPreference("migrate_profile");
|
||||
if (!G.isProfileMigrated()) {
|
||||
migrate.setOnPreferenceClickListener(preference -> {
|
||||
Context ctx = getActivity();
|
||||
ProfileHelper.migrateProfiles(ctx);
|
||||
if (ctx != null) {
|
||||
Api.toast(getActivity(), ctx.getString(R.string.profile_migrate_msg));
|
||||
mCategory.removePreference(migrate);
|
||||
|
||||
Preference migrate1 = findPreference("profile1");
|
||||
mCategory2.removePreference(migrate1);
|
||||
|
||||
migrate1 = findPreference("profile2");
|
||||
mCategory2.removePreference(migrate1);
|
||||
|
||||
migrate1 = findPreference("profile3");
|
||||
mCategory2.removePreference(migrate1);
|
||||
}
|
||||
return true;
|
||||
});
|
||||
} else {
|
||||
mCategory.removePreference(migrate);
|
||||
|
||||
Preference migrate2 = findPreference("profile1");
|
||||
mCategory2.removePreference(migrate2);
|
||||
|
||||
migrate2 = findPreference("profile2");
|
||||
mCategory2.removePreference(migrate2);
|
||||
|
||||
migrate2 = findPreference("profile3");
|
||||
mCategory2.removePreference(migrate2);
|
||||
}
|
||||
}
|
||||
|
||||
public void copy(File src, File dst) throws IOException {
|
||||
FileInputStream inStream = new FileInputStream(src);
|
||||
FileOutputStream outStream = new FileOutputStream(dst);
|
||||
FileChannel inChannel = inStream.getChannel();
|
||||
FileChannel outChannel = outStream.getChannel();
|
||||
inChannel.transferTo(0, inChannel.size(), outChannel);
|
||||
inStream.close();
|
||||
outStream.close();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,397 @@
|
|||
/**
|
||||
* Preference Interface.
|
||||
* All iptables "communication" is handled by this class.
|
||||
* <p>
|
||||
* Copyright (C) 2011-2012 Umakanthan Chandran
|
||||
* <p>
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
* <p>
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
* <p>
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* @author Umakanthan Chandran
|
||||
* @version 1.0
|
||||
*/
|
||||
|
||||
package dev.ukanth.ufirewall.preferences;
|
||||
|
||||
import android.app.NotificationManager;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.res.Configuration;
|
||||
import android.content.res.Resources;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.preference.PreferenceActivity;
|
||||
import android.preference.PreferenceFragment;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.Log;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.appcompat.widget.AppCompatCheckBox;
|
||||
import androidx.appcompat.widget.AppCompatCheckedTextView;
|
||||
import androidx.appcompat.widget.AppCompatEditText;
|
||||
import androidx.appcompat.widget.AppCompatRadioButton;
|
||||
import androidx.appcompat.widget.AppCompatSpinner;
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import dev.ukanth.ufirewall.Api;
|
||||
import dev.ukanth.ufirewall.R;
|
||||
import dev.ukanth.ufirewall.events.RulesEvent;
|
||||
import dev.ukanth.ufirewall.events.RxEvent;
|
||||
import dev.ukanth.ufirewall.service.LogService;
|
||||
import dev.ukanth.ufirewall.service.RootCommand;
|
||||
import dev.ukanth.ufirewall.util.G;
|
||||
import dev.ukanth.ufirewall.util.SecurityUtil;
|
||||
import io.reactivex.rxjava3.disposables.Disposable;
|
||||
|
||||
public class PreferencesActivity extends PreferenceActivity implements SharedPreferences.OnSharedPreferenceChangeListener {
|
||||
|
||||
private static final boolean ALWAYS_SIMPLE_PREFS = false;
|
||||
private Toolbar mToolBar;
|
||||
|
||||
private RxEvent rxEvent;
|
||||
private Disposable disposable;
|
||||
|
||||
|
||||
|
||||
|
||||
private void initTheme() {
|
||||
switch(G.getSelectedTheme()) {
|
||||
case "D":
|
||||
setTheme(R.style.AppDarkTheme);
|
||||
break;
|
||||
case "L":
|
||||
setTheme(R.style.AppLightTheme);
|
||||
break;
|
||||
case "B":
|
||||
setTheme(R.style.AppBlackTheme);
|
||||
break;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Helper method to determine if the device has an extra-large screen. For
|
||||
* example, 10" tablets are extra-large.
|
||||
*/
|
||||
private static boolean isXLargeTablet(Context context) {
|
||||
return (context.getResources().getConfiguration().screenLayout & Configuration.SCREENLAYOUT_SIZE_MASK) >= Configuration.SCREENLAYOUT_SIZE_XLARGE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether the simplified settings UI should be shown. This is
|
||||
* true if this is forced via {@link #ALWAYS_SIMPLE_PREFS}, or the device
|
||||
* doesn't have newer APIs like {@link PreferenceFragment}, or the device
|
||||
* doesn't have an extra-large screen. In these cases, a single-pane
|
||||
* "simplified" settings UI should be shown.
|
||||
*/
|
||||
private static boolean isSimplePreferences(Context context) {
|
||||
return ALWAYS_SIMPLE_PREFS
|
||||
|| Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB
|
||||
|| !isXLargeTablet(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
// set language
|
||||
Api.updateLanguage(getApplicationContext(), G.locale());
|
||||
initTheme();
|
||||
|
||||
super.onCreate(savedInstanceState);
|
||||
prepareLayout();
|
||||
subscribe();
|
||||
|
||||
Bundle bundle = getIntent().getExtras();
|
||||
if (bundle != null) {
|
||||
Object data = bundle.get("validate");
|
||||
if (data != null) {
|
||||
String check = (String) data;
|
||||
if (check.equals("yes")) {
|
||||
new SecurityUtil(PreferencesActivity.this).passCheck();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void subscribe() {
|
||||
rxEvent = new RxEvent();
|
||||
disposable = rxEvent.subscribe(event -> {
|
||||
if (event instanceof RulesEvent) {
|
||||
ruleChangeApplyRules((RulesEvent) event);
|
||||
} /*else if (event instanceof LogChangeEvent) {
|
||||
logDmesgChangeApplyRules((LogChangeEvent) event);
|
||||
}*/
|
||||
});
|
||||
}
|
||||
|
||||
private void ruleChangeApplyRules(RulesEvent rulesEvent) {
|
||||
final Context context = rulesEvent.ctx;
|
||||
Api.applySavedIptablesRules(context, false, new RootCommand()
|
||||
.setFailureToast(R.string.error_apply)
|
||||
.setCallback(new RootCommand.Callback() {
|
||||
@Override
|
||||
public void cbFunc(RootCommand state) {
|
||||
if (state.exitCode == 0) {
|
||||
Log.i(Api.TAG, "Rules applied successfully during preference change");
|
||||
} else {
|
||||
// error details are already in logcat
|
||||
Log.i(Api.TAG, "Error applying rules during preference change");
|
||||
}
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStart() {
|
||||
super.onStart();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStop() {
|
||||
super.onStop();
|
||||
}
|
||||
|
||||
private void prepareLayout() {
|
||||
ViewGroup root = findViewById(android.R.id.content);
|
||||
View content = root.getChildAt(0);
|
||||
LinearLayout toolbarContainer = (LinearLayout) View.inflate(this, R.layout.activity_prefs, null);
|
||||
|
||||
root.removeAllViews();
|
||||
toolbarContainer.addView(content);
|
||||
root.addView(toolbarContainer);
|
||||
|
||||
mToolBar = toolbarContainer.findViewById(R.id.toolbar);
|
||||
mToolBar.setTitle(getTitle() + " " + getString(R.string.preferences));
|
||||
mToolBar.setNavigationOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
finish();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected void onApplyThemeResource(Resources.Theme theme, int resid, boolean first) {
|
||||
theme.applyStyle(resid, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(String name, Context context, AttributeSet attrs) {
|
||||
// Allow super to try and create a view first
|
||||
final View result = super.onCreateView(name, context, attrs);
|
||||
if (result != null) {
|
||||
return result;
|
||||
}
|
||||
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
|
||||
// If we're running pre-L, we need to 'inject' our tint aware Views in place of the
|
||||
// standard framework versions
|
||||
switch (name) {
|
||||
case "EditText":
|
||||
return new AppCompatEditText(this, attrs);
|
||||
case "Spinner":
|
||||
return new AppCompatSpinner(this, attrs);
|
||||
case "CheckBox":
|
||||
return new AppCompatCheckBox(this, attrs);
|
||||
case "RadioButton":
|
||||
return new AppCompatRadioButton(this, attrs);
|
||||
case "CheckedTextView":
|
||||
return new AppCompatCheckedTextView(this, attrs);
|
||||
}
|
||||
}
|
||||
|
||||
Api.fixFolderPermissionsAsync(context);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
PreferenceManager.getDefaultSharedPreferences(this)
|
||||
.registerOnSharedPreferenceChangeListener(this);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPause() {
|
||||
PreferenceManager.getDefaultSharedPreferences(this)
|
||||
.unregisterOnSharedPreferenceChangeListener(this);
|
||||
super.onPause();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
case android.R.id.home:
|
||||
onBackPressed();
|
||||
return true;
|
||||
}
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isValidFragment(String fragmentName) {
|
||||
// Prevent fragment injection attacks by explicitly allowing only known safe fragments
|
||||
if (fragmentName == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return UIPreferenceFragment.class.getName().equals(fragmentName)
|
||||
|| ThemePreferenceFragment.class.getName().equals(fragmentName)
|
||||
|| RulesPreferenceFragment.class.getName().equals(fragmentName)
|
||||
|| LogPreferenceFragment.class.getName().equals(fragmentName)
|
||||
|| ExpPreferenceFragment.class.getName().equals(fragmentName)
|
||||
|| CustomBinaryPreferenceFragment.class.getName().equals(fragmentName)
|
||||
|| SecPreferenceFragment.class.getName().equals(fragmentName)
|
||||
|| MultiProfilePreferenceFragment.class.getName().equals(fragmentName)
|
||||
|| WidgetPreferenceFragment.class.getName().equals(fragmentName)
|
||||
|| LanguagePreferenceFragment.class.getName().equals(fragmentName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBuildHeaders(List<Header> target) {
|
||||
loadHeadersFromResource(R.xml.preferences_headers, target);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostCreate(Bundle savedInstanceState) {
|
||||
super.onPostCreate(savedInstanceState);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public boolean onIsMultiPane() {
|
||||
return isXLargeTablet(this) && !isSimplePreferences(this);
|
||||
}
|
||||
|
||||
/*public void logDmesgChangeApplyRules(LogChangeEvent logChangeEvent) {
|
||||
if (logChangeEvent != null) {
|
||||
final Context context = logChangeEvent.ctx;
|
||||
final Intent logIntent = new Intent(context, LogService.class);
|
||||
if (G.enableLogService()) {
|
||||
//restart service
|
||||
context.stopService(logIntent);
|
||||
Api.cleanupUid();
|
||||
context.startService(logIntent);
|
||||
} else {
|
||||
//log service disabled
|
||||
context.stopService(logIntent);
|
||||
Api.cleanupUid();
|
||||
}
|
||||
}
|
||||
}*/
|
||||
|
||||
@Override
|
||||
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
|
||||
Context ctx = getApplicationContext();
|
||||
boolean isRefreshRequired = false;
|
||||
|
||||
if (key.equals("showUid") || key.equals("disableIcons") || key.equals("enableVPN")
|
||||
|| key.equals("enableTether")
|
||||
|| key.equals("enableLAN") || key.equals("enableRoam")
|
||||
|| key.equals("locale") || key.equals("showFilter")) {
|
||||
G.reloadProfile();
|
||||
isRefreshRequired = true;
|
||||
}
|
||||
|
||||
if (key.equals("ipt_path") || key.equals("dns_value")) {
|
||||
rxEvent.publish(new RulesEvent("", ctx));
|
||||
}
|
||||
|
||||
/*if (key.equals("logDmesg")) {
|
||||
rxEvent.publish(new LogChangeEvent("", ctx));
|
||||
}*/
|
||||
|
||||
if (key.equals("notification_priority")) {
|
||||
NotificationManager notificationManager = (NotificationManager) ctx.getSystemService(Context.NOTIFICATION_SERVICE);
|
||||
notificationManager.cancelAll();
|
||||
Api.updateNotification(Api.isEnabled(ctx), ctx);
|
||||
}
|
||||
|
||||
if(key.equals("activeNotification")) {
|
||||
boolean enabled = sharedPreferences.getBoolean(key, false);
|
||||
if(!enabled) {
|
||||
NotificationManager notificationManager = (NotificationManager) ctx.getSystemService(Context.NOTIFICATION_SERVICE);
|
||||
notificationManager.cancelAll();
|
||||
} else {
|
||||
Api.updateNotification(Api.isEnabled(ctx), ctx);
|
||||
}
|
||||
}
|
||||
|
||||
if(key.equals("logTarget")) {
|
||||
// Log target changes are now handled by LogPreferenceFragment
|
||||
// This should not be called anymore due to the OnPreferenceChangeListener
|
||||
Log.d("PreferencesActivity", "logTarget preference changed: " + sharedPreferences.getString(key, ""));
|
||||
}
|
||||
if (key.equals("enableLogService")) {
|
||||
if(G.logTarget() !=null && !G.logTarget().trim().isEmpty()) {
|
||||
boolean enabled = sharedPreferences.getBoolean(key, false);
|
||||
if (enabled) {
|
||||
Toast.makeText(getApplicationContext(), getString(R.string.log_service_start), Toast.LENGTH_LONG).show();
|
||||
Intent intent = new Intent(ctx, LogService.class);
|
||||
ctx.stopService(intent);
|
||||
ctx.startService(intent);
|
||||
} else {
|
||||
Toast.makeText(getApplicationContext(), getString(R.string.log_service_stop), Toast.LENGTH_LONG).show();
|
||||
Intent intent = new Intent(ctx, LogService.class);
|
||||
ctx.stopService(intent);
|
||||
}
|
||||
} else{
|
||||
Toast.makeText(getApplicationContext(), getString(R.string.log_service_select), Toast.LENGTH_LONG).show();
|
||||
}
|
||||
}
|
||||
if (key.equals("enableMultiProfile")) {
|
||||
G.reloadProfile();
|
||||
}
|
||||
if (key.equals("theme")) {
|
||||
initTheme();
|
||||
recreate();
|
||||
Intent broadcastIntent = new Intent();
|
||||
broadcastIntent.setAction("dev.ukanth.ufirewall.theme.REFRESH");
|
||||
ctx.sendBroadcast(broadcastIntent);
|
||||
}
|
||||
|
||||
if (isRefreshRequired) {
|
||||
Intent broadcastIntent = new Intent();
|
||||
broadcastIntent.setAction("dev.ukanth.ufirewall.ui.CHECKREFRESH");
|
||||
ctx.sendBroadcast(broadcastIntent);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
if (rxEvent != null && disposable != null) {
|
||||
disposable.dispose();
|
||||
}
|
||||
super.onDestroy();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void attachBaseContext(Context base) {
|
||||
super.attachBaseContext(Api.updateBaseContextLocale(base));
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,307 @@
|
|||
package dev.ukanth.ufirewall.preferences;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.preference.CheckBoxPreference;
|
||||
import android.preference.Preference;
|
||||
import android.preference.PreferenceFragment;
|
||||
import android.preference.SwitchPreference;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import dev.ukanth.ufirewall.Api;
|
||||
import dev.ukanth.ufirewall.R;
|
||||
import dev.ukanth.ufirewall.service.RootCommand;
|
||||
import dev.ukanth.ufirewall.util.G;
|
||||
|
||||
public class RulesPreferenceFragment extends PreferenceFragment implements
|
||||
SharedPreferences.OnSharedPreferenceChangeListener {
|
||||
|
||||
private Context ctx;
|
||||
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
// Load the preferences from an XML resource
|
||||
addPreferencesFromResource(R.xml.rules_preferences);
|
||||
|
||||
try {
|
||||
updateRuleStatus();
|
||||
} catch (Exception e) {
|
||||
}
|
||||
|
||||
//make sure Roaming is disable in Wifi-only Tablets
|
||||
if (!Api.isMobileNetworkSupported(getActivity())) {
|
||||
CheckBoxPreference roamPreference = (CheckBoxPreference) findPreference("enableRoam");
|
||||
roamPreference.setChecked(false);
|
||||
roamPreference.setEnabled(false);
|
||||
} else {
|
||||
CheckBoxPreference roamPreference = (CheckBoxPreference) findPreference("enableRoam");
|
||||
roamPreference.setEnabled(true);
|
||||
}
|
||||
}
|
||||
|
||||
private void updateRuleStatus() {
|
||||
SwitchPreference input_chain = (SwitchPreference) findPreference("input_chain");
|
||||
input_chain.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
||||
@Override
|
||||
public boolean onPreferenceChange(Preference preference, Object o) {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
/* SwitchPreference output_chain = (SwitchPreference) findPreference("output_chain");
|
||||
SwitchPreference forward_chain = (SwitchPreference) findPreference("forward_chain");*/
|
||||
|
||||
SwitchPreference input_chain_v6 = (SwitchPreference) findPreference("input_chain_v6");
|
||||
SwitchPreference output_chain_v6 = (SwitchPreference) findPreference("output_chain_v6");
|
||||
SwitchPreference forward_chain_v6 = (SwitchPreference) findPreference("forward_chain_v6");
|
||||
|
||||
//ipv6 is not enabled
|
||||
if (!G.enableIPv6()) {
|
||||
input_chain_v6.setEnabled(false);
|
||||
output_chain_v6.setEnabled(false);
|
||||
forward_chain_v6.setEnabled(false);
|
||||
}
|
||||
|
||||
Api.getChainStatus(ctx, new RootCommand()
|
||||
.setFailureToast(R.string.error_apply)
|
||||
.setLogging(true)
|
||||
.setCallback(new RootCommand.Callback() {
|
||||
@Override
|
||||
public void cbFunc(RootCommand state) {
|
||||
if (state.exitCode == 0) {
|
||||
StringBuilder result = state.res;
|
||||
if (result != null) {
|
||||
String output = result.toString();
|
||||
|
||||
final String regexIn = "-P INPUT (\\w+)";
|
||||
final String regexOut = "-P OUTPUT (\\w+)";
|
||||
final String regexFwd = "-P FORWARD (\\w+)";
|
||||
final Pattern pattern = Pattern.compile(regexIn);
|
||||
final Pattern pattern2 = Pattern.compile(regexOut);
|
||||
final Pattern pattern3 = Pattern.compile(regexFwd);
|
||||
|
||||
final Matcher matcher = pattern.matcher(output);
|
||||
boolean firstTime = true;
|
||||
while (matcher.find()) {
|
||||
if (firstTime) {
|
||||
G.ipv4Input(matcher.group(1).equals("ACCEPT"));
|
||||
firstTime = false;
|
||||
} else {
|
||||
G.ipv6Input(matcher.group(1).equals("ACCEPT"));
|
||||
}
|
||||
}
|
||||
firstTime = true;
|
||||
final Matcher matcher2 = pattern2.matcher(output);
|
||||
while (matcher2.find()) {
|
||||
if (firstTime) {
|
||||
G.ipv4Output(matcher2.group(1).equals("ACCEPT"));
|
||||
firstTime = false;
|
||||
} else {
|
||||
G.ipv6Output(matcher2.group(1).equals("ACCEPT"));
|
||||
}
|
||||
}
|
||||
firstTime = true;
|
||||
final Matcher matcher3 = pattern3.matcher(output);
|
||||
while (matcher3.find()) {
|
||||
if (firstTime) {
|
||||
G.ipv4Fwd(matcher3.group(1).equals("ACCEPT"));
|
||||
firstTime = false;
|
||||
} else {
|
||||
G.ipv6Fwd(matcher3.group(1).equals("ACCEPT"));
|
||||
}
|
||||
}
|
||||
}
|
||||
getPreferenceScreen().removeAll();
|
||||
addPreferencesFromResource(R.xml.rules_preferences);
|
||||
}
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttach(Context context) {
|
||||
super.onAttach(context);
|
||||
ctx = context;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttach(Activity activity) {
|
||||
super.onAttach(activity);
|
||||
if(Build.VERSION.SDK_INT < Build.VERSION_CODES.M){
|
||||
ctx = activity;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
getPreferenceManager().getSharedPreferences()
|
||||
.registerOnSharedPreferenceChangeListener(this);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPause() {
|
||||
getPreferenceManager().getSharedPreferences()
|
||||
.unregisterOnSharedPreferenceChangeListener(this);
|
||||
super.onPause();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences,
|
||||
String key) {
|
||||
|
||||
if (key.equals("activeRules")) {
|
||||
if (!G.activeRules()) {
|
||||
//disable service when there is no active rules
|
||||
//stopService(new Intent(PreferencesActivity.this, RootShell.class));
|
||||
CheckBoxPreference enableRoam = (CheckBoxPreference) findPreference("enableRoam");
|
||||
enableRoam.setChecked(false);
|
||||
CheckBoxPreference enableLAN = (CheckBoxPreference) findPreference("enableLAN");
|
||||
enableLAN.setChecked(false);
|
||||
CheckBoxPreference enableVPN = (CheckBoxPreference) findPreference("enableVPN");
|
||||
enableVPN.setChecked(false);
|
||||
CheckBoxPreference enableTether = (CheckBoxPreference) findPreference("enableTether");
|
||||
enableTether.setChecked(false);
|
||||
CheckBoxPreference enableTor = (CheckBoxPreference) findPreference("enableTor");
|
||||
enableTor.setChecked(false);
|
||||
|
||||
G.enableRoam(false);
|
||||
G.enableLAN(false);
|
||||
G.enableVPN(false);
|
||||
G.enableTether(false);
|
||||
G.enableTor(false);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
//do chain apply for ipv4
|
||||
switch (key) {
|
||||
case "input_chain": {
|
||||
String rule = "-P INPUT " + (G.ipv4Input() ? "ACCEPT" : "DROP");
|
||||
Api.applyRule(ctx, rule, false, new RootCommand()
|
||||
.setFailureToast(R.string.error_apply)
|
||||
.setCallback(new RootCommand.Callback() {
|
||||
@Override
|
||||
public void cbFunc(RootCommand state) {
|
||||
if (state.exitCode == 0) {
|
||||
} else {
|
||||
}
|
||||
}
|
||||
}));
|
||||
break;
|
||||
}
|
||||
case "output_chain": {
|
||||
String rule = "-P OUTPUT " + (G.ipv4Output() ? "ACCEPT" : "DROP");
|
||||
Api.applyRule(ctx, rule, false, new RootCommand()
|
||||
.setFailureToast(R.string.error_apply)
|
||||
.setCallback(new RootCommand.Callback() {
|
||||
@Override
|
||||
public void cbFunc(RootCommand state) {
|
||||
if (state.exitCode == 0) {
|
||||
} else {
|
||||
}
|
||||
}
|
||||
}));
|
||||
break;
|
||||
}
|
||||
case "forward_chain": {
|
||||
String rule = "-P FORWARD " + (G.ipv4Fwd() ? "ACCEPT" : "DROP");
|
||||
Api.applyRule(ctx, rule, false, new RootCommand()
|
||||
.setFailureToast(R.string.error_apply)
|
||||
.setCallback(new RootCommand.Callback() {
|
||||
@Override
|
||||
public void cbFunc(RootCommand state) {
|
||||
if (state.exitCode == 0) {
|
||||
} else {
|
||||
}
|
||||
}
|
||||
}));
|
||||
break;
|
||||
}
|
||||
case "input_chain_v6": {
|
||||
String rule = "-P INPUT " + (G.ipv6Input() ? "ACCEPT" : "DROP");
|
||||
Api.applyRule(ctx, rule, true, new RootCommand()
|
||||
.setFailureToast(R.string.error_apply)
|
||||
.setCallback(new RootCommand.Callback() {
|
||||
@Override
|
||||
public void cbFunc(RootCommand state) {
|
||||
if (state.exitCode == 0) {
|
||||
} else {
|
||||
}
|
||||
}
|
||||
}));
|
||||
break;
|
||||
}
|
||||
case "output_chain_v6": {
|
||||
String rule = "-P OUTPUT " + (G.ipv6Output() ? "ACCEPT" : "DROP");
|
||||
Api.applyRule(ctx, rule, true, new RootCommand()
|
||||
.setFailureToast(R.string.error_apply)
|
||||
.setCallback(new RootCommand.Callback() {
|
||||
@Override
|
||||
public void cbFunc(RootCommand state) {
|
||||
if (state.exitCode == 0) {
|
||||
} else {
|
||||
}
|
||||
}
|
||||
}));
|
||||
break;
|
||||
}
|
||||
case "forward_chain_v6": {
|
||||
String rule = "-P FORWARD " + (G.ipv6Fwd() ? "ACCEPT" : "DROP");
|
||||
Api.applyRule(ctx, rule, true, new RootCommand()
|
||||
.setFailureToast(R.string.error_apply)
|
||||
.setCallback(new RootCommand.Callback() {
|
||||
@Override
|
||||
public void cbFunc(RootCommand state) {
|
||||
if (state.exitCode == 0) {
|
||||
} else {
|
||||
}
|
||||
}
|
||||
}));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (key.equals("enableIPv6"))
|
||||
|
||||
{
|
||||
File defaultIP6TablesPath = new File("/system/bin/ip6tables");
|
||||
if (!defaultIP6TablesPath.exists()) {
|
||||
G.enableIPv6(false);
|
||||
CheckBoxPreference enable = (CheckBoxPreference) findPreference("enableIPv6");
|
||||
enable.setChecked(false);
|
||||
|
||||
/*CheckBoxPreference block = (CheckBoxPreference) findPreference("blockIPv6");
|
||||
block.setChecked(false);
|
||||
if (ctx != null) {
|
||||
Api.toast(ctx, getString(R.string.ip6unavailable));
|
||||
}*/
|
||||
} else {
|
||||
switch (key) {
|
||||
case "enableIPv6":
|
||||
CheckBoxPreference block = (CheckBoxPreference) findPreference("controlIPv6");
|
||||
block.setChecked(false);
|
||||
break;
|
||||
case "controlIPv6":
|
||||
CheckBoxPreference allow = (CheckBoxPreference) findPreference("enableIPv6");
|
||||
allow.setChecked(false);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (key.equals("controlIPv6")) {
|
||||
CheckBoxPreference allow = (CheckBoxPreference) findPreference("enableIPv6");
|
||||
allow.setChecked(false);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,514 @@
|
|||
package dev.ukanth.ufirewall.preferences;
|
||||
|
||||
import static android.content.Context.FINGERPRINT_SERVICE;
|
||||
import static android.content.Context.KEYGUARD_SERVICE;
|
||||
import static haibison.android.lockpattern.LockPatternActivity.ACTION_COMPARE_PATTERN;
|
||||
import static haibison.android.lockpattern.LockPatternActivity.ACTION_CREATE_PATTERN;
|
||||
import static haibison.android.lockpattern.LockPatternActivity.EXTRA_PATTERN;
|
||||
|
||||
import android.Manifest;
|
||||
import android.annotation.SuppressLint;
|
||||
import android.annotation.TargetApi;
|
||||
import android.app.KeyguardManager;
|
||||
import android.app.admin.DevicePolicyManager;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.res.Resources;
|
||||
import android.hardware.fingerprint.FingerprintManager;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.preference.CheckBoxPreference;
|
||||
import android.preference.ListPreference;
|
||||
import android.preference.Preference;
|
||||
import android.preference.PreferenceCategory;
|
||||
import android.preference.PreferenceFragment;
|
||||
import android.preference.SwitchPreference;
|
||||
import android.text.InputType;
|
||||
import android.util.Log;
|
||||
import android.widget.EditText;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.core.app.ActivityCompat;
|
||||
|
||||
import com.afollestad.materialdialogs.DialogAction;
|
||||
import com.afollestad.materialdialogs.MaterialDialog;
|
||||
|
||||
import dev.ukanth.ufirewall.Api;
|
||||
import dev.ukanth.ufirewall.R;
|
||||
import dev.ukanth.ufirewall.admin.AdminDeviceReceiver;
|
||||
import dev.ukanth.ufirewall.util.FingerprintUtil;
|
||||
import dev.ukanth.ufirewall.util.G;
|
||||
import haibison.android.lockpattern.LockPatternActivity;
|
||||
import haibison.android.lockpattern.utils.AlpSettings;
|
||||
|
||||
public class SecPreferenceFragment extends PreferenceFragment implements
|
||||
OnSharedPreferenceChangeListener {
|
||||
|
||||
private SwitchPreference enableAdminPref;
|
||||
private CheckBoxPreference enableDeviceCheckPref;
|
||||
|
||||
private static final int REQ_CREATE_PATTERN = 9877;
|
||||
private static final int REQ_ENTER_PATTERN = 9755;
|
||||
|
||||
private static final int REQUEST_CODE_ENABLE_ADMIN = 10237; // identifies
|
||||
|
||||
private ComponentName deviceAdmin;
|
||||
private DevicePolicyManager mDPM;
|
||||
|
||||
private Context globalContext = null;
|
||||
|
||||
//private String passOption = "p0";
|
||||
|
||||
public void setupEnableAdmin(Preference pref) {
|
||||
if (pref == null) {
|
||||
return;
|
||||
}
|
||||
enableAdminPref = (SwitchPreference) pref;
|
||||
// query the actual device admin status from the system
|
||||
enableAdminPref.setChecked(mDPM.isAdminActive(deviceAdmin));
|
||||
}
|
||||
|
||||
@SuppressLint("NewApi")
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
// update settings with actual device admin setting
|
||||
mDPM = (DevicePolicyManager) this.getActivity().getSystemService(
|
||||
Context.DEVICE_POLICY_SERVICE);
|
||||
deviceAdmin = new ComponentName(this.getActivity()
|
||||
.getApplicationContext(), AdminDeviceReceiver.class);
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
globalContext = this.getActivity();
|
||||
|
||||
// Load the preferences from an XML resource
|
||||
addPreferencesFromResource(R.xml.security_preferences);
|
||||
|
||||
|
||||
//backward compatibility
|
||||
preSelectListForBackward();
|
||||
|
||||
setupDeviceSecurityCheck(findPreference("enableDeviceCheck"));
|
||||
setupEnableAdmin(findPreference("enableAdmin"));
|
||||
|
||||
//passOption = G.protectionLevel();
|
||||
|
||||
// Hide Fingerprint option if device not support it.
|
||||
if (!FingerprintUtil.isAndroidSupport() || !canUserFingerPrint()) {
|
||||
ListPreference itemList = (ListPreference) findPreference("passSetting");
|
||||
itemList.setEntries(new String[]{
|
||||
getString(R.string.pref_none),
|
||||
getString(R.string.pref_password),
|
||||
getString(R.string.pref_pattern),
|
||||
});
|
||||
itemList.setEntryValues(new String[]{
|
||||
"p0", "p1", "p2"
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void setupDeviceSecurityCheck(Preference pref) {
|
||||
PreferenceCategory mCategory = (PreferenceCategory) findPreference("securitySetting");
|
||||
enableDeviceCheckPref = (CheckBoxPreference) pref;
|
||||
if (Build.VERSION.SDK_INT >= 21) {
|
||||
//only for donate version
|
||||
if ((G.isDoKey(getActivity()) || G.isDonate())) {
|
||||
if (globalContext != null) {
|
||||
KeyguardManager keyguardManager = (KeyguardManager) globalContext.getSystemService(KEYGUARD_SERVICE);
|
||||
//enable only when keyguard has set
|
||||
if (keyguardManager.isKeyguardSecure()) {
|
||||
enableDeviceCheckPref.setEnabled(true);
|
||||
} else {
|
||||
enableDeviceCheckPref.setEnabled(false);
|
||||
enableDeviceCheckPref.setChecked(false);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
enableDeviceCheckPref.setEnabled(false);
|
||||
enableDeviceCheckPref.setChecked(false);
|
||||
}
|
||||
} else {
|
||||
//remove this option for older devices
|
||||
mCategory.removePreference(enableDeviceCheckPref);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void preSelectListForBackward() {
|
||||
|
||||
final ListPreference itemList = (ListPreference) findPreference("passSetting");
|
||||
//remove other option
|
||||
if (Build.VERSION.SDK_INT < 21) {
|
||||
itemList.setEntries(itemList.getEntries());
|
||||
itemList.setEntryValues(itemList.getEntryValues());
|
||||
}
|
||||
if (itemList != null) {
|
||||
switch (G.protectionLevel()) {
|
||||
case "p0":
|
||||
itemList.setValueIndex(0);
|
||||
break;
|
||||
case "p1":
|
||||
itemList.setValueIndex(1);
|
||||
break;
|
||||
case "p2":
|
||||
itemList.setValueIndex(2);
|
||||
break;
|
||||
case "p3":
|
||||
itemList.setValueIndex(3);
|
||||
break;
|
||||
case "Disable":
|
||||
itemList.setValueIndex(0);
|
||||
break;
|
||||
default:
|
||||
itemList.setValueIndex(0);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Set a new password lock
|
||||
*
|
||||
* @param pwd new password (empty to remove the lock)
|
||||
*/
|
||||
private void setPassword(String pwd) {
|
||||
final Resources res = getResources();
|
||||
String msg = "";
|
||||
if (pwd.length() > 0) {
|
||||
String enc = Api.hideCrypt("AFW@LL_P@SSWORD_PR0T3CTI0N", pwd);
|
||||
if (enc != null) {
|
||||
G.profile_pwd(enc);
|
||||
G.isEnc(true);
|
||||
msg = res.getString(R.string.passdefined);
|
||||
}
|
||||
} /*else {
|
||||
G.profile_pwd(pwd);
|
||||
G.isEnc(false);
|
||||
msg = res.getString(R.string.passremoved);
|
||||
}*/
|
||||
Api.toast(getActivity(), msg, Toast.LENGTH_SHORT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Display Password dialog
|
||||
*
|
||||
* @param itemList
|
||||
*/
|
||||
private void showPasswordActivity(final ListPreference itemList) {
|
||||
|
||||
final MaterialDialog.Builder builder = new MaterialDialog.Builder(getActivity());
|
||||
//you should edit this to fit your needs
|
||||
builder.title(getString(R.string.pass_titleset));
|
||||
|
||||
final EditText firstPass = new EditText(getActivity());
|
||||
firstPass.setHint(getString(R.string.enterpass));//optional
|
||||
final EditText secondPass = new EditText(getActivity());
|
||||
secondPass.setHint(getString(R.string.reenterpass));//optional
|
||||
|
||||
//in my example i use TYPE_CLASS_NUMBER for input only numbers
|
||||
firstPass.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD);
|
||||
secondPass.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD);
|
||||
|
||||
LinearLayout lay = new LinearLayout(getActivity());
|
||||
lay.setOrientation(LinearLayout.VERTICAL);
|
||||
lay.addView(firstPass);
|
||||
lay.addView(secondPass);
|
||||
builder.customView(lay, false);
|
||||
builder.autoDismiss(false);
|
||||
builder.positiveText(R.string.set_password);
|
||||
builder.negativeText(R.string.Cancel);
|
||||
|
||||
|
||||
builder.onPositive(new MaterialDialog.SingleButtonCallback() {
|
||||
@Override
|
||||
public void onClick(@NonNull MaterialDialog dialog, @NonNull DialogAction which) {
|
||||
//get the two inputs
|
||||
if (firstPass.getText().toString().equals(secondPass.getText().toString())) {
|
||||
setPassword(firstPass.getText().toString());
|
||||
G.enableDeviceCheck(false);
|
||||
dialog.dismiss();
|
||||
} else {
|
||||
Api.toast(getActivity(), getString(R.string.settings_pwd_not_equal));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
builder.onNegative((dialog, which) -> {
|
||||
itemList.setValueIndex(0);
|
||||
dialog.dismiss();
|
||||
});
|
||||
builder.show();
|
||||
}
|
||||
|
||||
private void showPatternActivity() {
|
||||
Intent intent = new Intent(ACTION_CREATE_PATTERN, null, getActivity(), LockPatternActivity.class);
|
||||
startActivityForResult(intent, REQ_CREATE_PATTERN);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences,
|
||||
String key) {
|
||||
|
||||
if (key.equals("passSetting")) {
|
||||
ListPreference itemList = (ListPreference) findPreference("passSetting");
|
||||
final ListPreference patternMaxTry = (ListPreference) findPreference("patternMax");
|
||||
final CheckBoxPreference stealthMode = (CheckBoxPreference) findPreference("stealthMode");
|
||||
stealthMode.setEnabled(false);
|
||||
patternMaxTry.setEnabled(false);
|
||||
switch (itemList.getValue()) {
|
||||
case "p0":
|
||||
//disable password completly -- add reconfirmation based on current index
|
||||
confirmResetPasswords(itemList);
|
||||
break;
|
||||
case "p1":
|
||||
//use the existing method to protect password
|
||||
showPasswordActivity(itemList);
|
||||
break;
|
||||
case "p2":
|
||||
//use the existing method to protect password
|
||||
showPatternActivity();
|
||||
break;
|
||||
case "p3":
|
||||
if (FingerprintUtil.isAndroidSupport()) {
|
||||
checkFingerprintDeviceSupport();
|
||||
}
|
||||
break;
|
||||
}
|
||||
// check if device support fingerprint,
|
||||
// if so check if one fingerprint already existed at least
|
||||
/* if (FingerprintUtil.isAndroidSupport()) {
|
||||
checkFingerprintDeviceSupport();
|
||||
}*/
|
||||
}
|
||||
if (key.equals("enableAdmin")) {
|
||||
boolean value = G.enableAdmin();
|
||||
if (value) {
|
||||
Log.d("Device Admin Active ?", mDPM.isAdminActive(deviceAdmin) + "");
|
||||
if (!mDPM.isAdminActive(deviceAdmin)) {
|
||||
// Launch the activity to have the user enable our admin.
|
||||
Intent intent = new Intent(
|
||||
DevicePolicyManager.ACTION_ADD_DEVICE_ADMIN);
|
||||
intent.putExtra(DevicePolicyManager.EXTRA_DEVICE_ADMIN,
|
||||
deviceAdmin);
|
||||
intent.putExtra(DevicePolicyManager.EXTRA_ADD_EXPLANATION,
|
||||
getString(R.string.device_admin_desc));
|
||||
startActivityForResult(intent, REQUEST_CODE_ENABLE_ADMIN);
|
||||
}
|
||||
} else {
|
||||
if (mDPM.isAdminActive(deviceAdmin)) {
|
||||
mDPM.removeActiveAdmin(deviceAdmin);
|
||||
Api.toast(this.getActivity().getApplicationContext(),
|
||||
getString(R.string.device_admin_disabled), Toast.LENGTH_LONG);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (key.equals("enableStealthPattern")) {
|
||||
AlpSettings.Display.setStealthMode(this.getActivity().getApplicationContext(),
|
||||
G.enableStealthPattern());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.M)
|
||||
private boolean canUserFingerPrint() {
|
||||
try {
|
||||
KeyguardManager keyguardManager = (KeyguardManager) globalContext.getSystemService(KEYGUARD_SERVICE);
|
||||
FingerprintManager fingerprintManager = (FingerprintManager) globalContext.getSystemService(FINGERPRINT_SERVICE);
|
||||
|
||||
return fingerprintManager.isHardwareDetected() &&
|
||||
ActivityCompat.checkSelfPermission(globalContext, Manifest.permission.USE_FINGERPRINT) == PackageManager.PERMISSION_GRANTED &&
|
||||
fingerprintManager.hasEnrolledFingerprints() &&
|
||||
keyguardManager.isKeyguardSecure();
|
||||
} catch (Exception e) {
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.M)
|
||||
private void checkFingerprintDeviceSupport() {
|
||||
// Initializing both Android Keyguard Manager and Fingerprint Manager
|
||||
KeyguardManager keyguardManager = (KeyguardManager) globalContext.getSystemService(KEYGUARD_SERVICE);
|
||||
FingerprintManager fingerprintManager = (FingerprintManager) globalContext.getSystemService(FINGERPRINT_SERVICE);
|
||||
ListPreference itemList = (ListPreference) findPreference("passSetting");
|
||||
|
||||
// Check whether the device has a Fingerprint sensor.
|
||||
if (!fingerprintManager.isHardwareDetected()) {
|
||||
Api.toast(globalContext, getString(R.string.device_with_no_fingerprint_sensor));
|
||||
itemList.setValueIndex(0);
|
||||
} else {
|
||||
// Checks whether fingerprint permission is set on manifest
|
||||
if (ActivityCompat.checkSelfPermission(globalContext, Manifest.permission.USE_FINGERPRINT) != PackageManager.PERMISSION_GRANTED) {
|
||||
Api.toast(globalContext, getString(R.string.fingerprint_permission_manifest_missing));
|
||||
itemList.setValueIndex(0);
|
||||
} else {
|
||||
// Check whether at least one fingerprint is registered
|
||||
if (!fingerprintManager.hasEnrolledFingerprints()) {
|
||||
Api.toast(globalContext, getString(R.string.register_at_least_one_fingerprint));
|
||||
itemList.setValueIndex(0);
|
||||
} else {
|
||||
// Checks whether lock screen security is enabled or not
|
||||
if (!keyguardManager.isKeyguardSecure()) {
|
||||
Api.toast(globalContext, getString(R.string.lock_screen_not_enabled));
|
||||
itemList.setValueIndex(0);
|
||||
} else {
|
||||
// Anything is ok
|
||||
if (!G.isFingerprintEnabled()) {
|
||||
G.isFingerprintEnabled(true);
|
||||
//make sure we set the index
|
||||
itemList.setValueIndex(3);
|
||||
Api.toast(globalContext, getString(R.string.fingerprint_enabled_successfully));
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Make sure it's verified before remove passwords
|
||||
*
|
||||
* @param itemList
|
||||
*/
|
||||
private void confirmResetPasswords(final ListPreference itemList) {
|
||||
String pattern = G.sPrefs.getString("LockPassword", "");
|
||||
String pwd = G.profile_pwd();
|
||||
if (pwd.length() > 0) {
|
||||
new MaterialDialog.Builder(getActivity()).cancelable(false)
|
||||
.title(R.string.confirmation).autoDismiss(false)
|
||||
.content(R.string.enterpass)
|
||||
.inputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD)
|
||||
.input(R.string.enterpass, R.string.password_empty, new MaterialDialog.InputCallback() {
|
||||
@Override
|
||||
public void onInput(MaterialDialog dialog, CharSequence input) {
|
||||
String pass = input.toString();
|
||||
boolean isAllowed = false;
|
||||
if (G.isEnc()) {
|
||||
String decrypt = Api.unhideCrypt("AFW@LL_P@SSWORD_PR0T3CTI0N", G.profile_pwd());
|
||||
if (decrypt != null) {
|
||||
if (decrypt.equals(pass)) {
|
||||
isAllowed = true;
|
||||
//Api.toast(getActivity(), getString(R.string.wrong_password));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (pass.equals(G.profile_pwd())) {
|
||||
//reset password
|
||||
isAllowed = true;
|
||||
//Api.toast(getActivity(), getString(R.string.wrong_password));
|
||||
}
|
||||
}
|
||||
if (isAllowed) {
|
||||
G.profile_pwd("");
|
||||
G.isEnc(false);
|
||||
itemList.setValueIndex(0);
|
||||
dialog.dismiss();
|
||||
} else {
|
||||
Api.toast(getActivity(), getString(R.string.wrong_password));
|
||||
}
|
||||
}
|
||||
}).show();
|
||||
|
||||
}
|
||||
|
||||
if (pattern.length() > 0) {
|
||||
Intent intent = new Intent(ACTION_COMPARE_PATTERN, null, getActivity(), LockPatternActivity.class);
|
||||
String savedPattern = G.sPrefs.getString("LockPassword", "");
|
||||
intent.putExtra(EXTRA_PATTERN, savedPattern.toCharArray());
|
||||
startActivityForResult(intent, REQ_ENTER_PATTERN);
|
||||
}
|
||||
|
||||
// check if fingerprint enabled and confirm disable by fingerprint itself
|
||||
if (G.isFingerprintEnabled()) {
|
||||
|
||||
final FingerprintUtil.FingerprintDialog dialog;
|
||||
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) {
|
||||
dialog = new FingerprintUtil.FingerprintDialog(globalContext);
|
||||
dialog.setOnFingerprintFailureListener(() -> {
|
||||
itemList.setValueIndex(3);
|
||||
dialog.dismiss();
|
||||
});
|
||||
dialog.setOnFingerprintSuccess(() -> {
|
||||
G.isFingerprintEnabled(false);
|
||||
Api.toast(globalContext, getString(R.string.fingerprint_disabled_successfully));
|
||||
});
|
||||
dialog.show();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
getPreferenceManager().getSharedPreferences()
|
||||
.registerOnSharedPreferenceChangeListener(this);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPause() {
|
||||
getPreferenceManager().getSharedPreferences()
|
||||
.unregisterOnSharedPreferenceChangeListener(this);
|
||||
super.onPause();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
setupEnableAdmin(findPreference("enableAdmin"));
|
||||
switch (requestCode) {
|
||||
|
||||
case REQ_CREATE_PATTERN: {
|
||||
ListPreference itemList = (ListPreference) findPreference("passSetting");
|
||||
if (resultCode == getActivity().RESULT_OK) {
|
||||
char[] pattern = data.getCharArrayExtra(
|
||||
EXTRA_PATTERN);
|
||||
final SharedPreferences.Editor editor = G.sPrefs.edit();
|
||||
editor.putString("LockPassword", new String(pattern));
|
||||
editor.commit();
|
||||
G.enableDeviceCheck(false);
|
||||
//enable
|
||||
if (itemList != null) {
|
||||
final ListPreference patternMaxTry = (ListPreference) findPreference("patternMax");
|
||||
final CheckBoxPreference stealthMode = (CheckBoxPreference) findPreference("stealthMode");
|
||||
if (stealthMode != null) stealthMode.setEnabled(true);
|
||||
if (patternMaxTry != null) patternMaxTry.setEnabled(true);
|
||||
}
|
||||
|
||||
} else {
|
||||
itemList = (ListPreference) findPreference("passSetting");
|
||||
if (itemList != null) {
|
||||
itemList.setValueIndex(0);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case REQ_ENTER_PATTERN: {
|
||||
ListPreference itemList = (ListPreference) findPreference("passSetting");
|
||||
if (resultCode == getActivity().RESULT_OK) {
|
||||
final SharedPreferences.Editor editor = G.sPrefs.edit();
|
||||
editor.putString("LockPassword", "");
|
||||
editor.commit();
|
||||
itemList = (ListPreference) findPreference("passSetting");
|
||||
if (itemList != null) {
|
||||
itemList.setValueIndex(0);
|
||||
}
|
||||
} else {
|
||||
if (itemList != null) {
|
||||
itemList.setValueIndex(2);
|
||||
G.enableDeviceCheck(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
package dev.ukanth.ufirewall.preferences;
|
||||
|
||||
/**
|
||||
* Created by ukanth on 19/7/16.
|
||||
*/
|
||||
public class ShareContract {
|
||||
|
||||
public static final String COLUMN_KEY = "key";
|
||||
public static final String COLUMN_TYPE = "type";
|
||||
public static final String COLUMN_VALUE = "value";
|
||||
|
||||
public static final int TYPE_NULL = 0;
|
||||
public static final int TYPE_STRING = 1;
|
||||
public static final int TYPE_STRING_SET = 2;
|
||||
public static final int TYPE_INT = 3;
|
||||
public static final int TYPE_LONG = 4;
|
||||
public static final int TYPE_FLOAT = 5;
|
||||
public static final int TYPE_BOOLEAN = 6;
|
||||
}
|
||||
|
|
@ -0,0 +1,294 @@
|
|||
package dev.ukanth.ufirewall.preferences;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.database.ContentObserver;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
import android.os.Handler;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.WeakHashMap;
|
||||
|
||||
/**
|
||||
* Created by ukanth on 19/7/16.
|
||||
*/
|
||||
public class SharePreference implements SharedPreferences {
|
||||
|
||||
private final Context mContext;
|
||||
private final Handler mHandler;
|
||||
private final Uri mBaseUri;
|
||||
private final WeakHashMap<OnSharedPreferenceChangeListener, PreferenceContentObserver> mListeners;
|
||||
|
||||
/**
|
||||
* Initializes a new remote preferences object.
|
||||
* You must use the same authority as the preference provider.
|
||||
* Note that if you pass invalid parameter values, the
|
||||
* constructor will complete successfully, but data accesses
|
||||
* will either throw {@link IllegalArgumentException} or return
|
||||
* default values.
|
||||
*
|
||||
* @param context Used to access the preference provider.
|
||||
* @param authority The authority of the preference provider.
|
||||
* @param prefName The name of the preference file to access.
|
||||
*/
|
||||
public SharePreference(Context context, String authority, String prefName) {
|
||||
mContext = context;
|
||||
mHandler = new Handler(context.getMainLooper());
|
||||
mBaseUri = Uri.parse("content://" + authority).buildUpon().appendPath(prefName).build();
|
||||
mListeners = new WeakHashMap<>();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, ?> getAll() {
|
||||
return queryAll();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getString(String key, String defValue) {
|
||||
return (String)querySingle(key, defValue, ShareContract.TYPE_STRING);
|
||||
}
|
||||
|
||||
@Override
|
||||
@TargetApi(11)
|
||||
public Set<String> getStringSet(String key, Set<String> defValues) {
|
||||
return ShareUtils.toStringSet(querySingle(key, defValues, ShareContract.TYPE_STRING_SET));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getInt(String key, int defValue) {
|
||||
return (Integer)querySingle(key, defValue, ShareContract.TYPE_INT);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getLong(String key, long defValue) {
|
||||
return (Long)querySingle(key, defValue, ShareContract.TYPE_LONG);
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getFloat(String key, float defValue) {
|
||||
return (Float)querySingle(key, defValue, ShareContract.TYPE_FLOAT);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getBoolean(String key, boolean defValue) {
|
||||
return (Boolean)querySingle(key, defValue, ShareContract.TYPE_BOOLEAN);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean contains(String key) {
|
||||
return containsKey(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Editor edit() {
|
||||
return new RemotePreferencesEditor();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerOnSharedPreferenceChangeListener(OnSharedPreferenceChangeListener listener) {
|
||||
if (mListeners.containsKey(listener)) return;
|
||||
PreferenceContentObserver observer = new PreferenceContentObserver(listener);
|
||||
mListeners.put(listener, observer);
|
||||
mContext.getContentResolver().registerContentObserver(mBaseUri, true, observer);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unregisterOnSharedPreferenceChangeListener(OnSharedPreferenceChangeListener listener) {
|
||||
PreferenceContentObserver observer = mListeners.remove(listener);
|
||||
if (observer != null) {
|
||||
mContext.getContentResolver().unregisterContentObserver(observer);
|
||||
}
|
||||
}
|
||||
|
||||
private Object querySingle(String key, Object defValue, int expectedType) {
|
||||
Uri uri = mBaseUri.buildUpon().appendPath(key).build();
|
||||
String[] columns = {ShareContract.COLUMN_TYPE, ShareContract.COLUMN_VALUE};
|
||||
Cursor cursor = mContext.getContentResolver().query(uri, columns, null, null, null);
|
||||
try {
|
||||
if (cursor == null || !cursor.moveToFirst() || cursor.getInt(0) == ShareContract.TYPE_NULL) {
|
||||
return defValue;
|
||||
} else if (cursor.getInt(0) != expectedType) {
|
||||
throw new ClassCastException("Preference type mismatch");
|
||||
} else {
|
||||
return getValue(cursor, 0, 1);
|
||||
}
|
||||
} finally {
|
||||
if (cursor != null) {
|
||||
cursor.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Map<String, Object> queryAll() {
|
||||
Uri uri = mBaseUri.buildUpon().appendPath("").build();
|
||||
String[] columns = {ShareContract.COLUMN_KEY, ShareContract.COLUMN_TYPE, ShareContract.COLUMN_VALUE};
|
||||
Cursor cursor = mContext.getContentResolver().query(uri, columns, null, null, null);
|
||||
try {
|
||||
HashMap<String, Object> map = new HashMap<String, Object>(0);
|
||||
if (cursor == null) {
|
||||
return map;
|
||||
}
|
||||
while (cursor.moveToNext()) {
|
||||
String name = cursor.getString(0);
|
||||
map.put(name, getValue(cursor, 1, 2));
|
||||
}
|
||||
return map;
|
||||
} finally {
|
||||
if (cursor != null) {
|
||||
cursor.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean containsKey(String key) {
|
||||
Uri uri = mBaseUri.buildUpon().appendPath(key).build();
|
||||
String[] columns = {ShareContract.COLUMN_TYPE};
|
||||
Cursor cursor = mContext.getContentResolver().query(uri, columns, null, null, null);
|
||||
try {
|
||||
return (cursor != null && cursor.moveToFirst() && cursor.getInt(0) != ShareContract.TYPE_NULL);
|
||||
} finally {
|
||||
if (cursor != null) {
|
||||
cursor.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Object getValue(Cursor cursor, int typeCol, int valueCol) {
|
||||
int expectedType = cursor.getInt(typeCol);
|
||||
switch (expectedType) {
|
||||
case ShareContract.TYPE_STRING:
|
||||
return cursor.getString(valueCol);
|
||||
case ShareContract.TYPE_STRING_SET:
|
||||
return ShareUtils.deserializeStringSet(cursor.getString(valueCol));
|
||||
case ShareContract.TYPE_INT:
|
||||
return cursor.getInt(valueCol);
|
||||
case ShareContract.TYPE_LONG:
|
||||
return cursor.getLong(valueCol);
|
||||
case ShareContract.TYPE_FLOAT:
|
||||
return cursor.getFloat(valueCol);
|
||||
case ShareContract.TYPE_BOOLEAN:
|
||||
return cursor.getInt(valueCol) != 0;
|
||||
default:
|
||||
throw new AssertionError("Invalid expected type: " + expectedType);
|
||||
}
|
||||
}
|
||||
|
||||
private class RemotePreferencesEditor implements Editor {
|
||||
private final List<ContentValues> mToAdd = new ArrayList<ContentValues>();
|
||||
private final Set<String> mToRemove = new HashSet<String>();
|
||||
|
||||
private ContentValues add(String key, int type) {
|
||||
ContentValues values = new ContentValues(3);
|
||||
values.put(ShareContract.COLUMN_KEY, key);
|
||||
values.put(ShareContract.COLUMN_TYPE, type);
|
||||
mToAdd.add(values);
|
||||
return values;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Editor putString(String key, String value) {
|
||||
add(key, ShareContract.TYPE_STRING)
|
||||
.put(ShareContract.COLUMN_VALUE, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
@TargetApi(11)
|
||||
public Editor putStringSet(String key, Set<String> value) {
|
||||
add(key, ShareContract.TYPE_STRING_SET)
|
||||
.put(ShareContract.COLUMN_VALUE, ShareUtils.serializeStringSet(value));
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Editor putInt(String key, int value) {
|
||||
add(key, ShareContract.TYPE_INT)
|
||||
.put(ShareContract.COLUMN_VALUE, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Editor putLong(String key, long value) {
|
||||
add(key, ShareContract.TYPE_LONG)
|
||||
.put(ShareContract.COLUMN_VALUE, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Editor putFloat(String key, float value) {
|
||||
add(key, ShareContract.TYPE_FLOAT)
|
||||
.put(ShareContract.COLUMN_VALUE, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Editor putBoolean(String key, boolean value) {
|
||||
add(key, ShareContract.TYPE_BOOLEAN)
|
||||
.put(ShareContract.COLUMN_VALUE, value ? 1 : 0);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Editor remove(String key) {
|
||||
mToRemove.add(key);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Editor clear() {
|
||||
return remove("");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean commit() {
|
||||
for (String key : mToRemove) {
|
||||
Uri uri = mBaseUri.buildUpon().appendPath(key).build();
|
||||
mContext.getContentResolver().delete(uri, null, null);
|
||||
}
|
||||
ContentValues[] values = mToAdd.toArray(new ContentValues[0]);
|
||||
Uri uri = mBaseUri.buildUpon().appendPath("").build();
|
||||
mContext.getContentResolver().bulkInsert(uri, values);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void apply() {
|
||||
commit();
|
||||
}
|
||||
}
|
||||
|
||||
private class PreferenceContentObserver extends ContentObserver {
|
||||
private final WeakReference<OnSharedPreferenceChangeListener> mListener;
|
||||
|
||||
private PreferenceContentObserver(OnSharedPreferenceChangeListener listener) {
|
||||
super(mHandler);
|
||||
mListener = new WeakReference<OnSharedPreferenceChangeListener>(listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean deliverSelfNotifications() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onChange(boolean selfChange, Uri uri) {
|
||||
String prefKey = uri.getLastPathSegment();
|
||||
OnSharedPreferenceChangeListener listener = mListener.get();
|
||||
if (listener == null) {
|
||||
mContext.getContentResolver().unregisterContentObserver(this);
|
||||
} else {
|
||||
listener.onSharedPreferenceChanged(SharePreference.this, prefKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,256 @@
|
|||
package dev.ukanth.ufirewall.preferences;
|
||||
|
||||
/**
|
||||
* Created by ukanth on 19/7/16.
|
||||
*/
|
||||
|
||||
import android.content.ContentProvider;
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.UriMatcher;
|
||||
import android.database.Cursor;
|
||||
import android.database.MatrixCursor;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import dev.ukanth.ufirewall.BuildConfig;
|
||||
|
||||
/**
|
||||
* Exposes {@link SharedPreferences} to other apps running on the device.
|
||||
*
|
||||
* You must extend this class and declare a default constructor which
|
||||
* calls the super constructor with the appropriate authority and
|
||||
* preference file name parameters. Remember to add your provider to
|
||||
* your AndroidManifest.xml file and set the {@code android:exported}
|
||||
* property to true.
|
||||
*
|
||||
* To access the data from a remote process, use {@link SharedPreferences}
|
||||
* initialized with the same authority and the desired preference file name.
|
||||
*
|
||||
* For granular access control, override {@link #checkAccess(String, String, boolean)}
|
||||
* and return {@code false} to deny the operation.
|
||||
*/
|
||||
public abstract class SharePreferenceProvider extends ContentProvider implements SharedPreferences.OnSharedPreferenceChangeListener {
|
||||
private static final int PREFERENCES_ID = 1;
|
||||
private static final int PREFERENCE_ID = 2;
|
||||
|
||||
public static final String AUTHORITY = BuildConfig.APPLICATION_ID;
|
||||
|
||||
private final Uri mBaseUri;
|
||||
private final String[] mPrefNames;
|
||||
private final Map<String, SharedPreferences> mPreferences;
|
||||
private final UriMatcher mUriMatcher;
|
||||
|
||||
/**
|
||||
* Initializes the remote preference provider with the specified
|
||||
* authority and preference files. The authority must match the
|
||||
* {@code android:authorities} property defined in your manifest
|
||||
* file. Only the specified preference files will be accessible
|
||||
* through the provider.
|
||||
*
|
||||
* @param prefNames The names of the preference files to expose.
|
||||
*/
|
||||
public SharePreferenceProvider(String[] prefNames) {
|
||||
mBaseUri = Uri.parse("content://" + AUTHORITY);
|
||||
mPrefNames = prefNames;
|
||||
mPreferences = new HashMap<String, SharedPreferences>(prefNames.length);
|
||||
mUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
|
||||
mUriMatcher.addURI(AUTHORITY, "*/", PREFERENCES_ID);
|
||||
mUriMatcher.addURI(AUTHORITY, "*/*", PREFERENCE_ID);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreate() {
|
||||
Context context = getContext();
|
||||
for (String prefName : mPrefNames) {
|
||||
SharedPreferences preferences = context.getSharedPreferences(prefName, Context.MODE_PRIVATE);
|
||||
preferences.registerOnSharedPreferenceChangeListener(this);
|
||||
mPreferences.put(prefName, preferences);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
|
||||
PrefNameKeyPair nameKeyPair = parseUri(uri);
|
||||
SharedPreferences preferences = getPreferences(nameKeyPair, false);
|
||||
Map<String, ?> preferenceMap = preferences.getAll();
|
||||
MatrixCursor cursor = new MatrixCursor(projection);
|
||||
if (nameKeyPair.key.length() == 0) {
|
||||
for (Map.Entry<String, ?> entry : preferenceMap.entrySet()) {
|
||||
String key = entry.getKey();
|
||||
Object value = entry.getValue();
|
||||
cursor.addRow(buildRow(projection, key, value));
|
||||
}
|
||||
} else {
|
||||
String key = nameKeyPair.key;
|
||||
Object value = preferenceMap.get(key);
|
||||
cursor.addRow(buildRow(projection, key, value));
|
||||
}
|
||||
return cursor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getType(Uri uri) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Uri insert(Uri uri, ContentValues values) {
|
||||
PrefNameKeyPair nameKeyPair = parseUri(uri);
|
||||
String key = nameKeyPair.key;
|
||||
if (key.length() == 0) {
|
||||
key = values.getAsString(ShareContract.COLUMN_KEY);
|
||||
}
|
||||
int type = values.getAsInteger(ShareContract.COLUMN_TYPE);
|
||||
Object value = ShareUtils.deserialize(values.get(ShareContract.COLUMN_VALUE), type);
|
||||
SharedPreferences preferences = getPreferences(nameKeyPair, true);
|
||||
SharedPreferences.Editor editor = preferences.edit();
|
||||
if (value == null) {
|
||||
throw new IllegalArgumentException("Attempting to insert preference with null value");
|
||||
} else if (value instanceof String) {
|
||||
editor.putString(key, (String)value);
|
||||
} else if (value instanceof Set<?>) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
|
||||
editor.putStringSet(key, ShareUtils.toStringSet(value));
|
||||
} else {
|
||||
throw new IllegalArgumentException("String set preferences not supported on API < 11");
|
||||
}
|
||||
} else if (value instanceof Integer) {
|
||||
editor.putInt(key, (Integer)value);
|
||||
} else if (value instanceof Long) {
|
||||
editor.putLong(key, (Long)value);
|
||||
} else if (value instanceof Float) {
|
||||
editor.putFloat(key, (Float)value);
|
||||
} else if (value instanceof Boolean) {
|
||||
editor.putBoolean(key, (Boolean)value);
|
||||
} else {
|
||||
throw new IllegalArgumentException("Cannot set preference with type " + value.getClass());
|
||||
}
|
||||
editor.commit();
|
||||
return uri;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int delete(Uri uri, String selection, String[] selectionArgs) {
|
||||
PrefNameKeyPair nameKeyPair = parseUri(uri);
|
||||
String key = nameKeyPair.key;
|
||||
SharedPreferences preferences = getPreferences(nameKeyPair, true);
|
||||
if (key.length() == 0) {
|
||||
preferences.edit().clear().commit();
|
||||
} else {
|
||||
preferences.edit().remove(key).commit();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
|
||||
insert(uri, values);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
|
||||
String prefName = getPreferencesName(sharedPreferences);
|
||||
Uri uri = mBaseUri.buildUpon().appendPath(prefName).appendPath(key).build();
|
||||
getContext().getContentResolver().notifyChange(uri, null);
|
||||
}
|
||||
|
||||
private PrefNameKeyPair parseUri(Uri uri) {
|
||||
int match = mUriMatcher.match(uri);
|
||||
if (match != PREFERENCE_ID && match != PREFERENCES_ID) {
|
||||
throw new IllegalArgumentException("Invalid URI: " + uri);
|
||||
}
|
||||
List<String> pathSegments = uri.getPathSegments();
|
||||
String prefName = pathSegments.get(0);
|
||||
String prefKey = "";
|
||||
if (match == PREFERENCE_ID) {
|
||||
prefKey = pathSegments.get(1);
|
||||
}
|
||||
return new PrefNameKeyPair(prefName, prefKey);
|
||||
}
|
||||
|
||||
private int getPrefType(Object value) {
|
||||
if (value == null) return ShareContract.TYPE_NULL;
|
||||
if (value instanceof String) return ShareContract.TYPE_STRING;
|
||||
if (value instanceof Set<?>) return ShareContract.TYPE_STRING_SET;
|
||||
if (value instanceof Integer) return ShareContract.TYPE_INT;
|
||||
if (value instanceof Long) return ShareContract.TYPE_LONG;
|
||||
if (value instanceof Float) return ShareContract.TYPE_FLOAT;
|
||||
if (value instanceof Boolean) return ShareContract.TYPE_BOOLEAN;
|
||||
throw new AssertionError("Unknown preference type: " + value.getClass());
|
||||
}
|
||||
|
||||
private Object[] buildRow(String[] projection, String key, Object value) {
|
||||
Object[] row = new Object[projection.length];
|
||||
for (int i = 0; i < row.length; ++i) {
|
||||
String col = projection[i];
|
||||
if (ShareContract.COLUMN_KEY.equals(col)) {
|
||||
row[i] = key;
|
||||
} else if (ShareContract.COLUMN_TYPE.equals(col)) {
|
||||
row[i] = getPrefType(value);
|
||||
} else if (ShareContract.COLUMN_VALUE.equals(col)) {
|
||||
row[i] = ShareUtils.serialize(value);
|
||||
} else {
|
||||
throw new IllegalArgumentException("Invalid column name: " + col);
|
||||
}
|
||||
}
|
||||
return row;
|
||||
}
|
||||
|
||||
private SharedPreferences getPreferences(PrefNameKeyPair nameKeyPair, boolean write) {
|
||||
String prefName = nameKeyPair.name;
|
||||
String prefKey = nameKeyPair.key;
|
||||
SharedPreferences prefs = mPreferences.get(prefName);
|
||||
if (prefs == null) {
|
||||
throw new IllegalArgumentException("Unknown preference file name: " + prefName);
|
||||
}
|
||||
if (!checkAccess(prefName, prefKey, write)) {
|
||||
throw new SecurityException("Insufficient permissions to access: " + prefName + "/" + prefKey);
|
||||
}
|
||||
return prefs;
|
||||
}
|
||||
|
||||
private String getPreferencesName(SharedPreferences preferences) {
|
||||
for (Map.Entry<String, SharedPreferences> entry : mPreferences.entrySet()) {
|
||||
if (entry.getValue() == preferences) {
|
||||
return entry.getKey();
|
||||
}
|
||||
}
|
||||
throw new AssertionError("Cannot find name for SharedPreferences");
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether a specific preference is accessible by clients.
|
||||
* The default implementation returns {@code true} for all accesses.
|
||||
* You may override this method to control which preferences can be
|
||||
* read or written.
|
||||
*
|
||||
* @param prefName The name of the preference file.
|
||||
* @param prefKey The preference key. This is an empty string when handling the
|
||||
* {@link SharedPreferences#getAll()} and
|
||||
* {@link SharedPreferences.Editor#clear()} operations.
|
||||
* @param write {@code true} for "put" operations; {@code false} for "get" operations.
|
||||
* @return {@code true} if the access is allowed; {@code false} otherwise.
|
||||
*/
|
||||
protected boolean checkAccess(String prefName, String prefKey, boolean write) {
|
||||
return true;
|
||||
}
|
||||
|
||||
private static class PrefNameKeyPair {
|
||||
private final String name;
|
||||
private final String key;
|
||||
|
||||
private PrefNameKeyPair(String prefName, String prefKey) {
|
||||
name = prefName;
|
||||
key = prefKey;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
package dev.ukanth.ufirewall.preferences;
|
||||
|
||||
import dev.ukanth.ufirewall.Api;
|
||||
import dev.ukanth.ufirewall.BuildConfig;
|
||||
|
||||
/**
|
||||
* Created by ukanth on 19/7/16.
|
||||
*/
|
||||
public class ShareProfilePreference extends SharePreferenceProvider {
|
||||
public ShareProfilePreference() {
|
||||
super(new String[] {BuildConfig.APPLICATION_ID + "_preferences", Api.PREFS_NAME});
|
||||
}
|
||||
|
||||
//make sure only read access
|
||||
@Override
|
||||
protected boolean checkAccess(String prefName, String prefKey, boolean write) {
|
||||
// Only allow read access
|
||||
return !write;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,64 @@
|
|||
package dev.ukanth.ufirewall.preferences;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
|
||||
/**
|
||||
* Created by ukanth on 19/7/16.
|
||||
*/
|
||||
public class ShareUtils {
|
||||
@SuppressWarnings("unchecked")
|
||||
public static Set<String> toStringSet(Object value) {
|
||||
return (Set<String>)value;
|
||||
}
|
||||
|
||||
public static Object serialize(Object value) {
|
||||
if (value instanceof Boolean) {
|
||||
return (Boolean)value ? 1 : 0;
|
||||
} else if (value instanceof Set<?>) {
|
||||
return ShareUtils.serializeStringSet(toStringSet(value));
|
||||
} else {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
public static Object deserialize(Object value, int expectedType) {
|
||||
if (value == null) {
|
||||
return null;
|
||||
} else if (expectedType == ShareContract.TYPE_BOOLEAN) {
|
||||
return (Integer)value != 0;
|
||||
} else if (expectedType == ShareContract.TYPE_STRING_SET) {
|
||||
return ShareUtils.deserializeStringSet((String)value);
|
||||
} else {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
public static String serializeStringSet(Set<String> stringSet) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (String s : stringSet) {
|
||||
sb.append(s.replace("\\", "\\\\").replace(";", "\\;"));
|
||||
sb.append(';');
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public static Set<String> deserializeStringSet(String serializedString) {
|
||||
HashSet<String> stringSet = new HashSet<String>();
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (int i = 0; i < serializedString.length(); ++i) {
|
||||
char c = serializedString.charAt(i);
|
||||
if (c == '\\') {
|
||||
char next = serializedString.charAt(++i);
|
||||
sb.append(next);
|
||||
} else if (c == ';') {
|
||||
stringSet.add(sb.toString());
|
||||
sb.setLength(0);
|
||||
} else {
|
||||
sb.append(c);
|
||||
}
|
||||
}
|
||||
return stringSet;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,81 @@
|
|||
package dev.ukanth.ufirewall.preferences;
|
||||
|
||||
import static dev.ukanth.ufirewall.util.G.isDonate;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.Bundle;
|
||||
import android.preference.PreferenceFragment;
|
||||
import android.widget.Toast;
|
||||
|
||||
import dev.ukanth.ufirewall.Api;
|
||||
import dev.ukanth.ufirewall.R;
|
||||
import dev.ukanth.ufirewall.util.G;
|
||||
|
||||
public class ThemePreferenceFragment extends PreferenceFragment implements
|
||||
SharedPreferences.OnSharedPreferenceChangeListener {
|
||||
private Context ctx;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
// Load the preferences from an XML resource
|
||||
addPreferencesFromResource(R.xml.theme_preference);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttach(Context context) {
|
||||
super.onAttach(context);
|
||||
ctx = context;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
getPreferenceManager().getSharedPreferences()
|
||||
.registerOnSharedPreferenceChangeListener(this);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPause() {
|
||||
getPreferenceManager().getSharedPreferences()
|
||||
.unregisterOnSharedPreferenceChangeListener(this);
|
||||
super.onPause();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences,
|
||||
String key) {
|
||||
if (ctx == null) {
|
||||
ctx = getActivity();
|
||||
}
|
||||
if (ctx != null) {
|
||||
if (key.equals("theme")) {
|
||||
switch (G.getSelectedTheme()){
|
||||
case "D":
|
||||
G.getInstance().setTheme(R.style.AppDarkTheme);
|
||||
break;
|
||||
case "L":
|
||||
if ((G.isDoKey(ctx) || isDonate())) {
|
||||
G.getInstance().setTheme(R.style.AppLightTheme);
|
||||
} else {
|
||||
Api.toast(ctx, ctx.getText(R.string.donate_only), Toast.LENGTH_LONG);
|
||||
G.getSelectedTheme("D");
|
||||
}
|
||||
break;
|
||||
case "B":
|
||||
if ((G.isDoKey(ctx) || isDonate())) {
|
||||
G.getInstance().setTheme(R.style.AppBlackTheme);
|
||||
} else {
|
||||
Api.toast(ctx, ctx.getText(R.string.donate_only), Toast.LENGTH_LONG);
|
||||
G.getSelectedTheme("D");
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,148 @@
|
|||
package dev.ukanth.ufirewall.preferences;
|
||||
|
||||
import static dev.ukanth.ufirewall.util.G.isDonate;
|
||||
|
||||
import android.app.NotificationManager;
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.Bundle;
|
||||
import android.preference.Preference;
|
||||
import android.preference.PreferenceFragment;
|
||||
|
||||
import com.afollestad.materialdialogs.MaterialDialog;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
import dev.ukanth.ufirewall.Api;
|
||||
import dev.ukanth.ufirewall.R;
|
||||
import dev.ukanth.ufirewall.util.G;
|
||||
|
||||
public class UIPreferenceFragment extends PreferenceFragment implements
|
||||
SharedPreferences.OnSharedPreferenceChangeListener {
|
||||
|
||||
private Context ctx;
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
// Load the preferences from an XML resource
|
||||
addPreferencesFromResource(R.xml.ui_preferences);
|
||||
if ((G.isDoKey(ctx) || isDonate())) {
|
||||
populatePreference(findPreference("default_behavior_allow_mode"), getString(R.string.connection_default_allow), 0);
|
||||
populatePreference(findPreference("default_behavior_block_mode"), getString(R.string.connection_default_allow), 1);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttach(Context context) {
|
||||
super.onAttach(context);
|
||||
ctx = context;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
getPreferenceManager().getSharedPreferences()
|
||||
.registerOnSharedPreferenceChangeListener(this);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPause() {
|
||||
getPreferenceManager().getSharedPreferences()
|
||||
.unregisterOnSharedPreferenceChangeListener(this);
|
||||
super.onPause();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences,
|
||||
String key) {
|
||||
if(ctx == null) {
|
||||
ctx = getActivity();
|
||||
}
|
||||
if(ctx != null) {
|
||||
if (key.equals("notification_priority")) {
|
||||
NotificationManager notificationManager = (NotificationManager) ctx.getSystemService(Context.NOTIFICATION_SERVICE);
|
||||
notificationManager.cancel(1);
|
||||
//Api.showNotification(Api.isEnabled(ctx), ctx);
|
||||
Api.updateNotification(Api.isEnabled(ctx), ctx);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void populatePreference(Preference list, String title, int modeType) {
|
||||
final ArrayList<CharSequence> entriesList = new ArrayList<CharSequence>();
|
||||
final ArrayList<Integer> entryValuesList = new ArrayList<Integer>();
|
||||
|
||||
entriesList.add(getString(R.string.lan));
|
||||
entriesList.add(getString(R.string.wifi));
|
||||
entriesList.add(getString(R.string.data));
|
||||
entriesList.add(getString(R.string.roaming));
|
||||
entriesList.add(getString(R.string.tor));
|
||||
entriesList.add(getString(R.string.vpn));
|
||||
entriesList.add(getString(R.string.tether));
|
||||
|
||||
entryValuesList.add(0);
|
||||
entryValuesList.add(1);
|
||||
entryValuesList.add(2);
|
||||
entryValuesList.add(3);
|
||||
entryValuesList.add(4);
|
||||
entryValuesList.add(5);
|
||||
entryValuesList.add(6);
|
||||
|
||||
list.setOnPreferenceClickListener(preference -> {
|
||||
//open browser or intent here
|
||||
|
||||
MaterialDialog dialog = new MaterialDialog.Builder(getActivity())
|
||||
.title(title)
|
||||
.itemsIds(convertIntegers(entryValuesList))
|
||||
.items(entriesList)
|
||||
.itemsCallbackMultiChoice(null, (dialog1, which, text) -> {
|
||||
List<Integer> listPerf = new ArrayList<Integer>();
|
||||
List<Integer> selectedItems = new ArrayList<Integer>();
|
||||
List<Integer> ignoredItems = new ArrayList<Integer>();
|
||||
for (int i : which) {
|
||||
selectedItems.add(i);
|
||||
listPerf.add(entryValuesList.get(i));
|
||||
}
|
||||
for (int item: entryValuesList) {
|
||||
if(!selectedItems.contains(item)) {
|
||||
ignoredItems.add(item);
|
||||
}
|
||||
}
|
||||
G.storeDefaultConnection(listPerf,ignoredItems,modeType);
|
||||
return true;
|
||||
})
|
||||
.positiveText(R.string.OK)
|
||||
.negativeText(R.string.close)
|
||||
.show();
|
||||
|
||||
if (G.readDefaultConnection(modeType).size() > 0) {
|
||||
dialog.setSelectedIndices(selectItems(entryValuesList,modeType));
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
public static int[] convertIntegers(List<Integer> integers) {
|
||||
int[] ret = new int[integers.size()];
|
||||
Iterator<Integer> iterator = integers.iterator();
|
||||
for (int i = 0; i < ret.length; i++) {
|
||||
ret[i] = iterator.next().intValue();
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
private Integer[] selectItems(ArrayList<Integer> entryValuesList, int modeType) {
|
||||
List<Integer> items = new ArrayList<>();
|
||||
for (Integer in : G.readDefaultConnection(modeType)) {
|
||||
if (entryValuesList.contains(in)) {
|
||||
items.add(entryValuesList.indexOf(in));
|
||||
}
|
||||
}
|
||||
return items.toArray(new Integer[0]);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
package dev.ukanth.ufirewall.preferences;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.preference.EditTextPreference;
|
||||
import android.preference.PreferenceFragment;
|
||||
import android.text.InputType;
|
||||
import android.util.DisplayMetrics;
|
||||
import android.view.WindowManager;
|
||||
import android.widget.EditText;
|
||||
|
||||
import dev.ukanth.ufirewall.R;
|
||||
|
||||
public class WidgetPreferenceFragment extends PreferenceFragment {
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
try {
|
||||
// Load the preferences from an XML resource
|
||||
addPreferencesFromResource(R.xml.widget_preferences);
|
||||
|
||||
EditTextPreference editX = (EditTextPreference) findPreference("widgetX");
|
||||
EditTextPreference editY = (EditTextPreference) findPreference("widgetY");
|
||||
|
||||
EditText prefEditTextX = editX.getEditText();
|
||||
prefEditTextX.setInputType(InputType.TYPE_CLASS_TEXT);
|
||||
|
||||
EditText prefEditTextY = editY.getEditText();
|
||||
prefEditTextY.setInputType(InputType.TYPE_CLASS_TEXT);
|
||||
|
||||
if (editX != null && (editX.getText() == null || editX.getText().equals("")) && editY != null && (editY.getText() == null || editY.getText().equals(""))) {
|
||||
DisplayMetrics dm = new DisplayMetrics();
|
||||
Context hostActivity = getActivity();
|
||||
if (hostActivity != null) {
|
||||
WindowManager wm = (WindowManager) hostActivity.getSystemService(Context.WINDOW_SERVICE);
|
||||
wm.getDefaultDisplay().getMetrics(dm);
|
||||
editX.setText(dm.widthPixels + "");
|
||||
editY.setText(dm.heightPixels + "");
|
||||
}
|
||||
}
|
||||
} catch(ClassCastException e) {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,68 @@
|
|||
package dev.ukanth.ufirewall.profiles;
|
||||
|
||||
import android.content.Context;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.TextView;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import dev.ukanth.ufirewall.R;
|
||||
|
||||
/**
|
||||
* Created by ukanth on 31/7/15.
|
||||
*/
|
||||
public class ProfileAdapter extends ArrayAdapter<ProfileData> {
|
||||
|
||||
private final List<ProfileData> profileList;
|
||||
private final Context context;
|
||||
|
||||
|
||||
public ProfileAdapter(List<ProfileData> profileList, Context ctx) {
|
||||
super(ctx, R.layout.profile_layout, profileList);
|
||||
this.profileList = profileList;
|
||||
this.context = ctx;
|
||||
}
|
||||
|
||||
public int getCount() {
|
||||
return profileList.size();
|
||||
}
|
||||
|
||||
public ProfileData getItem(int position) {
|
||||
return profileList.get(position);
|
||||
}
|
||||
|
||||
public long getItemId(int position) {
|
||||
return profileList.get(position).hashCode();
|
||||
}
|
||||
|
||||
public View getView(int position, View convertView, ViewGroup parent) {
|
||||
View v = convertView;
|
||||
ProfileHolder holder = new ProfileHolder();
|
||||
if (convertView == null) {
|
||||
LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
|
||||
v = inflater.inflate(R.layout.profile_layout, null);
|
||||
holder.profileNameView = v.findViewById(R.id.pro_name);
|
||||
v.setTag(holder);
|
||||
} else {
|
||||
holder = (ProfileHolder) v.getTag();
|
||||
}
|
||||
ProfileData p = profileList.get(position);
|
||||
holder.profile = p;
|
||||
holder.profileNameView.setText(p.getName());
|
||||
return v;
|
||||
}
|
||||
|
||||
/* *********************************
|
||||
* We use the holder pattern
|
||||
* It makes the view faster and avoid finding the component
|
||||
* **********************************/
|
||||
|
||||
private static class ProfileHolder {
|
||||
public TextView profileNameView;
|
||||
public ProfileData profile;
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,82 @@
|
|||
package dev.ukanth.ufirewall.profiles;
|
||||
|
||||
import com.raizlabs.android.dbflow.annotation.Column;
|
||||
import com.raizlabs.android.dbflow.annotation.PrimaryKey;
|
||||
import com.raizlabs.android.dbflow.annotation.Table;
|
||||
import com.raizlabs.android.dbflow.structure.BaseModel;
|
||||
|
||||
/**
|
||||
* Created by ukanth on 17/1/16.
|
||||
*/
|
||||
|
||||
@Table(database = ProfilesDatabase.class)
|
||||
public class ProfileData extends BaseModel implements Cloneable{
|
||||
|
||||
@Column
|
||||
@PrimaryKey(autoincrement = true)
|
||||
long id;
|
||||
|
||||
public long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void removeId(){
|
||||
id = -1;
|
||||
}
|
||||
|
||||
@Column
|
||||
private String name;
|
||||
|
||||
@Column
|
||||
private String identifier;
|
||||
|
||||
@Column
|
||||
private String attibutes;
|
||||
|
||||
@Column
|
||||
private String parentProfile;
|
||||
|
||||
public ProfileData() {
|
||||
}
|
||||
|
||||
public ProfileData(String name, String identifier) {
|
||||
this.name = name;
|
||||
this.identifier = identifier;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public String getIdentifier() {
|
||||
return identifier;
|
||||
}
|
||||
|
||||
public void setIdentifier(String identifier) {
|
||||
this.identifier = identifier;
|
||||
}
|
||||
|
||||
public String getAttibutes() {
|
||||
return attibutes;
|
||||
}
|
||||
|
||||
public void setAttibutes(String attibutes) {
|
||||
this.attibutes = attibutes;
|
||||
}
|
||||
|
||||
public String getParentProfile() {
|
||||
return parentProfile;
|
||||
}
|
||||
|
||||
public void setParentProfile(String parentProfile) {
|
||||
this.parentProfile = parentProfile;
|
||||
}
|
||||
|
||||
public ProfileData clone() throws CloneNotSupportedException {
|
||||
return (ProfileData) super.clone();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,135 @@
|
|||
package dev.ukanth.ufirewall.profiles;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import com.raizlabs.android.dbflow.config.FlowConfig;
|
||||
import com.raizlabs.android.dbflow.config.FlowManager;
|
||||
import com.raizlabs.android.dbflow.sql.language.SQLite;
|
||||
import com.raizlabs.android.dbflow.structure.database.DatabaseWrapper;
|
||||
import com.raizlabs.android.dbflow.structure.database.transaction.ITransaction;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import dev.ukanth.ufirewall.R;
|
||||
import dev.ukanth.ufirewall.log.Log;
|
||||
import dev.ukanth.ufirewall.util.G;
|
||||
|
||||
/**
|
||||
* Created by ukanth on 31/7/15.
|
||||
*/
|
||||
public class ProfileHelper {
|
||||
|
||||
private static final String TAG = "AFWall";
|
||||
|
||||
public static void storeProfile(final ProfileData profile, Context ctx, ProfileData parentProfile) {
|
||||
try {
|
||||
FlowManager.getDatabase(ProfilesDatabase.class).beginTransactionAsync(new ITransaction() {
|
||||
@Override
|
||||
public void execute(DatabaseWrapper databaseWrapper) {
|
||||
profile.save(databaseWrapper);
|
||||
}
|
||||
}).build().execute();
|
||||
} catch (IllegalStateException e) {
|
||||
if (e.getMessage().contains("connection pool has been closed")) {
|
||||
//reconnect logic
|
||||
try {
|
||||
FlowManager.init(new FlowConfig.Builder(ctx).build());
|
||||
} catch (Exception de) {
|
||||
Log.i(TAG, "Exception while saving profile data:" + e.getLocalizedMessage());
|
||||
}
|
||||
}
|
||||
Log.i(TAG, "Exception while saving profile data:" + e.getLocalizedMessage());
|
||||
} catch (Exception e) {
|
||||
Log.i(TAG, "Exception while saving profile data:" + e.getLocalizedMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public static List<ProfileData> getProfiles() {
|
||||
|
||||
return SQLite.select()
|
||||
.from(ProfileData.class)
|
||||
.queryList();
|
||||
}
|
||||
|
||||
|
||||
public static ProfileData getProfileByName(String profileName) {
|
||||
return SQLite.select()
|
||||
.from(ProfileData.class).where(ProfileData_Table.name.eq(profileName))
|
||||
.querySingle();
|
||||
}
|
||||
|
||||
public static ProfileData getProfileByIdentifier(String identifier) {
|
||||
return SQLite.select()
|
||||
.from(ProfileData.class).where(ProfileData_Table.identifier.eq(identifier))
|
||||
.querySingle();
|
||||
}
|
||||
|
||||
public static void updateProfileName(String identifier,String newName) {
|
||||
ProfileData profileData = SQLite.select()
|
||||
.from(ProfileData.class).where(ProfileData_Table.name.eq(identifier))
|
||||
.querySingle();
|
||||
profileData.setName(newName);
|
||||
profileData.save();
|
||||
}
|
||||
|
||||
public static boolean deleteProfile(String identifier) {
|
||||
ProfileData data = getProfileByIdentifier(identifier);
|
||||
if (data != null) {
|
||||
data.delete();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
public static boolean deleteProfileByName(String profileName) {
|
||||
ProfileData data = getProfileByName(profileName);
|
||||
if (data != null) {
|
||||
data.delete();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public static void migrateProfiles(Context ctx) {
|
||||
if (!G.isProfileMigrated()) {
|
||||
List<ProfileData> listProfile = new ArrayList<>();
|
||||
List<String> addProfiles = G.getAdditionalProfiles();
|
||||
List<String> defaultProfiles = G.getDefaultProfiles();
|
||||
if (defaultProfiles != null && addProfiles != null) {
|
||||
for (int i = 0; i < defaultProfiles.size(); i++) {
|
||||
String profileName = defaultProfiles.get(i);
|
||||
String customName = "";
|
||||
switch (i) {
|
||||
case 0:
|
||||
customName = G.gPrefs.getString("profile1", ctx.getString(R.string.profile1));
|
||||
break;
|
||||
case 1:
|
||||
customName = G.gPrefs.getString("profile2", ctx.getString(R.string.profile2));
|
||||
break;
|
||||
case 2:
|
||||
customName = G.gPrefs.getString("profile3", ctx.getString(R.string.profile3));
|
||||
break;
|
||||
}
|
||||
ProfileData profile = new ProfileData();
|
||||
profile.setName(customName);
|
||||
profile.setIdentifier(profileName);
|
||||
listProfile.add(profile);
|
||||
}
|
||||
for (String profileName : addProfiles) {
|
||||
ProfileData profile = new ProfileData();
|
||||
profile.setName(profileName);
|
||||
profile.setIdentifier(profileName);
|
||||
listProfile.add(profile);
|
||||
}
|
||||
}
|
||||
//now store the migrateProfile
|
||||
try {
|
||||
for (ProfileData profile : listProfile) {
|
||||
ProfileHelper.storeProfile(profile, ctx, null);
|
||||
}
|
||||
//now all is well, mark as migrated
|
||||
G.isProfileMigrated(true);
|
||||
} catch (Exception e) {
|
||||
G.isProfileMigrated(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
package dev.ukanth.ufirewall.profiles;
|
||||
|
||||
import com.raizlabs.android.dbflow.annotation.Database;
|
||||
|
||||
/**
|
||||
* Created by ukanth .
|
||||
*/
|
||||
|
||||
@Database(name = ProfilesDatabase.NAME, version = ProfilesDatabase.VERSION)
|
||||
public class ProfilesDatabase {
|
||||
|
||||
public static final String NAME = "profiles";
|
||||
|
||||
public static final int VERSION = 1;
|
||||
}
|
||||
|
|
@ -0,0 +1,293 @@
|
|||
package dev.ukanth.ufirewall.service;
|
||||
|
||||
import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_SPECIAL_USE;
|
||||
|
||||
import android.app.Notification;
|
||||
import android.app.NotificationChannel;
|
||||
import android.app.NotificationManager;
|
||||
import android.app.PendingIntent;
|
||||
import android.app.Service;
|
||||
import android.bluetooth.BluetoothAdapter;
|
||||
import android.bluetooth.BluetoothProfile;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.net.ConnectivityManager;
|
||||
import android.os.Build;
|
||||
import android.os.IBinder;
|
||||
|
||||
import androidx.core.app.NotificationCompat;
|
||||
|
||||
import dev.ukanth.ufirewall.Api;
|
||||
import dev.ukanth.ufirewall.InterfaceTracker;
|
||||
import dev.ukanth.ufirewall.MainActivity;
|
||||
import dev.ukanth.ufirewall.R;
|
||||
import dev.ukanth.ufirewall.broadcast.ConnectivityChangeReceiver;
|
||||
import dev.ukanth.ufirewall.broadcast.PackageBroadcast;
|
||||
import dev.ukanth.ufirewall.log.Log;
|
||||
import dev.ukanth.ufirewall.util.G;
|
||||
|
||||
public class FirewallService extends Service {
|
||||
|
||||
private static final int NOTIFICATION_ID = 1;
|
||||
BroadcastReceiver connectivityReciver;
|
||||
BroadcastReceiver packageReceiver;
|
||||
IntentFilter filter;
|
||||
private BluetoothAdapter bluetoothAdapter;
|
||||
private BluetoothProfile.ServiceListener btListener;
|
||||
private static BluetoothProfile btPanProfile;
|
||||
private static boolean btConnectionRequested = false; // Track if connection was requested
|
||||
public Context context;
|
||||
@Override
|
||||
public IBinder onBind(Intent intent) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
context = this;
|
||||
}
|
||||
|
||||
private void registerBTListener() {
|
||||
// Only create listener if it doesn't exist to prevent leaks
|
||||
if (btListener == null) {
|
||||
btListener = new BluetoothProfile.ServiceListener() {
|
||||
@Override
|
||||
public void onServiceConnected(int profile, BluetoothProfile proxy) {
|
||||
Log.d(G.TAG, "BluetoothProfile.ServiceListener connected");
|
||||
btPanProfile = proxy;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onServiceDisconnected(int profile) {
|
||||
Log.d(G.TAG, "BluetoothProfile.ServiceListener disconnected");
|
||||
btPanProfile = null; // Clear reference on disconnect
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void addNotification() {
|
||||
String NOTIFICATION_CHANNEL_ID = "firewall.service";
|
||||
String channelName = getString(R.string.firewall_service);
|
||||
|
||||
NotificationManager manager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
|
||||
manager.cancel(NOTIFICATION_ID);
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
NotificationChannel notificationChannel = new NotificationChannel(NOTIFICATION_CHANNEL_ID, channelName, NotificationManager.IMPORTANCE_LOW);
|
||||
notificationChannel.setLockscreenVisibility(Notification.VISIBILITY_PUBLIC);
|
||||
assert manager != null;
|
||||
if(G.getNotificationPriority() == 0) {
|
||||
notificationChannel.setImportance(NotificationManager.IMPORTANCE_DEFAULT);
|
||||
} else {
|
||||
notificationChannel.setImportance(NotificationManager.IMPORTANCE_LOW);
|
||||
}
|
||||
notificationChannel.setSound(null, null);
|
||||
notificationChannel.enableLights(false);
|
||||
notificationChannel.setShowBadge(true);
|
||||
notificationChannel.enableVibration(false);
|
||||
manager.createNotificationChannel(notificationChannel);
|
||||
}
|
||||
|
||||
|
||||
Intent appIntent = new Intent(this, MainActivity.class);
|
||||
appIntent.setAction(Intent.ACTION_MAIN);
|
||||
appIntent.addCategory(Intent.CATEGORY_LAUNCHER);
|
||||
appIntent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP | Intent.FLAG_ACTIVITY_CLEAR_TOP);
|
||||
|
||||
|
||||
/*TaskStackBuilder stackBuilder = TaskStackBuilder.create(this);
|
||||
stackBuilder.addParentStack(MainActivity.class);
|
||||
stackBuilder.addNextIntent(appIntent);*/
|
||||
|
||||
int icon;
|
||||
String notificationText = "";
|
||||
|
||||
if (Api.isEnabled(this)) {
|
||||
if (G.enableMultiProfile()) {
|
||||
String profile = "";
|
||||
switch (G.storedProfile()) {
|
||||
case "AFWallPrefs":
|
||||
profile = G.gPrefs.getString("default", getString(R.string.defaultProfile));
|
||||
break;
|
||||
case "AFWallProfile1":
|
||||
profile = G.gPrefs.getString("profile1", getString(R.string.profile1));
|
||||
break;
|
||||
case "AFWallProfile2":
|
||||
profile = G.gPrefs.getString("profile2", getString(R.string.profile2));
|
||||
break;
|
||||
case "AFWallProfile3":
|
||||
profile = G.gPrefs.getString("profile3", getString(R.string.profile3));
|
||||
break;
|
||||
default:
|
||||
profile = G.storedProfile();
|
||||
break;
|
||||
}
|
||||
notificationText = getString(R.string.active) + " (" + profile + ")";
|
||||
} else {
|
||||
notificationText = getString(R.string.active);
|
||||
}
|
||||
//notificationText = context.getString(R.string.active);
|
||||
icon = R.drawable.notification;
|
||||
} else {
|
||||
notificationText = getString(R.string.inactive);
|
||||
icon = R.drawable.notification_error;
|
||||
}
|
||||
|
||||
|
||||
PendingIntent notifyPendingIntent = PendingIntent.getActivity(this, 0, appIntent, PendingIntent.FLAG_IMMUTABLE);
|
||||
NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(this, NOTIFICATION_CHANNEL_ID);
|
||||
notificationBuilder.setContentIntent(notifyPendingIntent);
|
||||
|
||||
//int notifyType = G.getNotificationPriority();
|
||||
Notification notification = notificationBuilder
|
||||
.setContentTitle(getString(R.string.app_name))
|
||||
.setTicker(getString(R.string.app_name))
|
||||
.setSound(null)
|
||||
.setChannelId(NOTIFICATION_CHANNEL_ID)
|
||||
.setPriority(NotificationCompat.PRIORITY_LOW)
|
||||
.setCategory(Notification.CATEGORY_SERVICE)
|
||||
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
|
||||
.setContentText(notificationText)
|
||||
.setSmallIcon(icon)
|
||||
.setOngoing(true)
|
||||
.build();
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
|
||||
startForeground(NOTIFICATION_ID, notification, FOREGROUND_SERVICE_TYPE_SPECIAL_USE);
|
||||
} else if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O ) {
|
||||
startForeground(NOTIFICATION_ID, notification);
|
||||
} else {
|
||||
if(G.activeNotification()) {
|
||||
manager.notify(NOTIFICATION_ID, notification);
|
||||
}
|
||||
}
|
||||
/*} else {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
startForeground(NOTIFICATION_ID, notification);
|
||||
} else {
|
||||
//empty one
|
||||
startForeground(NOTIFICATION_ID, new Notification());
|
||||
}
|
||||
}*/
|
||||
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public int onStartCommand(Intent intent, int flags, int startId) {
|
||||
|
||||
addNotification();
|
||||
registerBTListener();
|
||||
|
||||
//incase if it's not null, make sure we unregister it
|
||||
if(packageReceiver != null) {
|
||||
unregisterReceiver(packageReceiver);
|
||||
}
|
||||
|
||||
if (connectivityReciver != null) {
|
||||
unregisterReceiver(connectivityReciver);
|
||||
}
|
||||
|
||||
connectivityReciver = new ConnectivityChangeReceiver();
|
||||
filter = new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION);
|
||||
filter.addAction(ConnectivityChangeReceiver.TETHER_STATE_CHANGED_ACTION);
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
registerReceiver(connectivityReciver, filter, RECEIVER_EXPORTED);
|
||||
} else {
|
||||
registerReceiver(connectivityReciver, filter);
|
||||
}
|
||||
|
||||
IntentFilter intentFilter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED);
|
||||
intentFilter.addDataScheme("package");
|
||||
packageReceiver = new PackageBroadcast();
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
registerReceiver(packageReceiver, intentFilter, RECEIVER_EXPORTED);
|
||||
} else {
|
||||
registerReceiver(packageReceiver, intentFilter);
|
||||
}
|
||||
|
||||
|
||||
intentFilter = new IntentFilter(Intent.ACTION_PACKAGE_REMOVED);
|
||||
intentFilter.addDataScheme("package");
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
registerReceiver(packageReceiver, intentFilter,RECEIVER_EXPORTED);
|
||||
} else {
|
||||
registerReceiver(packageReceiver, intentFilter);
|
||||
}
|
||||
|
||||
// TEMPORARY: Bluetooth initialization completely disabled to prevent connection leaks
|
||||
Log.d(G.TAG, "Bluetooth initialization disabled to prevent service connection leaks");
|
||||
|
||||
return START_STICKY;
|
||||
}
|
||||
|
||||
private BluetoothAdapter getBTAdapter(Context context) {
|
||||
BluetoothAdapter bluetoothAdapter = null;
|
||||
PackageManager pm = context.getPackageManager();
|
||||
boolean hasBluetooth = pm.hasSystemFeature(PackageManager.FEATURE_BLUETOOTH);
|
||||
if (hasBluetooth) {
|
||||
bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
|
||||
|
||||
// TEMPORARY: Disable Bluetooth profile connection to prevent service leaks
|
||||
// TODO: Find better way to handle Bluetooth tethering detection without connection leaks
|
||||
Log.d(G.TAG, "Bluetooth PAN profile connection disabled to prevent service leaks");
|
||||
} else {
|
||||
Log.d(G.TAG, "Device does not support Bluetooth, skipping");
|
||||
}
|
||||
return bluetoothAdapter;
|
||||
}
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
if (connectivityReciver != null) {
|
||||
unregisterReceiver(connectivityReciver);
|
||||
connectivityReciver = null;
|
||||
}
|
||||
if (packageReceiver != null) {
|
||||
unregisterReceiver(packageReceiver);
|
||||
packageReceiver = null;
|
||||
}
|
||||
|
||||
// Close bluetooth profile connection to prevent ServiceConnection leak
|
||||
if(bluetoothAdapter != null) {
|
||||
try {
|
||||
if(btPanProfile != null) {
|
||||
bluetoothAdapter.closeProfileProxy(5, btPanProfile); // BluetoothProfile.PAN
|
||||
btPanProfile = null;
|
||||
Log.d(G.TAG, "Closed Bluetooth PAN profile proxy");
|
||||
}
|
||||
} catch (Exception e){
|
||||
Log.e(G.TAG, "Error closing bt profile", e);
|
||||
} finally {
|
||||
// Always clean up references regardless of profile state
|
||||
btListener = null;
|
||||
bluetoothAdapter = null;
|
||||
btConnectionRequested = false; // Reset connection flag
|
||||
Log.d(G.TAG, "Bluetooth cleanup completed");
|
||||
}
|
||||
} else if (btConnectionRequested || btPanProfile != null) {
|
||||
// Edge case: Clean up even if adapter is null
|
||||
Log.w(G.TAG, "Bluetooth adapter is null but connection state exists, cleaning up");
|
||||
btPanProfile = null;
|
||||
btListener = null;
|
||||
btConnectionRequested = false;
|
||||
}
|
||||
super.onDestroy();
|
||||
}
|
||||
|
||||
|
||||
|
||||
public static BluetoothProfile getBtPanProfile() {
|
||||
// TEMPORARY: Return null to disable Bluetooth tethering detection
|
||||
// This prevents service connection leaks while we find a better solution
|
||||
Log.d(G.TAG, "Bluetooth PAN profile disabled, returning null");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
742
app/src/main/java/dev/ukanth/ufirewall/service/LogService.java
Normal file
742
app/src/main/java/dev/ukanth/ufirewall/service/LogService.java
Normal file
|
|
@ -0,0 +1,742 @@
|
|||
/**
|
||||
* Background service to spool /proc/kmesg command output using klogripper
|
||||
* <p/>
|
||||
* Copyright (C) 2014 Umakanthan Chandran
|
||||
* <p/>
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
* <p/>
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
* <p/>
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* @author Umakanthan Chandran
|
||||
* @version 1.0
|
||||
*/
|
||||
|
||||
package dev.ukanth.ufirewall.service;
|
||||
|
||||
import static dev.ukanth.ufirewall.util.G.ctx;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.AlarmManager;
|
||||
import android.app.Notification;
|
||||
import android.app.NotificationChannel;
|
||||
import android.app.NotificationManager;
|
||||
import android.app.PendingIntent;
|
||||
import android.app.Service;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Build;
|
||||
import android.os.Handler;
|
||||
import android.os.IBinder;
|
||||
import android.os.Looper;
|
||||
import android.os.SystemClock;
|
||||
import android.provider.Settings;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.core.app.NotificationCompat;
|
||||
|
||||
import com.raizlabs.android.dbflow.config.FlowConfig;
|
||||
import com.raizlabs.android.dbflow.config.FlowManager;
|
||||
import com.topjohnwu.superuser.CallbackList;
|
||||
import com.topjohnwu.superuser.NoShellException;
|
||||
import com.topjohnwu.superuser.Shell;
|
||||
|
||||
import org.ocpsoft.prettytime.PrettyTime;
|
||||
import org.ocpsoft.prettytime.TimeUnit;
|
||||
import org.ocpsoft.prettytime.units.JustNow;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
import dev.ukanth.ufirewall.Api;
|
||||
import dev.ukanth.ufirewall.R;
|
||||
import dev.ukanth.ufirewall.activity.LogActivity;
|
||||
import dev.ukanth.ufirewall.events.LogEvent;
|
||||
import dev.ukanth.ufirewall.log.Log;
|
||||
import dev.ukanth.ufirewall.log.LogData;
|
||||
import dev.ukanth.ufirewall.log.LogDatabase;
|
||||
import dev.ukanth.ufirewall.log.LogInfo;
|
||||
import dev.ukanth.ufirewall.util.G;
|
||||
|
||||
public class LogService extends Service {
|
||||
|
||||
public static final String TAG = "AFWall";
|
||||
public static String logPath;
|
||||
public static final int QUEUE_NUM = 40;
|
||||
|
||||
public static final String ACTION_GRACEFUL_SHUTDOWN = "dev.ukanth.ufirewall.GRACEFUL_SHUTDOWN";
|
||||
public static final String ACTION_CHANGE_LOG_TARGET = "dev.ukanth.ufirewall.CHANGE_LOG_TARGET";
|
||||
public static final String EXTRA_NEW_LOG_TARGET = "new_log_target";
|
||||
|
||||
private String NOTIFICATION_CHANNEL_ID = "firewall.logservice";
|
||||
|
||||
|
||||
private NotificationManager manager;
|
||||
private NotificationCompat.Builder notificationBuilder;
|
||||
|
||||
private List<String> callbackList;
|
||||
private ExecutorService executorService;
|
||||
private volatile boolean isShuttingDown = false;
|
||||
|
||||
private Shell logWatcherShell; // Additional shell for long running log-watcher process
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public IBinder onBind(Intent intent) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int onStartCommand(Intent intent, int flags, int startId) {
|
||||
if (intent != null) {
|
||||
if (ACTION_GRACEFUL_SHUTDOWN.equals(intent.getAction())) {
|
||||
Log.i(TAG, "Received graceful shutdown request");
|
||||
initiateGracefulShutdown();
|
||||
return START_NOT_STICKY;
|
||||
} else if (ACTION_CHANGE_LOG_TARGET.equals(intent.getAction())) {
|
||||
String newLogTarget = intent.getStringExtra(EXTRA_NEW_LOG_TARGET);
|
||||
Log.i(TAG, "Received log target change request to: " + newLogTarget);
|
||||
changeLogTarget(newLogTarget);
|
||||
return START_STICKY;
|
||||
}
|
||||
}
|
||||
|
||||
// Reset shutdown flag when service starts normally
|
||||
isShuttingDown = false;
|
||||
startLogService();
|
||||
return START_STICKY;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
startLogService();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the best available command for reading kernel logs with iptables messages
|
||||
* Tries multiple methods in order of preference for efficiency and compatibility
|
||||
* @return command string or null if no suitable method is available
|
||||
*/
|
||||
private String getBestLogCommand() {
|
||||
// Method 1: Try dmesg with follow and grep (most efficient for iptables logs)
|
||||
if (isCommandAvailable("dmesg --follow")) {
|
||||
return "dmesg --follow | grep '{AFL}'";
|
||||
}
|
||||
|
||||
// Method 2: Try dmesg with tail simulation (good fallback)
|
||||
if (isCommandAvailable("dmesg") && isCommandAvailable("tail")) {
|
||||
return "while true; do dmesg | grep '{AFL}' | tail -n +$(( $(wc -l < /tmp/afwall_lastline 2>/dev/null || echo 0) + 1 )); dmesg | wc -l > /tmp/afwall_lastline; sleep 1; done";
|
||||
}
|
||||
|
||||
// Method 3: Try logcat kernel logs (Android 7+)
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && isCommandAvailable("logcat")) {
|
||||
return "logcat -s kernel:* | grep '{AFL}'";
|
||||
}
|
||||
|
||||
// Method 4: Try journalctl if available (some Android variants)
|
||||
if (isCommandAvailable("journalctl")) {
|
||||
return "journalctl -k -f | grep '{AFL}'";
|
||||
}
|
||||
|
||||
// Method 5: Fall back to /proc/kmsg with improvements
|
||||
Log.w(TAG, "Falling back to /proc/kmsg - less efficient method");
|
||||
return "cat /proc/kmsg | grep --line-buffered '{AFL}'";
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a command is available on the system
|
||||
* @param command the command to test
|
||||
* @return true if command is available
|
||||
*/
|
||||
private boolean isCommandAvailable(String command) {
|
||||
try {
|
||||
String testCommand = command.split(" ")[0]; // Get the base command
|
||||
Shell.Result result = Shell.cmd("which " + testCommand + " || command -v " + testCommand).exec();
|
||||
return result.isSuccess() && !result.getOut().isEmpty();
|
||||
} catch (Exception e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private void startLogService() {
|
||||
if (G.enableLogService()) {
|
||||
// this method is executed in a background thread
|
||||
// no problem calling su here
|
||||
String log = G.logTarget();
|
||||
if (log != null) {
|
||||
log = log.trim();
|
||||
if(log.isEmpty()) {
|
||||
Toast.makeText(getApplicationContext(), "Please select log target first", Toast.LENGTH_LONG).show();
|
||||
return;
|
||||
}
|
||||
switch (log) {
|
||||
case "LOG":
|
||||
logPath = getBestLogCommand();
|
||||
if (logPath == null) {
|
||||
Log.e(TAG, "No suitable log reading method available");
|
||||
return;
|
||||
}
|
||||
break;
|
||||
case "NFLOG":
|
||||
logPath = Api.getEnhancedNflogCommand(getApplicationContext(), QUEUE_NUM);
|
||||
if (logPath == null) {
|
||||
Log.e(TAG, "NFLOG binary not available, cannot start logging service");
|
||||
return;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
Log.i(TAG, "Starting Log Service: " + logPath + " for LogTarget: " + G.logTarget());
|
||||
callbackList = new CallbackList<String>() {
|
||||
@Override
|
||||
public void onAddElement(String line) {
|
||||
// Handle device suspend/resume scenarios
|
||||
if(line.contains("suspend exit") || line.contains("PM: suspend exit")) {
|
||||
restartWatcher(logPath);
|
||||
}
|
||||
|
||||
// Handle log rotation or kernel ring buffer wrap
|
||||
if(line.contains("log_buf_len") || line.contains("Buffer wrap")) {
|
||||
restartWatcher(logPath);
|
||||
}
|
||||
|
||||
// Process iptables/netfilter log entries
|
||||
if(line.contains("{AFL}")) {
|
||||
storeLogInfo(line, getApplicationContext());
|
||||
}
|
||||
}
|
||||
};
|
||||
initiateLogWatcher(logPath);
|
||||
createNotification();
|
||||
|
||||
} else {
|
||||
Log.i(TAG, "Unable to start log service. LogTarget is empty");
|
||||
Api.toast(getApplicationContext(), getApplicationContext().getString(R.string.error_log));
|
||||
G.enableLogService(false);
|
||||
stopSelf();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void restartWatcher(String logPath) {
|
||||
if (isShuttingDown) {
|
||||
return;
|
||||
}
|
||||
|
||||
final Handler handler = new Handler(Looper.getMainLooper());
|
||||
handler.postDelayed(() -> {
|
||||
if (G.enableLogService() && !isShuttingDown) {
|
||||
Log.i(G.TAG, "Restarting log watcher after 5s");
|
||||
cleanupTempFiles();
|
||||
initiateLogWatcher(logPath);
|
||||
}
|
||||
}, 5000);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean up temporary files used by log watchers
|
||||
*/
|
||||
private void cleanupTempFiles() {
|
||||
try {
|
||||
Shell.cmd("rm -f /tmp/afwall_lastline").exec();
|
||||
} catch (Exception e) {
|
||||
// Ignore cleanup errors
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initiate graceful shutdown of the log service
|
||||
*/
|
||||
private void initiateGracefulShutdown() {
|
||||
Log.i(TAG, "Starting graceful shutdown process");
|
||||
|
||||
// Set shutdown flag to prevent new tasks
|
||||
isShuttingDown = true;
|
||||
|
||||
// Stop in background thread to avoid blocking the main thread
|
||||
new Thread(() -> {
|
||||
try {
|
||||
// Close shell first to stop generating new tasks
|
||||
if (logWatcherShell != null) {
|
||||
try {
|
||||
logWatcherShell.close();
|
||||
Log.i(TAG, "Log watcher shell closed");
|
||||
} catch (Exception e) {
|
||||
Log.w(TAG, "Error closing log watcher shell during graceful shutdown: " + e.getMessage());
|
||||
}
|
||||
logWatcherShell = null;
|
||||
}
|
||||
|
||||
// Give executor service time to finish current tasks
|
||||
if (executorService != null) {
|
||||
try {
|
||||
Log.i(TAG, "Shutting down executor service...");
|
||||
executorService.shutdown(); // Don't accept new tasks
|
||||
if (!executorService.awaitTermination(5000, java.util.concurrent.TimeUnit.MILLISECONDS)) {
|
||||
Log.w(TAG, "Executor service didn't terminate within 5s, forcing shutdown");
|
||||
executorService.shutdownNow();
|
||||
// Wait a bit more for tasks to respond to being cancelled
|
||||
if (!executorService.awaitTermination(2000, java.util.concurrent.TimeUnit.MILLISECONDS)) {
|
||||
Log.w(TAG, "Executor service still didn't terminate after force shutdown");
|
||||
}
|
||||
}
|
||||
Log.i(TAG, "Executor service shutdown complete");
|
||||
} catch (InterruptedException e) {
|
||||
Log.w(TAG, "Interrupted while shutting down executor service");
|
||||
executorService.shutdownNow();
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
}
|
||||
|
||||
// Clean up and stop service
|
||||
cleanupTempFiles();
|
||||
Log.i(TAG, "Graceful shutdown complete, stopping service");
|
||||
|
||||
// Stop the service on the main thread
|
||||
Handler mainHandler = new Handler(Looper.getMainLooper());
|
||||
mainHandler.post(() -> stopSelf());
|
||||
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Error during graceful shutdown: " + e.getMessage(), e);
|
||||
// Fallback to immediate stop
|
||||
Handler mainHandler = new Handler(Looper.getMainLooper());
|
||||
mainHandler.post(() -> stopSelf());
|
||||
}
|
||||
}, "LogService-GracefulShutdown").start();
|
||||
}
|
||||
|
||||
/**
|
||||
* Change the log target without restarting the service
|
||||
*/
|
||||
private void changeLogTarget(String newLogTarget) {
|
||||
if (newLogTarget == null || newLogTarget.trim().isEmpty()) {
|
||||
Log.w(TAG, "Invalid log target provided, ignoring change request");
|
||||
return;
|
||||
}
|
||||
|
||||
String currentLogTarget = G.logTarget();
|
||||
if (newLogTarget.equals(currentLogTarget)) {
|
||||
Log.i(TAG, "New log target is same as current, no change needed");
|
||||
return;
|
||||
}
|
||||
|
||||
Log.i(TAG, "Changing log target from " + currentLogTarget + " to " + newLogTarget);
|
||||
|
||||
// Stop current log watcher gracefully in background thread
|
||||
new Thread(() -> {
|
||||
try {
|
||||
// Set shutdown flag temporarily to prevent restarts
|
||||
isShuttingDown = true;
|
||||
|
||||
// Close current shell and executor
|
||||
if (logWatcherShell != null) {
|
||||
try {
|
||||
logWatcherShell.close();
|
||||
Log.i(TAG, "Closed existing log watcher shell");
|
||||
} catch (Exception e) {
|
||||
Log.w(TAG, "Error closing existing shell: " + e.getMessage());
|
||||
}
|
||||
logWatcherShell = null;
|
||||
}
|
||||
|
||||
if (executorService != null) {
|
||||
try {
|
||||
executorService.shutdown();
|
||||
if (!executorService.awaitTermination(3000, java.util.concurrent.TimeUnit.MILLISECONDS)) {
|
||||
Log.w(TAG, "Executor didn't terminate gracefully, forcing shutdown");
|
||||
executorService.shutdownNow();
|
||||
}
|
||||
Log.i(TAG, "Executor service shut down successfully");
|
||||
} catch (InterruptedException e) {
|
||||
Log.w(TAG, "Interrupted while shutting down executor");
|
||||
executorService.shutdownNow();
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
executorService = null;
|
||||
}
|
||||
|
||||
// Wait a moment for cleanup
|
||||
Thread.sleep(1000);
|
||||
|
||||
// Update log target in preferences
|
||||
G.logTarget(newLogTarget);
|
||||
|
||||
// Reset shutdown flag
|
||||
isShuttingDown = false;
|
||||
|
||||
// Restart log service with new target on main thread
|
||||
Handler mainHandler = new Handler(Looper.getMainLooper());
|
||||
mainHandler.post(() -> {
|
||||
Log.i(TAG, "Restarting log service with new target: " + newLogTarget);
|
||||
startLogService();
|
||||
});
|
||||
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Error during log target change: " + e.getMessage(), e);
|
||||
isShuttingDown = false; // Reset flag on error
|
||||
}
|
||||
}, "LogService-ChangeTarget").start();
|
||||
}
|
||||
|
||||
private void createNotification() {
|
||||
manager = (NotificationManager) ctx.getSystemService(Context.NOTIFICATION_SERVICE);
|
||||
manager.cancel(109);
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
NotificationChannel notificationChannel = new NotificationChannel(NOTIFICATION_CHANNEL_ID, ctx.getString(R.string.firewall_log_notify), NotificationManager.IMPORTANCE_DEFAULT);
|
||||
notificationChannel.setLockscreenVisibility(Notification.VISIBILITY_PRIVATE);
|
||||
assert manager != null;
|
||||
if (G.getNotificationPriority() == 0) {
|
||||
notificationChannel.setImportance(NotificationManager.IMPORTANCE_DEFAULT);
|
||||
}
|
||||
notificationChannel.setSound(null, null);
|
||||
notificationChannel.setShowBadge(false);
|
||||
notificationChannel.enableLights(false);
|
||||
notificationChannel.enableVibration(false);
|
||||
manager.createNotificationChannel(notificationChannel);
|
||||
}
|
||||
|
||||
Intent appIntent = new Intent(ctx, LogActivity.class);
|
||||
appIntent.setAction(Intent.ACTION_MAIN);
|
||||
appIntent.addCategory(Intent.CATEGORY_LAUNCHER);
|
||||
appIntent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP | Intent.FLAG_ACTIVITY_CLEAR_TOP);
|
||||
|
||||
PendingIntent notifyPendingIntent = PendingIntent.getActivity(ctx, 0, appIntent, PendingIntent.FLAG_IMMUTABLE);
|
||||
notificationBuilder = new NotificationCompat.Builder(ctx, NOTIFICATION_CHANNEL_ID);
|
||||
notificationBuilder.setContentIntent(notifyPendingIntent);
|
||||
}
|
||||
|
||||
|
||||
private void initiateLogWatcher(String logCommand) {
|
||||
// Clear/remove existing tasks
|
||||
if(executorService != null) {
|
||||
executorService.shutdownNow();
|
||||
}
|
||||
|
||||
//make sure it's enabled first
|
||||
if(G.enableLogService() && !isShuttingDown) {
|
||||
if (executorService == null) {
|
||||
executorService = Executors.newCachedThreadPool();
|
||||
}
|
||||
if (logWatcherShell == null) {
|
||||
try {
|
||||
logWatcherShell = Shell.Builder.create()
|
||||
.setFlags(Shell.FLAG_REDIRECT_STDERR)
|
||||
.setTimeout(10) // 10 second timeout for shell creation
|
||||
.build();
|
||||
} catch (NoShellException e) {
|
||||
Log.e(TAG, "Failed to create root shell for log watcher", e);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
Log.i(TAG, "Starting log watcher with command: " + logCommand);
|
||||
try {
|
||||
if (executorService == null || executorService.isShutdown() || executorService.isTerminated()) {
|
||||
Log.w(TAG, "ExecutorService is not available, recreating...");
|
||||
if (executorService != null) {
|
||||
executorService.shutdownNow();
|
||||
}
|
||||
executorService = Executors.newCachedThreadPool();
|
||||
}
|
||||
|
||||
logWatcherShell.newJob()
|
||||
.add(logCommand)
|
||||
.to(callbackList)
|
||||
.submit(executorService, out -> {
|
||||
try {
|
||||
Log.i(TAG, "Log watcher finished with code: " + out.getCode());
|
||||
|
||||
// Don't restart if service is shutting down
|
||||
if (isShuttingDown) {
|
||||
Log.i(TAG, "Service is shutting down, not restarting log watcher");
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle different exit scenarios
|
||||
if (out.getCode() == 0) {
|
||||
// Normal termination, try restart after delay
|
||||
Log.w(TAG, "Log watcher terminated normally, restarting...");
|
||||
restartWatcher(logPath);
|
||||
} else if (out.getCode() == 130) {
|
||||
// SIGINT - likely manual termination
|
||||
Log.i(TAG, "Log watcher interrupted (SIGINT)");
|
||||
} else if (out.getCode() == 137) {
|
||||
// SIGKILL - system killed the process
|
||||
Log.w(TAG, "Log watcher killed by system, restarting...");
|
||||
restartWatcher(logPath);
|
||||
} else {
|
||||
// Other error codes, try fallback method
|
||||
Log.w(TAG, "Log watcher failed with code " + out.getCode() + ", trying fallback");
|
||||
tryFallbackLogMethod();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
if (e.getMessage() != null && e.getMessage().contains("RejectedExecutionException")) {
|
||||
Log.w(TAG, "Caught SuperUser library RejectedExecutionException during app shutdown, ignoring to prevent crash");
|
||||
} else {
|
||||
Log.e(TAG, "Error in log watcher completion callback: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
});
|
||||
} catch(Exception e) {
|
||||
Log.e(TAG, "Unable to start log service: " + e.getMessage(), e);
|
||||
if (e.getMessage() != null && (e.getMessage().contains("rejected") || e.getMessage().contains("terminated"))) {
|
||||
Log.w(TAG, "ExecutorService rejected task, recreating executor and retrying...");
|
||||
try {
|
||||
if (executorService != null) {
|
||||
executorService.shutdownNow();
|
||||
}
|
||||
executorService = Executors.newCachedThreadPool();
|
||||
initiateLogWatcher(logPath);
|
||||
return;
|
||||
} catch (Exception retryException) {
|
||||
Log.e(TAG, "Retry also failed: " + retryException.getMessage(), retryException);
|
||||
}
|
||||
}
|
||||
tryFallbackLogMethod();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Try a fallback log reading method if the primary method fails
|
||||
*/
|
||||
private void tryFallbackLogMethod() {
|
||||
if (isShuttingDown) {
|
||||
return;
|
||||
}
|
||||
|
||||
Log.i(TAG, "Attempting fallback to basic /proc/kmsg reading");
|
||||
String fallbackCommand = "cat /proc/kmsg | grep --line-buffered '{AFL}'";
|
||||
|
||||
final Handler handler = new Handler(Looper.getMainLooper());
|
||||
handler.postDelayed(() -> {
|
||||
if (G.enableLogService() && !isShuttingDown) {
|
||||
Log.i(TAG, "Starting fallback log watcher");
|
||||
initiateLogWatcherWithCommand(fallbackCommand);
|
||||
}
|
||||
}, 3000);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initiate log watcher with a specific command (used for fallback)
|
||||
*/
|
||||
private void initiateLogWatcherWithCommand(String logCommand) {
|
||||
if(G.enableLogService() && logWatcherShell != null && !isShuttingDown) {
|
||||
try {
|
||||
if (executorService == null || executorService.isShutdown() || executorService.isTerminated()) {
|
||||
Log.w(TAG, "ExecutorService is not available for fallback, recreating...");
|
||||
if (executorService != null) {
|
||||
executorService.shutdownNow();
|
||||
}
|
||||
executorService = Executors.newCachedThreadPool();
|
||||
}
|
||||
|
||||
logWatcherShell.newJob()
|
||||
.add(logCommand)
|
||||
.to(callbackList)
|
||||
.submit(executorService, out -> {
|
||||
Log.i(TAG, "Fallback log watcher finished with code: " + out.getCode());
|
||||
if (out.getCode() == 0) {
|
||||
restartWatcher(logCommand);
|
||||
}
|
||||
});
|
||||
} catch(Exception e) {
|
||||
Log.e(TAG, "Fallback log service also failed: " + e.getMessage(), e);
|
||||
if (e.getMessage() != null && (e.getMessage().contains("rejected") || e.getMessage().contains("terminated"))) {
|
||||
Log.w(TAG, "ExecutorService rejected fallback task, service may be shutting down");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void storeLogInfo(String line, Context context) {
|
||||
try {
|
||||
|
||||
LogEvent event = new LogEvent(LogInfo.parseLogs(line, context, "{AFL}", 0), context);
|
||||
if(event.logInfo != null) {
|
||||
store(event.logInfo, event.ctx);
|
||||
showNotification(event.logInfo);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void checkBatteryOptimize() {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
final Intent doze = new Intent(Settings.ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS);
|
||||
if (Api.batteryOptimized(this) && getPackageManager().resolveActivity(doze, 0) != null) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static PrettyTime prettyTime;
|
||||
|
||||
public static String pretty(Date date) {
|
||||
if (prettyTime == null) {
|
||||
prettyTime = new PrettyTime(new Locale(G.locale()));
|
||||
for (TimeUnit t : prettyTime.getUnits()) {
|
||||
if (t instanceof JustNow) {
|
||||
prettyTime.removeUnit(t);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
prettyTime.setReference(date);
|
||||
return prettyTime.format(new Date(0));
|
||||
}
|
||||
|
||||
@SuppressLint("RestrictedApi")
|
||||
private void showNotification(LogInfo logInfo) {
|
||||
if(G.enableLogService() && G.canShow(logInfo.uid) && logInfo.uid != -100) {
|
||||
manager.notify(109, notificationBuilder.setOngoing(false)
|
||||
.setCategory(NotificationCompat.CATEGORY_EVENT)
|
||||
.setVisibility(NotificationCompat.VISIBILITY_SECRET)
|
||||
.setContentText(logInfo.uidString)
|
||||
.setSmallIcon(R.drawable.ic_block_black_24dp)
|
||||
.setAutoCancel(true)
|
||||
.build());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
private static void store(final LogInfo logInfo, Context context) {
|
||||
try {
|
||||
if (logInfo != null) {
|
||||
LogData data = new LogData();
|
||||
data.setDst(logInfo.dst);
|
||||
data.setOut(logInfo.out);
|
||||
data.setSrc(logInfo.src);
|
||||
data.setDpt(logInfo.dpt);
|
||||
data.setIn(logInfo.in);
|
||||
data.setLen(logInfo.len);
|
||||
data.setProto(logInfo.proto);
|
||||
data.setTimestamp(System.currentTimeMillis());
|
||||
data.setSpt(logInfo.spt);
|
||||
data.setUid(logInfo.uid);
|
||||
data.setAppName(logInfo.appName);
|
||||
data.setType(logInfo.type);
|
||||
if (G.isDoKey(context) || G.isDonate()) {
|
||||
try {
|
||||
data.setHostname(logInfo.host != null ? logInfo.host : "");
|
||||
} catch (Exception e) {
|
||||
}
|
||||
}
|
||||
data.setType(0);
|
||||
FlowManager.getDatabase(LogDatabase.class).beginTransactionAsync(databaseWrapper ->
|
||||
data.save(databaseWrapper)).build().execute();
|
||||
}
|
||||
} catch (IllegalStateException e) {
|
||||
if (e.getMessage().contains("connection pool has been closed")) {
|
||||
//reconnect logic
|
||||
try {
|
||||
FlowManager.init(new FlowConfig.Builder(context).build());
|
||||
store(logInfo,context);
|
||||
} catch (Exception de) {
|
||||
Log.e(TAG, "Exception while saving log data:" + e.getLocalizedMessage(), de);
|
||||
}
|
||||
}
|
||||
Log.e(TAG, "Exception while saving log data:" + e.getLocalizedMessage(), e);
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Exception while saving log data:" + e.getLocalizedMessage(),e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
|
||||
// Set shutdown flag to prevent new tasks from starting
|
||||
isShuttingDown = true;
|
||||
|
||||
// Close log watcher shell first to stop generating new tasks
|
||||
if(logWatcherShell != null) {
|
||||
try {
|
||||
logWatcherShell.close();
|
||||
} catch (Exception e) {
|
||||
Log.w(TAG, "Error closing log watcher shell: " + e.getMessage());
|
||||
}
|
||||
logWatcherShell = null;
|
||||
}
|
||||
|
||||
// Shutdown executor service gracefully
|
||||
if(executorService != null) {
|
||||
try {
|
||||
executorService.shutdown(); // Try graceful shutdown first
|
||||
if (!executorService.awaitTermination(2000, java.util.concurrent.TimeUnit.MILLISECONDS)) {
|
||||
Log.w(TAG, "ExecutorService did not terminate gracefully, forcing shutdown");
|
||||
executorService.shutdownNow();
|
||||
// Wait a bit more for tasks to respond to being cancelled
|
||||
if (!executorService.awaitTermination(1000, java.util.concurrent.TimeUnit.MILLISECONDS)) {
|
||||
Log.w(TAG, "ExecutorService did not terminate after force shutdown");
|
||||
}
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
Log.w(TAG, "Interrupted while shutting down ExecutorService");
|
||||
executorService.shutdownNow();
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
}
|
||||
executorService = null;
|
||||
|
||||
// Clean up temporary files
|
||||
cleanupTempFiles();
|
||||
|
||||
super.onDestroy();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTaskRemoved(Intent rootIntent) {
|
||||
super.onTaskRemoved(rootIntent);
|
||||
|
||||
// Restart service if log service is still enabled
|
||||
if (G.enableLogService()) {
|
||||
Intent intent = new Intent(getApplicationContext(), LogService.class);
|
||||
PendingIntent pendingIntent = PendingIntent.getService(this, 1, intent, PendingIntent.FLAG_MUTABLE);
|
||||
AlarmManager alarmManager = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
|
||||
alarmManager.set(AlarmManager.RTC_WAKEUP, SystemClock.elapsedRealtime() + 5000, pendingIntent);
|
||||
}
|
||||
|
||||
// Clean up resources gracefully
|
||||
if(logWatcherShell != null && !logWatcherShell.isAlive()) {
|
||||
try {
|
||||
logWatcherShell.close();
|
||||
} catch (Exception e) {
|
||||
Log.w(TAG, "Error closing shell in onTaskRemoved: " + e.getMessage());
|
||||
}
|
||||
logWatcherShell = null;
|
||||
}
|
||||
|
||||
if(executorService != null) {
|
||||
try {
|
||||
executorService.shutdown();
|
||||
if (!executorService.awaitTermination(1000, java.util.concurrent.TimeUnit.MILLISECONDS)) {
|
||||
executorService.shutdownNow();
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
executorService.shutdownNow();
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
}
|
||||
executorService = null;
|
||||
|
||||
cleanupTempFiles();
|
||||
}
|
||||
}
|
||||
184
app/src/main/java/dev/ukanth/ufirewall/service/RootCommand.java
Normal file
184
app/src/main/java/dev/ukanth/ufirewall/service/RootCommand.java
Normal file
|
|
@ -0,0 +1,184 @@
|
|||
package dev.ukanth.ufirewall.service;
|
||||
|
||||
import static dev.ukanth.ufirewall.service.RootShellService.NO_TOAST;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Created by ukanth on 21/10/17.
|
||||
*/
|
||||
|
||||
public class RootCommand {
|
||||
public Callback cb = null;
|
||||
public int successToast = NO_TOAST;
|
||||
public int failureToast = NO_TOAST;
|
||||
public boolean reopenShell = false;
|
||||
public int retryExitCode = -1;
|
||||
public int commandIndex;
|
||||
public boolean ignoreExitCode;
|
||||
public Date startTime;
|
||||
public int retryCount;
|
||||
public StringBuilder res;
|
||||
public String lastCommand;
|
||||
public StringBuilder lastCommandResult;
|
||||
public int exitCode;
|
||||
public boolean done = false;
|
||||
public int hash = -1;
|
||||
public boolean isv6 = false;
|
||||
|
||||
private List<String> commmands;
|
||||
|
||||
private RootShellService rootShellService;
|
||||
private RootShellService2 rootShellService2;
|
||||
|
||||
public RootCommand() {
|
||||
rootShellService = new RootShellService();
|
||||
rootShellService2 = new RootShellService2();
|
||||
}
|
||||
|
||||
|
||||
/*@Override
|
||||
public RootCommand clone() {
|
||||
RootCommand rootCommand = null;
|
||||
try {
|
||||
rootCommand = (RootCommand) super.clone();
|
||||
rootCommand.isv6 = true;
|
||||
} catch (CloneNotSupportedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return rootCommand;
|
||||
}*/
|
||||
|
||||
public List<String> getCommmands() {
|
||||
return commmands;
|
||||
}
|
||||
|
||||
public void setCommmands(List<String> commmands) {
|
||||
this.commmands = commmands;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set callback to run after command completion
|
||||
*
|
||||
* @param cb Callback object, with cbFunc() populated
|
||||
* @return RootCommand builder object
|
||||
*/
|
||||
public RootCommand setCallback(Callback cb) {
|
||||
this.cb = cb;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tell RootShell to display a toast message on success
|
||||
*
|
||||
* @param resId Resource ID of the toast string
|
||||
* @return RootCommand builder object
|
||||
*/
|
||||
public RootCommand setSuccessToast(int resId) {
|
||||
this.successToast = resId;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tell RootShell to display a toast message on failure
|
||||
*
|
||||
* @param resId Resource ID of the toast string
|
||||
* @return RootCommand builder object
|
||||
*/
|
||||
public RootCommand setFailureToast(int resId) {
|
||||
this.failureToast = resId;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tell RootShell whether or not it should try to open a new root shell if the last attempt
|
||||
* died. To avoid "thrashing" it might be best to only try this in response to a user
|
||||
* request
|
||||
*
|
||||
* @param reopenShell true to attempt reopening a failed shell
|
||||
* @return RootCommand builder object
|
||||
*/
|
||||
public RootCommand setReopenShell(boolean reopenShell) {
|
||||
this.reopenShell = reopenShell;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Capture the command output in this.res
|
||||
*
|
||||
* @param enableLog true to enable logging
|
||||
* @return RootCommand builder object
|
||||
*/
|
||||
public RootCommand setLogging(boolean enableLog) {
|
||||
if (enableLog) {
|
||||
this.res = new StringBuilder();
|
||||
} else {
|
||||
this.res = null;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retry a failed command on a specific exit code
|
||||
*
|
||||
* @param retryExitCode code that indicates a transient failure
|
||||
* @return RootCommand builder object
|
||||
*/
|
||||
public RootCommand setRetryExitCode(int retryExitCode) {
|
||||
this.retryExitCode = retryExitCode;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Run a series of commands as root; call cb.cbFunc() when complete
|
||||
*
|
||||
* @param ctx Context object used to create toasts
|
||||
* @param script List of commands to run as root
|
||||
*/
|
||||
public final void run(Context ctx, List<String> script) {
|
||||
if (rootShellService == null) {
|
||||
rootShellService = new RootShellService();
|
||||
}
|
||||
rootShellService.runScriptAsRoot(ctx, script, this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Run a series of commands as root; call cb.cbFunc() when complete
|
||||
*
|
||||
* @param ctx Context object used to create toasts
|
||||
* @param script List of commands to run as root
|
||||
*/
|
||||
public final void run(Context ctx, List<String> script, boolean isv6) {
|
||||
if (rootShellService2 == null) {
|
||||
rootShellService2 = new RootShellService2();
|
||||
}
|
||||
|
||||
rootShellService2.runScriptAsRoot(ctx, script, this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Run a single command as root; call cb.cbFunc() when complete
|
||||
*
|
||||
* @param ctx Context object used to create toasts
|
||||
* @param cmd Command to run as root
|
||||
*/
|
||||
public final void run(Context ctx, String cmd) {
|
||||
if (rootShellService == null) {
|
||||
rootShellService = new RootShellService();
|
||||
}
|
||||
List<String> script = new ArrayList<String>();
|
||||
script.add(cmd);
|
||||
rootShellService.runScriptAsRoot(ctx, script, this);
|
||||
}
|
||||
|
||||
public static abstract class Callback {
|
||||
/**
|
||||
* Optional user-specified callback
|
||||
*/
|
||||
public abstract void cbFunc(RootCommand state);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,447 @@
|
|||
/**
|
||||
* Keep a persistent root shell running in the background
|
||||
* <p>
|
||||
* Copyright (C) 2013 Kevin Cernekee
|
||||
* <p>
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
* <p>
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
* <p>
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* @author Kevin Cernekee
|
||||
* @version 1.0
|
||||
*/
|
||||
|
||||
package dev.ukanth.ufirewall.service;
|
||||
|
||||
import static dev.ukanth.ufirewall.service.RootShellService.ShellState.INIT;
|
||||
|
||||
import android.app.Notification;
|
||||
import android.app.NotificationChannel;
|
||||
import android.app.NotificationManager;
|
||||
import android.app.PendingIntent;
|
||||
import android.app.Service;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Build;
|
||||
import android.os.IBinder;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.core.app.NotificationCompat;
|
||||
import androidx.core.app.TaskStackBuilder;
|
||||
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.ListIterator;
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.Timer;
|
||||
import java.util.TimerTask;
|
||||
|
||||
import dev.ukanth.ufirewall.Api;
|
||||
import dev.ukanth.ufirewall.MainActivity;
|
||||
import dev.ukanth.ufirewall.R;
|
||||
import dev.ukanth.ufirewall.log.Log;
|
||||
import dev.ukanth.ufirewall.util.G;
|
||||
import eu.chainfire.libsuperuser.Debug;
|
||||
import eu.chainfire.libsuperuser.Shell;
|
||||
|
||||
|
||||
public class RootShellService extends Service implements Cloneable {
|
||||
|
||||
public static final String TAG = "AFWall";
|
||||
public static final int NOTIFICATION_ID = 33347;
|
||||
public static final int EXIT_NO_ROOT_ACCESS = -1;
|
||||
public static final int NO_TOAST = -1;
|
||||
/* write command completion times to logcat */
|
||||
private static final boolean enableProfiling = false;
|
||||
//number of retries - increase the count
|
||||
private final static int MAX_RETRIES = 10;
|
||||
private static Shell.Interactive rootSession;
|
||||
private Context mContext;
|
||||
private NotificationManager notificationManager;
|
||||
private static ShellState rootState = INIT;
|
||||
private final LinkedList<RootCommand> waitQueue = new LinkedList<>();
|
||||
|
||||
private void complete(final RootCommand state, int exitCode) {
|
||||
if (enableProfiling) {
|
||||
Log.d(TAG, "RootShell: " + state.getCommmands().size() + " commands completed in " +
|
||||
(new Date().getTime() - state.startTime.getTime()) + " ms");
|
||||
}
|
||||
state.exitCode = exitCode;
|
||||
state.done = true;
|
||||
if (state.cb != null) {
|
||||
state.cb.cbFunc(state);
|
||||
}
|
||||
|
||||
if (exitCode == 0 && state.successToast != NO_TOAST) {
|
||||
Api.sendToastBroadcast(mContext, mContext.getString(state.successToast));
|
||||
} else if (exitCode != 0 && state.failureToast != NO_TOAST) {
|
||||
Api.sendToastBroadcast(mContext, mContext.getString(state.failureToast));
|
||||
}
|
||||
|
||||
if (notificationManager != null) {
|
||||
notificationManager.cancel(NOTIFICATION_ID);
|
||||
}
|
||||
}
|
||||
|
||||
private void runNextSubmission() {
|
||||
|
||||
do {
|
||||
RootCommand state;
|
||||
try {
|
||||
state = waitQueue.remove();
|
||||
} catch (NoSuchElementException e) {
|
||||
// nothing left to do
|
||||
if (rootState == ShellState.BUSY) {
|
||||
rootState = ShellState.READY;
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (state != null) {
|
||||
//same as last one. ignore it
|
||||
if (enableProfiling) {
|
||||
state.startTime = new Date();
|
||||
}
|
||||
if (rootState == ShellState.FAIL) {
|
||||
// if we don't have root, abort all queued commands
|
||||
complete(state, EXIT_NO_ROOT_ACCESS);
|
||||
//continue;
|
||||
} else if (rootState == ShellState.READY) {
|
||||
rootState = ShellState.BUSY;
|
||||
if (G.isRun()) {
|
||||
createNotification(mContext);
|
||||
}
|
||||
processCommands(state);
|
||||
}
|
||||
}
|
||||
} while (false);
|
||||
}
|
||||
|
||||
private void processCommands(final RootCommand state) {
|
||||
if (state.commandIndex < state.getCommmands().size() && state.getCommmands().get(state.commandIndex) != null) {
|
||||
String command = state.getCommmands().get(state.commandIndex);
|
||||
//Log.i("AFWall", command);
|
||||
|
||||
//not to send conflicting status
|
||||
if (!state.isv6) {
|
||||
sendUpdate(state);
|
||||
}
|
||||
if (command != null) {
|
||||
state.ignoreExitCode = false;
|
||||
|
||||
if (command.startsWith("#NOCHK# ")) {
|
||||
command = command.replaceFirst("#NOCHK# ", "");
|
||||
state.ignoreExitCode = true;
|
||||
}
|
||||
state.lastCommand = command;
|
||||
state.lastCommandResult = new StringBuilder();
|
||||
try {
|
||||
// Check if shell is still valid before executing command
|
||||
if (rootSession == null || !rootSession.isRunning() ) {
|
||||
rootState = ShellState.FAIL;
|
||||
complete(state, -1);
|
||||
return;
|
||||
}
|
||||
|
||||
rootSession.addCommand(command, 0, (Shell.OnCommandResultListener2) (commandCode, exitCode, output, STDERR)-> {
|
||||
ListIterator<String> iter = output.listIterator();
|
||||
while (iter.hasNext()) {
|
||||
String line = iter.next();
|
||||
if (line != null && !line.equals("")) {
|
||||
if (state.res != null) {
|
||||
state.res.append(line).append("\n");
|
||||
}
|
||||
state.lastCommandResult.append(line).append("\n");
|
||||
}
|
||||
}
|
||||
// Special handling for exit code 126 (command not executable) - fallback to system iptables
|
||||
if (exitCode == 126 && shouldFallbackToSystem(state)) {
|
||||
Log.w(TAG, "Built-in iptables failed with exit 126, attempting fallback to system iptables");
|
||||
// Remember that built-in iptables failed for future preference
|
||||
G.setBuiltinIptablesFailed(true);
|
||||
fallbackToSystemBinary(state);
|
||||
processCommands(state);
|
||||
return;
|
||||
}
|
||||
|
||||
if (exitCode >= 0 && exitCode == state.retryExitCode && state.retryCount < MAX_RETRIES) {
|
||||
//lets wait for few ms before trying ?
|
||||
state.retryCount++;
|
||||
|
||||
// Add exponential backoff delay for retries
|
||||
try {
|
||||
Thread.sleep(100 * state.retryCount);
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
|
||||
processCommands(state);
|
||||
return;
|
||||
}
|
||||
|
||||
state.commandIndex++;
|
||||
state.retryCount = 0;
|
||||
|
||||
boolean errorExit = exitCode != 0 && !state.ignoreExitCode;
|
||||
if (state.commandIndex >= state.getCommmands().size() || errorExit) {
|
||||
complete(state, exitCode);
|
||||
if (exitCode < 0) {
|
||||
rootState = ShellState.FAIL;
|
||||
Log.e(TAG, "libsuperuser error " + exitCode + " on command '" + state.lastCommand + "'");
|
||||
} else {
|
||||
if (errorExit) {
|
||||
Log.i(TAG, "command '" + state.lastCommand + "' exited with status " + exitCode +
|
||||
"\nOutput:\n" + state.lastCommandResult);
|
||||
}
|
||||
rootState = ShellState.READY;
|
||||
}
|
||||
runNextSubmission();
|
||||
} else {
|
||||
processCommands(state);
|
||||
}
|
||||
});
|
||||
} catch (NullPointerException | ArrayIndexOutOfBoundsException e) {
|
||||
Log.e(TAG, e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
complete(state, 0);
|
||||
}
|
||||
}
|
||||
|
||||
private void sendUpdate(final RootCommand state2) {
|
||||
new Thread(() -> {
|
||||
Intent broadcastIntent = new Intent();
|
||||
broadcastIntent.setAction("UPDATEUI4");
|
||||
broadcastIntent.putExtra("SIZE", state2.getCommmands().size());
|
||||
broadcastIntent.putExtra("INDEX", state2.commandIndex);
|
||||
LocalBroadcastManager.getInstance(mContext).sendBroadcast(broadcastIntent);
|
||||
}).start();
|
||||
}
|
||||
|
||||
private void createNotification(Context context) {
|
||||
|
||||
String CHANNEL_ID = "firewall.apply";
|
||||
notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
|
||||
NotificationCompat.Builder builder = new NotificationCompat.Builder(context, CHANNEL_ID);
|
||||
|
||||
Intent appIntent = new Intent(context, MainActivity.class);
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
/* Create or update. */
|
||||
NotificationChannel channel = new NotificationChannel(CHANNEL_ID, context.getString(R.string.runNotification),
|
||||
NotificationManager.IMPORTANCE_LOW);
|
||||
channel.setDescription("");
|
||||
channel.setShowBadge(false);
|
||||
channel.setSound(null, null);
|
||||
channel.enableLights(false);
|
||||
channel.enableVibration(false);
|
||||
notificationManager.createNotificationChannel(channel);
|
||||
}
|
||||
|
||||
TaskStackBuilder stackBuilder = TaskStackBuilder.create(context);
|
||||
stackBuilder.addParentStack(MainActivity.class);
|
||||
stackBuilder.addNextIntent(appIntent);
|
||||
PendingIntent resultPendingIntent = stackBuilder.getPendingIntent(0, PendingIntent.FLAG_IMMUTABLE);
|
||||
builder.setContentIntent(resultPendingIntent);
|
||||
|
||||
|
||||
int notifyType = G.getNotificationPriority();
|
||||
|
||||
Notification notification = builder.setSmallIcon(R.drawable.ic_apply)
|
||||
.setAutoCancel(false)
|
||||
.setContentTitle(context.getString(R.string.applying_rules))
|
||||
.setTicker(context.getString(R.string.app_name))
|
||||
.setChannelId(CHANNEL_ID)
|
||||
.setCategory(Notification.CATEGORY_SERVICE)
|
||||
.setVisibility(NotificationCompat.VISIBILITY_SECRET)
|
||||
.setOnlyAlertOnce(true)
|
||||
.setPriority(NotificationCompat.PRIORITY_LOW)
|
||||
.setPriority(NotificationManager.IMPORTANCE_LOW)
|
||||
.setContentText("").build();
|
||||
/*switch (notifyType) {
|
||||
case 0:
|
||||
notification.priority = NotificationCompat.PRIORITY_LOW;
|
||||
break;
|
||||
case 1:
|
||||
notification.priority = NotificationCompat.PRIORITY_MIN;
|
||||
break;
|
||||
}*/
|
||||
builder.setProgress(0, 0, true);
|
||||
notificationManager.notify(NOTIFICATION_ID, notification);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int onStartCommand(Intent intent, int flags, int startId) {
|
||||
if (intent == null) { // if crash restart...
|
||||
Log.i(TAG, "Restarting RootShell...");
|
||||
List<String> cmds = new ArrayList<>();
|
||||
cmds.add("true");
|
||||
new RootCommand().setFailureToast(R.string.error_su)
|
||||
.setReopenShell(true).run(getApplicationContext(), cmds);
|
||||
}
|
||||
return Service.START_STICKY;
|
||||
}
|
||||
|
||||
private void setupLogging() {
|
||||
Debug.setDebug(false);
|
||||
Debug.setLogTypeEnabled(Debug.LOG_ALL, false);
|
||||
Debug.setLogTypeEnabled(Debug.LOG_GENERAL, false);
|
||||
Debug.setSanityChecksEnabled(false);
|
||||
Debug.setOnLogListener((type, typeIndicator, message) -> Log.i(TAG, "[libsuperuser] " + message));
|
||||
}
|
||||
|
||||
|
||||
private synchronized void startShellInBackground() {
|
||||
Log.d(TAG, "Starting root shell(4)...");
|
||||
setupLogging();
|
||||
//start only rootSession is null or closed
|
||||
if (rootSession == null || !rootSession.isRunning()) {
|
||||
if (rootSession != null && !rootSession.isRunning()) {
|
||||
rootSession = null;
|
||||
}
|
||||
|
||||
rootSession = new Shell.Builder().
|
||||
useSU().
|
||||
setWatchdogTimeout(5).
|
||||
open((success, reason) -> {
|
||||
if (reason < 0) {
|
||||
Log.e(TAG, "Can't open root shell: exitCode " + reason);
|
||||
rootState = ShellState.FAIL;
|
||||
} else {
|
||||
Log.d(TAG, "Root shell(4) is open");
|
||||
rootState = ShellState.READY;
|
||||
}
|
||||
runNextSubmission();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void reOpenShell(Context context) {
|
||||
if (rootState == null || rootState != ShellState.READY || rootState == ShellState.FAIL) {
|
||||
if (notificationManager != null) {
|
||||
notificationManager.cancel(NOTIFICATION_ID);
|
||||
}
|
||||
rootState = ShellState.BUSY;
|
||||
startShellInBackground();
|
||||
try {
|
||||
Intent intent = new Intent(context, RootShellService.class);
|
||||
context.startService(intent);
|
||||
} catch (Exception e){
|
||||
Log.e(TAG, e.getMessage(),e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void runScriptAsRoot(Context ctx, List<String> cmds, RootCommand state) {
|
||||
state.setCommmands(cmds);
|
||||
state.commandIndex = 0;
|
||||
state.retryCount = 0;
|
||||
if (mContext == null) {
|
||||
mContext = ctx.getApplicationContext();
|
||||
}
|
||||
//already in memory and applied
|
||||
//add it to queue
|
||||
|
||||
waitQueue.add(state);
|
||||
|
||||
if (rootState == INIT || (rootState == ShellState.FAIL && state.reopenShell)) {
|
||||
reOpenShell(ctx);
|
||||
} else if (rootState != ShellState.BUSY) {
|
||||
runNextSubmission();
|
||||
} else {
|
||||
new Timer().schedule(new TimerTask() {
|
||||
@Override
|
||||
public void run() {
|
||||
Log.i(TAG, "State of rootShell(4): " + rootState);
|
||||
if (rootState == ShellState.BUSY) {
|
||||
//try resetting state to READY forcefully
|
||||
Log.i(TAG, "Forcefully changing the state " + rootState);
|
||||
rootState = ShellState.READY;
|
||||
}
|
||||
runNextSubmission();
|
||||
}
|
||||
}, 1000);
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public IBinder onBind(Intent intent) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if fallback to system binary should be attempted for exit code 126
|
||||
* Only fallback once per command to avoid infinite loops
|
||||
*/
|
||||
private boolean shouldFallbackToSystem(RootCommand state) {
|
||||
if (state.lastCommand == null || mContext == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if command contains built-in iptables path and hasn't been fallback attempted
|
||||
String builtinDir = mContext.getDir("bin", 0).getAbsolutePath();
|
||||
return state.lastCommand.contains(builtinDir) &&
|
||||
!state.lastCommand.contains("__FALLBACK_ATTEMPTED__");
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace built-in iptables/ip6tables paths with system paths in the current command
|
||||
*/
|
||||
private void fallbackToSystemBinary(RootCommand state) {
|
||||
if (state.lastCommand == null || mContext == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
String builtinDir = mContext.getDir("bin", 0).getAbsolutePath();
|
||||
String originalCommand = state.lastCommand;
|
||||
|
||||
// Try to find system iptables
|
||||
String systemIptables = Api.findSystemBinary("iptables");
|
||||
String systemIp6tables = Api.findSystemBinary("ip6tables");
|
||||
|
||||
if (systemIptables != null || systemIp6tables != null) {
|
||||
String updatedCommand = originalCommand;
|
||||
|
||||
// Replace built-in paths with system paths
|
||||
if (systemIptables != null) {
|
||||
updatedCommand = updatedCommand.replace(builtinDir + "/iptables", systemIptables);
|
||||
}
|
||||
if (systemIp6tables != null) {
|
||||
updatedCommand = updatedCommand.replace(builtinDir + "/ip6tables", systemIp6tables);
|
||||
}
|
||||
|
||||
// Mark as fallback attempted to prevent infinite loops
|
||||
updatedCommand += " # __FALLBACK_ATTEMPTED__";
|
||||
|
||||
// Update the command in the current state
|
||||
List<String> commands = state.getCommmands();
|
||||
if (state.commandIndex < commands.size()) {
|
||||
commands.set(state.commandIndex, updatedCommand);
|
||||
Log.i(TAG, "Fallback applied: " + originalCommand + " -> " + updatedCommand);
|
||||
}
|
||||
} else {
|
||||
Log.w(TAG, "No system iptables found for fallback");
|
||||
}
|
||||
}
|
||||
|
||||
public enum ShellState {
|
||||
INIT,
|
||||
READY,
|
||||
BUSY,
|
||||
FAIL
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,420 @@
|
|||
/**
|
||||
* Keep a persistent root shell running in the background
|
||||
* <p>
|
||||
* Copyright (C) 2013 Kevin Cernekee
|
||||
* <p>
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
* <p>
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
* <p>
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* @author Kevin Cernekee
|
||||
* @version 1.0
|
||||
*/
|
||||
|
||||
package dev.ukanth.ufirewall.service;
|
||||
|
||||
import static dev.ukanth.ufirewall.service.RootShellService2.ShellState2.INIT;
|
||||
|
||||
import android.app.Notification;
|
||||
import android.app.NotificationChannel;
|
||||
import android.app.NotificationManager;
|
||||
import android.app.PendingIntent;
|
||||
import android.app.Service;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Build;
|
||||
import android.os.IBinder;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.core.app.NotificationCompat;
|
||||
import androidx.core.app.TaskStackBuilder;
|
||||
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.ListIterator;
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.Timer;
|
||||
import java.util.TimerTask;
|
||||
|
||||
import dev.ukanth.ufirewall.Api;
|
||||
import dev.ukanth.ufirewall.MainActivity;
|
||||
import dev.ukanth.ufirewall.R;
|
||||
import dev.ukanth.ufirewall.log.Log;
|
||||
import dev.ukanth.ufirewall.util.G;
|
||||
import eu.chainfire.libsuperuser.Debug;
|
||||
import eu.chainfire.libsuperuser.Shell;
|
||||
|
||||
|
||||
public class RootShellService2 extends Service {
|
||||
|
||||
public static final String TAG = "AFWall6";
|
||||
public static final int NOTIFICATION_ID = 33347;
|
||||
public static final int EXIT_NO_ROOT_ACCESS = -1;
|
||||
public static final int NO_TOAST = -1;
|
||||
/* write command completion times to logcat */
|
||||
private static final boolean enableProfiling = false;
|
||||
//number of retries - increase the count
|
||||
private final static int MAX_RETRIES = 10;
|
||||
private static Shell.Interactive rootSession2;
|
||||
private Context mContext;
|
||||
private NotificationManager notificationManager;
|
||||
private static ShellState2 rootState = INIT;
|
||||
private final LinkedList<RootCommand> waitQueue = new LinkedList<>();
|
||||
|
||||
private void complete(final RootCommand state, int exitCode) {
|
||||
if (enableProfiling) {
|
||||
Log.d(TAG, "RootShell6: " + state.getCommmands().size() + " commands completed in " +
|
||||
(new Date().getTime() - state.startTime.getTime()) + " ms");
|
||||
}
|
||||
state.exitCode = exitCode;
|
||||
state.done = true;
|
||||
if (state.cb != null) {
|
||||
state.cb.cbFunc(state);
|
||||
}
|
||||
|
||||
if (exitCode == 0 && state.successToast != NO_TOAST) {
|
||||
Api.sendToastBroadcast(this.mContext, mContext.getString(state.successToast));
|
||||
} else if (exitCode != 0 && state.failureToast != NO_TOAST) {
|
||||
Api.sendToastBroadcast(mContext, mContext.getString(state.failureToast));
|
||||
}
|
||||
|
||||
if (notificationManager != null) {
|
||||
notificationManager.cancel(NOTIFICATION_ID);
|
||||
}
|
||||
}
|
||||
|
||||
private void runNextSubmission() {
|
||||
|
||||
do {
|
||||
RootCommand state;
|
||||
try {
|
||||
state = waitQueue.remove();
|
||||
} catch (NoSuchElementException e) {
|
||||
// nothing left to do
|
||||
if (rootState == ShellState2.BUSY) {
|
||||
rootState = ShellState2.READY;
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (state != null) {
|
||||
//same as last one. ignore it
|
||||
if (enableProfiling) {
|
||||
state.startTime = new Date();
|
||||
}
|
||||
if (rootState == ShellState2.FAIL) {
|
||||
// if we don't have root, abort all queued commands
|
||||
complete(state, EXIT_NO_ROOT_ACCESS);
|
||||
//continue;
|
||||
} else if (rootState == ShellState2.READY) {
|
||||
rootState = ShellState2.BUSY;
|
||||
if (G.isRun()) {
|
||||
createNotification(mContext);
|
||||
}
|
||||
processCommands(state);
|
||||
}
|
||||
}
|
||||
} while (false);
|
||||
}
|
||||
|
||||
private void processCommands(final RootCommand state) {
|
||||
if (state.commandIndex < state.getCommmands().size() && state.getCommmands().get(state.commandIndex) != null) {
|
||||
String command = state.getCommmands().get(state.commandIndex);
|
||||
//Log.i("AFWall", command);
|
||||
//not to send conflicting status
|
||||
sendUpdate(state);
|
||||
|
||||
if (command != null) {
|
||||
state.ignoreExitCode = false;
|
||||
|
||||
if (command.startsWith("#NOCHK# ")) {
|
||||
command = command.replaceFirst("#NOCHK# ", "");
|
||||
state.ignoreExitCode = true;
|
||||
}
|
||||
state.lastCommand = command;
|
||||
state.lastCommandResult = new StringBuilder();
|
||||
try {
|
||||
rootSession2.addCommand(command, 0, (Shell.OnCommandResultListener2) (commandCode, exitCode, output, STDERR) -> {
|
||||
ListIterator<String> iter = output.listIterator();
|
||||
while (iter.hasNext()) {
|
||||
String line = iter.next();
|
||||
if (line != null && !line.equals("")) {
|
||||
if (state.res != null) {
|
||||
state.res.append(line).append("\n");
|
||||
}
|
||||
state.lastCommandResult.append(line).append("\n");
|
||||
}
|
||||
}
|
||||
// Special handling for exit code 126 (command not executable) - fallback to system iptables
|
||||
if (exitCode == 126 && shouldFallbackToSystem(state)) {
|
||||
Log.w(TAG, "Built-in iptables failed with exit 126, attempting fallback to system iptables");
|
||||
// Remember that built-in iptables failed for future preference
|
||||
G.setBuiltinIptablesFailed(true);
|
||||
fallbackToSystemBinary(state);
|
||||
processCommands(state);
|
||||
return;
|
||||
}
|
||||
|
||||
if (exitCode >= 0 && exitCode == state.retryExitCode && state.retryCount < MAX_RETRIES) {
|
||||
//lets wait for few ms before trying ?
|
||||
state.retryCount++;
|
||||
Log.d(TAG, "command '" + state.lastCommand + "' exited with status " + exitCode +
|
||||
", retrying (attempt " + state.retryCount + "/" + MAX_RETRIES + ")");
|
||||
processCommands(state);
|
||||
return;
|
||||
}
|
||||
|
||||
state.commandIndex++;
|
||||
state.retryCount = 0;
|
||||
|
||||
boolean errorExit = exitCode != 0 && !state.ignoreExitCode;
|
||||
if (state.commandIndex >= state.getCommmands().size() || errorExit) {
|
||||
complete(state, exitCode);
|
||||
if (exitCode < 0) {
|
||||
rootState = ShellState2.FAIL;
|
||||
Log.e(TAG, "libsuperuser error " + exitCode + " on command '" + state.lastCommand + "'");
|
||||
} else {
|
||||
if (errorExit) {
|
||||
Log.i(TAG, "command '" + state.lastCommand + "' exited with status " + exitCode +
|
||||
"\nOutput:\n" + state.lastCommandResult);
|
||||
}
|
||||
rootState = ShellState2.READY;
|
||||
}
|
||||
runNextSubmission();
|
||||
} else {
|
||||
processCommands(state);
|
||||
}
|
||||
});
|
||||
} catch (NullPointerException | ArrayIndexOutOfBoundsException e) {
|
||||
Log.e(TAG, e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
complete(state, 0);
|
||||
}
|
||||
}
|
||||
|
||||
private void sendUpdate(final RootCommand state2) {
|
||||
new Thread(() -> {
|
||||
Intent broadcastIntent = new Intent();
|
||||
broadcastIntent.setAction("UPDATEUI6");
|
||||
broadcastIntent.putExtra("SIZE", state2.getCommmands().size());
|
||||
broadcastIntent.putExtra("INDEX", state2.commandIndex);
|
||||
LocalBroadcastManager.getInstance(this.mContext).sendBroadcast(broadcastIntent);
|
||||
}).start();
|
||||
}
|
||||
|
||||
private void createNotification(Context context) {
|
||||
|
||||
String CHANNEL_ID = "firewall.apply";
|
||||
notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
|
||||
NotificationCompat.Builder builder = new NotificationCompat.Builder(context, CHANNEL_ID);
|
||||
|
||||
Intent appIntent = new Intent(context, MainActivity.class);
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
/* Create or update. */
|
||||
NotificationChannel channel = new NotificationChannel(CHANNEL_ID, context.getString(R.string.runNotification),
|
||||
NotificationManager.IMPORTANCE_LOW);
|
||||
channel.setDescription("");
|
||||
channel.setShowBadge(false);
|
||||
channel.setSound(null, null);
|
||||
channel.enableLights(false);
|
||||
channel.enableVibration(false);
|
||||
if(G.getNotificationPriority() == 0) {
|
||||
channel.setImportance(NotificationManager.IMPORTANCE_DEFAULT);
|
||||
}
|
||||
notificationManager.createNotificationChannel(channel);
|
||||
}
|
||||
|
||||
TaskStackBuilder stackBuilder = TaskStackBuilder.create(context);
|
||||
stackBuilder.addParentStack(MainActivity.class);
|
||||
stackBuilder.addNextIntent(appIntent);
|
||||
PendingIntent resultPendingIntent = stackBuilder.getPendingIntent(0, PendingIntent.FLAG_IMMUTABLE);
|
||||
builder.setContentIntent(resultPendingIntent);
|
||||
|
||||
|
||||
int notifyType = G.getNotificationPriority();
|
||||
|
||||
Notification notification = builder.setSmallIcon(R.drawable.ic_apply)
|
||||
.setAutoCancel(false)
|
||||
.setContentTitle(context.getString(R.string.applying_rules))
|
||||
.setTicker(context.getString(R.string.app_name))
|
||||
.setPriority(NotificationCompat.PRIORITY_LOW)
|
||||
.setChannelId(CHANNEL_ID)
|
||||
.setCategory(Notification.CATEGORY_SERVICE)
|
||||
.setVisibility(NotificationCompat.VISIBILITY_SECRET)
|
||||
.setOnlyAlertOnce(true)
|
||||
.setPriority(NotificationManager.IMPORTANCE_LOW)
|
||||
.setContentText("").build();
|
||||
builder.setProgress(0, 0, true);
|
||||
notificationManager.notify(NOTIFICATION_ID, notification);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int onStartCommand(Intent intent, int flags, int startId) {
|
||||
if (intent == null) { // if crash restart...
|
||||
Log.i(TAG, "Restarting RootShell...");
|
||||
List<String> cmds = new ArrayList<>();
|
||||
cmds.add("true");
|
||||
new RootCommand().setFailureToast(R.string.error_su)
|
||||
.setReopenShell(true).run(getApplicationContext(), cmds);
|
||||
}
|
||||
return Service.START_STICKY;
|
||||
}
|
||||
|
||||
private void setupLogging() {
|
||||
Debug.setDebug(false);
|
||||
Debug.setLogTypeEnabled(Debug.LOG_ALL, false);
|
||||
Debug.setLogTypeEnabled(Debug.LOG_GENERAL, false);
|
||||
Debug.setSanityChecksEnabled(false);
|
||||
Debug.setOnLogListener((type, typeIndicator, message) -> Log.i(TAG, "[libsuperuser] " + message));
|
||||
}
|
||||
|
||||
|
||||
private void startShellInBackground() {
|
||||
Log.d(TAG, "Starting root shell(6)...");
|
||||
setupLogging();
|
||||
//start only rootSession is null
|
||||
if (rootSession2 == null) {
|
||||
rootSession2 = new Shell.Builder().
|
||||
useSU().
|
||||
setWatchdogTimeout(5).
|
||||
open((success, reason) -> {
|
||||
if (reason < 0) {
|
||||
Log.e(TAG, "Can't open root shell: exitCode " + reason);
|
||||
rootState = ShellState2.FAIL;
|
||||
} else {
|
||||
Log.d(TAG, "Root shell(6) is open");
|
||||
rootState = ShellState2.READY;
|
||||
}
|
||||
runNextSubmission();
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void reOpenShell(Context context) {
|
||||
if (rootState == null || rootState != ShellState2.READY || rootState == ShellState2.FAIL) {
|
||||
if (notificationManager != null) {
|
||||
notificationManager.cancel(NOTIFICATION_ID);
|
||||
}
|
||||
rootState = ShellState2.BUSY;
|
||||
startShellInBackground();
|
||||
Intent intent = new Intent(context, RootShellService2.class);
|
||||
context.startService(intent);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void runScriptAsRoot(Context ctx, List<String> cmds, RootCommand state) {
|
||||
state.setCommmands(cmds);
|
||||
state.commandIndex = 0;
|
||||
state.retryCount = 0;
|
||||
if (mContext == null) {
|
||||
mContext = ctx.getApplicationContext();
|
||||
}
|
||||
//already in memory and applied
|
||||
//add it to queue
|
||||
|
||||
waitQueue.add(state);
|
||||
|
||||
if (rootState == INIT || (rootState == ShellState2.FAIL && state.reopenShell)) {
|
||||
reOpenShell(ctx);
|
||||
} else if (rootState != ShellState2.BUSY) {
|
||||
runNextSubmission();
|
||||
} else {
|
||||
new Timer().schedule(new TimerTask() {
|
||||
@Override
|
||||
public void run() {
|
||||
Log.i(TAG, "State of rootShell(6): " + rootState);
|
||||
if (rootState == ShellState2.BUSY) {
|
||||
//try resetting state to READY forcefully
|
||||
Log.i(TAG, "Forcefully changing the state " + rootState);
|
||||
rootState = ShellState2.READY;
|
||||
}
|
||||
runNextSubmission();
|
||||
}
|
||||
}, 1000);
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public IBinder onBind(Intent intent) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if fallback to system binary should be attempted for exit code 126
|
||||
* Only fallback once per command to avoid infinite loops
|
||||
*/
|
||||
private boolean shouldFallbackToSystem(RootCommand state) {
|
||||
if (state.lastCommand == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if command contains built-in iptables path and hasn't been fallback attempted
|
||||
String builtinDir = getApplicationContext().getDir("bin", 0).getAbsolutePath();
|
||||
return state.lastCommand.contains(builtinDir) &&
|
||||
!state.lastCommand.contains("__FALLBACK_ATTEMPTED__");
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace built-in iptables/ip6tables paths with system paths in the current command
|
||||
*/
|
||||
private void fallbackToSystemBinary(RootCommand state) {
|
||||
if (state.lastCommand == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
String builtinDir = getApplicationContext().getDir("bin", 0).getAbsolutePath();
|
||||
String originalCommand = state.lastCommand;
|
||||
|
||||
// Try to find system iptables
|
||||
String systemIptables = Api.findSystemBinary("iptables");
|
||||
String systemIp6tables = Api.findSystemBinary("ip6tables");
|
||||
|
||||
if (systemIptables != null || systemIp6tables != null) {
|
||||
String updatedCommand = originalCommand;
|
||||
|
||||
// Replace built-in paths with system paths
|
||||
if (systemIptables != null) {
|
||||
updatedCommand = updatedCommand.replace(builtinDir + "/iptables", systemIptables);
|
||||
}
|
||||
if (systemIp6tables != null) {
|
||||
updatedCommand = updatedCommand.replace(builtinDir + "/ip6tables", systemIp6tables);
|
||||
}
|
||||
|
||||
// Mark as fallback attempted to prevent infinite loops
|
||||
updatedCommand += " # __FALLBACK_ATTEMPTED__";
|
||||
|
||||
// Update the command in the current state
|
||||
List<String> commands = state.getCommmands();
|
||||
if (state.commandIndex < commands.size()) {
|
||||
commands.set(state.commandIndex, updatedCommand);
|
||||
Log.i(TAG, "Fallback applied: " + originalCommand + " -> " + updatedCommand);
|
||||
}
|
||||
} else {
|
||||
Log.w(TAG, "No system iptables found for fallback");
|
||||
}
|
||||
}
|
||||
|
||||
public enum ShellState2 {
|
||||
INIT,
|
||||
READY,
|
||||
BUSY,
|
||||
FAIL
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
package dev.ukanth.ufirewall.service;
|
||||
|
||||
import android.app.IntentService;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
|
||||
import dev.ukanth.ufirewall.Api;
|
||||
import dev.ukanth.ufirewall.InterfaceTracker;
|
||||
import dev.ukanth.ufirewall.log.Log;
|
||||
import dev.ukanth.ufirewall.util.G;
|
||||
|
||||
/**
|
||||
* Created by ukanth on 14/11/16.
|
||||
*/
|
||||
|
||||
public class RulesApplyService extends IntentService {
|
||||
|
||||
public RulesApplyService() {
|
||||
super(RulesApplyService.class.getName());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onHandleIntent(Intent intent) {
|
||||
|
||||
Context context = RulesApplyService.this;
|
||||
if(Api.isEnabled(context)) {
|
||||
if(G.activeRules()) {
|
||||
Log.d(Api.TAG, "Applying rules on connectivity change");
|
||||
InterfaceTracker.applyRulesOnChange(context, InterfaceTracker.CONNECTIVITY_CHANGE);
|
||||
}
|
||||
final Intent logIntent = new Intent(context, LogService.class);
|
||||
if (G.enableLogService()) {
|
||||
context.stopService(logIntent);
|
||||
context.startService(logIntent);
|
||||
} else {
|
||||
context.stopService(logIntent);
|
||||
//Api.cleanupUid();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue