├── .github └── workflows │ └── gradle.yml ├── .gitignore ├── LICENSE.md ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── releasestandard │ │ └── scriptmanager │ │ ├── ExampleInstrumentedTest.java │ │ ├── StorageManagerTest.java │ │ ├── model │ │ └── TimeManagerTest.java │ │ └── tools │ │ └── CallStackTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── ic_launcher-playstore.png │ ├── java │ │ └── com │ │ │ └── releasestandard │ │ │ └── scriptmanager │ │ │ ├── AlarmReceiver.java │ │ │ ├── BootReceiver.java │ │ │ ├── JavaCallbacksTrigger.java │ │ │ ├── JavaEventsReceiver.java │ │ │ ├── JobView.java │ │ │ ├── JobsView.java │ │ │ ├── MainActivity.java │ │ │ ├── SettingsView.java │ │ │ ├── controller │ │ │ ├── JobData.java │ │ │ ├── OverflowMenu.java │ │ │ └── TimeManagerView.java │ │ │ ├── model │ │ │ ├── KSHEvent.java │ │ │ ├── KornShellInterface.java │ │ │ ├── Shell.java │ │ │ ├── StorageManager.java │ │ │ └── TimeManager.java │ │ │ └── tools │ │ │ ├── CallStack.java │ │ │ ├── CompatAPI.java │ │ │ └── Logger.java │ └── res │ │ ├── drawable-v24 │ │ └── ic_launcher_foreground.xml │ │ ├── drawable │ │ ├── ic_launcher_background.xml │ │ ├── logo.png │ │ └── logo2.png │ │ ├── layout │ │ ├── job_view.xml │ │ ├── jobs_view.xml │ │ ├── main_activity.xml │ │ └── rename_dialog.xml │ │ ├── menu │ │ └── menu_main.xml │ │ ├── mipmap-anydpi-v26 │ │ ├── ic_launcher.xml │ │ └── ic_launcher_round.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.png │ │ ├── ic_launcher_foreground.png │ │ ├── ic_launcher_round.png │ │ └── logo2.png │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.png │ │ ├── ic_launcher_foreground.png │ │ ├── ic_launcher_round.png │ │ └── logo2.png │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.png │ │ ├── ic_launcher_foreground.png │ │ ├── ic_launcher_round.png │ │ └── logo2.png │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.png │ │ ├── ic_launcher_foreground.png │ │ ├── ic_launcher_round.png │ │ └── logo2.png │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.png │ │ ├── ic_launcher_foreground.png │ │ ├── ic_launcher_round.png │ │ └── logo2.png │ │ ├── values-night │ │ └── themes.xml │ │ ├── values │ │ ├── api.xml │ │ ├── colors.xml │ │ ├── dimens.xml │ │ ├── overflow_menu_values.xml │ │ ├── quit_dialog.xml │ │ ├── settings_view.xml │ │ ├── strings.xml │ │ └── themes.xml │ │ └── xml │ │ ├── provider_paths.xml │ │ └── settings_fragment.xml │ └── test │ └── java │ └── com │ └── releasestandard │ └── scriptmanager │ └── ExampleUnitTest.java ├── build.gradle ├── fastlane └── metadata │ └── android │ └── en-US │ ├── full_description.txt │ ├── images │ └── phoneScreenshots │ │ ├── 1.jpg │ │ ├── 2.jpg │ │ └── 3.jpg │ ├── short_description.txt │ └── title.txt ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── scripts ├── checkcompat.sh └── newversion.sh └── settings.gradle /.github/workflows/gradle.yml: -------------------------------------------------------------------------------- 1 | # This workflow will build a Java project with Gradle 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-gradle 3 | 4 | name: Java CI with Gradle 5 | 6 | on: 7 | push: 8 | tags: '*' 9 | 10 | jobs: 11 | build: 12 | runs-on: ubuntu-latest 13 | env: 14 | GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}" 15 | 16 | steps: 17 | - uses: actions/checkout@v2 18 | 19 | - name: Set up JDK 1.8 20 | uses: actions/setup-java@v1 21 | with: 22 | java-version: 1.8 23 | 24 | - name: Grant execute permission for gradlew 25 | run: chmod +x gradlew 26 | 27 | - name: Build with Gradle 28 | run: ./gradlew packageDebug 29 | 30 | - name: Upload package to github packages 31 | run: G=./app/build.gradle && export VERSION=$(grep -oP 'versionName "\K(.*?)(?=")' $G) && export APPNAME=$(grep -oP 'applicationId ".*' $G |sed 's/.*\.\([^\.]\+\)"/\1/g') && export APK=./app/build/outputs/apk/debug/app-debug.apk && export FNAME=${APPNAME}-${VERSION}.apk && env && mv $APK $FNAME && hub release create -a $FNAME -m $FNAME $VERSION 32 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/caches 5 | /.idea/libraries 6 | /.idea/modules.xml 7 | /.idea/workspace.xml 8 | /.idea/navEditor.xml 9 | /.idea/assetWizardSettings.xml 10 | .DS_Store 11 | /build 12 | /captures 13 | .externalNativeBuild 14 | .cxx 15 | local.properties 16 | .idea 17 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 ReleaseStandard Inc. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | Terms of license apply to all files in this repository. 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | This project is no longer maintained but has been integrated and improved part of [scriptmanagerfree](https://github.com/ReleaseStandard/ScriptManagerFree). 3 | 4 | # ScriptManager 5 |

6 | 7 |

8 | Open source application to manage, launch and schedule your sh scripts.
9 | You can view it has cron schedulder of sh scripts.
10 |
11 |
12 | 13 | 14 | 15 |
16 |
17 | 18 | 19 | ## Basic usage 20 | Press "+" to create a new job.
21 | Longpress the new job and overflow menu > Edit (script your script).
22 | Then click play to launch the script.
23 | Check the result by long press then View log.
24 | 25 | 26 | ## Project motivations 27 | This project is inspired by [SL4A](https://en.wikipedia.org/wiki/Scripting_Layer_for_Android).
28 | The goal is to offert same capabilities on the device (ex: write and send SMS with sh line),
29 | throught an sh API.
30 | 31 | 32 | ## Current state of the project 33 | - [X] No root required.
34 | - [X] You can schedule and repeat jobs.
35 | - [X] Schedulded jobs persist accross reboot.
36 | - [X] Clear & Show jobs log.
37 | - [X] Import sh scripts.
38 | - [ ] SH API
39 | 40 | 41 | 42 |
propertyavaliable
smsReceivedyes
43 | All GUI is well advanced but the sh api, documentation requires tons of work lol.
44 | Feel free to fork/reuse/complete it !
45 | Also if you have any question you can use releasestandard chez netcourrier point com
46 | 47 | ## Documentation 48 | Everything is on the [wiki](https://github.com/ReleaseStandard/ScriptManager/wiki). 49 | 50 | ## Donation 51 | ```bash 52 | https://paypal.me/ReleaseStandard 53 | ``` 54 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'com.android.application' 3 | } 4 | 5 | android { 6 | compileSdkVersion 30 7 | buildToolsVersion "30.0.3" 8 | 9 | defaultConfig { 10 | applicationId "com.releasestandard.scriptmanager" 11 | minSdkVersion 23 12 | maxSdkVersion 30 13 | targetSdkVersion 28 14 | versionCode 5 15 | versionName "0.5" 16 | 17 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 18 | } 19 | 20 | buildTypes { 21 | release { 22 | minifyEnabled false 23 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 24 | } 25 | } 26 | compileOptions { 27 | sourceCompatibility JavaVersion.VERSION_1_8 28 | targetCompatibility JavaVersion.VERSION_1_8 29 | } 30 | } 31 | 32 | dependencies { 33 | 34 | testImplementation 'androidx.test:core:1.0.0' 35 | testImplementation 'org.mockito:mockito-core:1.10.19' 36 | androidTestImplementation 'androidx.test.ext:junit:1.1.1' 37 | 38 | 39 | implementation 'androidx.appcompat:appcompat:1.2.0' 40 | implementation 'com.google.android.material:material:1.2.1' 41 | implementation 'androidx.constraintlayout:constraintlayout:2.0.4' 42 | implementation 'androidx.navigation:navigation-fragment:2.3.2' 43 | implementation 'androidx.navigation:navigation-ui:2.3.2' 44 | implementation 'androidx.legacy:legacy-support-v4:1.0.0' 45 | implementation 'androidx.work:work-runtime:2.3.4' 46 | implementation 'androidx.preference:preference:1.1.1' 47 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0' 48 | } 49 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile -------------------------------------------------------------------------------- /app/src/androidTest/java/com/releasestandard/scriptmanager/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package com.releasestandard.scriptmanager; 2 | 3 | import android.content.Context; 4 | 5 | import androidx.test.platform.app.InstrumentationRegistry; 6 | 7 | import org.junit.Test; 8 | 9 | import static org.junit.Assert.*; 10 | 11 | /** 12 | * Instrumented test, which will execute on an Android device. 13 | * 14 | * @see Testing documentation 15 | */ 16 | public class ExampleInstrumentedTest { 17 | @Test 18 | public void useAppContext() { 19 | // Context of the app under test. 20 | Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); 21 | assertEquals("com.releasestandard.scriptmanager", appContext.getPackageName()); 22 | } 23 | } -------------------------------------------------------------------------------- /app/src/androidTest/java/com/releasestandard/scriptmanager/StorageManagerTest.java: -------------------------------------------------------------------------------- 1 | package com.releasestandard.scriptmanager; 2 | 3 | import com.releasestandard.scriptmanager.model.StorageManager; 4 | 5 | import junit.framework.TestCase; 6 | 7 | import java.lang.reflect.Field; 8 | 9 | public class StorageManagerTest extends TestCase { 10 | 11 | Field script_name; 12 | 13 | public void setUp() throws Exception { 14 | super.setUp(); 15 | 16 | script_name = StorageManager.class. 17 | getDeclaredField("script_name"); 18 | 19 | script_name.setAccessible(true); 20 | } 21 | 22 | public void testSetScriptName() { 23 | String scriptname = "test"; 24 | StorageManager sm = new StorageManager(); 25 | sm.setScriptName(scriptname); 26 | assertNull(sm.getLogAbsolutePath()); 27 | assertTrue(script_name.equals(scriptname)); 28 | } 29 | 30 | public void testGetLogAbsolutePath() { 31 | } 32 | 33 | public void testTestGetLogAbsolutePath() { 34 | } 35 | 36 | public void testGetStateFileAbsolutePath() { 37 | } 38 | 39 | public void testTestGetStateFileAbsolutePath() { 40 | } 41 | 42 | public void testGetScriptAbsolutePath() { 43 | } 44 | 45 | public void testTestGetScriptAbsolutePath() { 46 | } 47 | 48 | public void testGetOutputAbsolutePath() { 49 | } 50 | 51 | public void testTestGetOutputAbsolutePath() { 52 | } 53 | 54 | public void testGetExternalAbsolutePath() { 55 | } 56 | 57 | public void testGetInternalAbsolutePath() { 58 | } 59 | 60 | public void testGetScriptsFromFilesystem() { 61 | } 62 | } -------------------------------------------------------------------------------- /app/src/androidTest/java/com/releasestandard/scriptmanager/model/TimeManagerTest.java: -------------------------------------------------------------------------------- 1 | package com.releasestandard.scriptmanager.model; 2 | 3 | import com.releasestandard.scriptmanager.tools.Logger; 4 | 5 | import junit.framework.TestCase; 6 | 7 | import java.util.Random; 8 | 9 | import static com.releasestandard.scriptmanager.model.TimeManager.isRepeated; 10 | import static com.releasestandard.scriptmanager.model.TimeManager.sched2str; 11 | import static com.releasestandard.scriptmanager.model.TimeManager.str2sched; 12 | import static com.releasestandard.scriptmanager.model.TimeManager.validDate; 13 | 14 | public class TimeManagerTest extends TestCase { 15 | 16 | public void setUp() throws Exception { 17 | super.setUp(); 18 | } 19 | 20 | public void testStr2sched() { 21 | { 22 | String s1 = "1 2 3 4 5"; 23 | int[] expected = new int[]{1, 2, 3, 4, 5}; 24 | int[] sched = str2sched(s1); 25 | assertEquals(sched, expected); 26 | } 27 | { 28 | String s = "* 2 3 4 5"; 29 | int[] expected = new int[]{TimeManager.EACH_TIME, 2, 3, 4, 5}; 30 | int[] sched = str2sched(s); 31 | assertEquals(sched, expected); 32 | } 33 | } 34 | 35 | public void testSched2str() { 36 | { 37 | String s1 = "1 2 3 4 5"; 38 | int[] sched = new int[]{1, 2, 3, 4, 5}; 39 | String expected = sched2str(sched); 40 | assertEquals(expected,s1); 41 | } 42 | { 43 | String s1 = "* 2 3 4 5"; 44 | int[] sched = new int[]{TimeManager.EACH_TIME, 2, 3, 4, 5}; 45 | String expected = sched2str(sched); 46 | assertEquals(expected,s1); 47 | } 48 | } 49 | 50 | public void testSymetry() { 51 | Random r = new Random(); 52 | for ( int i = 0; i < 10 ; i = i + 1) { 53 | int [] sched = new int[5]; 54 | for ( int j = 0; j < 5 ; j = j + 1) { 55 | int x = r.nextInt(); 56 | if ( x < 0 ) { x = x * -1; } 57 | sched[j] = x; 58 | } 59 | String sched_ = sched2str(sched); 60 | Logger.debug("sched_=" + sched_); 61 | assertEquals(str2sched(sched_),sched); 62 | } 63 | } 64 | 65 | public void testIsRepeated() { 66 | String s1 = "* 2 3 4 5"; 67 | assertEquals(isRepeated(str2sched(s1)),true); 68 | String s2 = "* * * * *"; 69 | assertEquals(isRepeated(str2sched(s2)),true); 70 | String s3 = "1 2 3 4 5"; 71 | assertEquals(isRepeated(str2sched(s3)),false); 72 | } 73 | 74 | public void testNextSched() { 75 | } 76 | public void testValidDate() { 77 | String [] valid = new String[] { 78 | "* 1 2 3 4", 79 | "1 2 3 4 5", 80 | "* * * * *", 81 | "0 1 2 3 4", 82 | "0 * 1 2 3" 83 | }; 84 | for(String s : valid) { 85 | String ss = sched2str(str2sched(s)); 86 | assertEquals(validDate(ss),true); 87 | } 88 | } 89 | } -------------------------------------------------------------------------------- /app/src/androidTest/java/com/releasestandard/scriptmanager/tools/CallStackTest.java: -------------------------------------------------------------------------------- 1 | package com.releasestandard.scriptmanager.tools; 2 | import junit.framework.TestCase; 3 | import org.junit.Test; 4 | 5 | public class CallStackTest extends TestCase { 6 | 7 | @Test 8 | public void testGetLastCaller() { 9 | String n = CallStack.getLastCaller(); 10 | assertEquals(n, "testGetLastCaller"); 11 | } 12 | 13 | public void testTestGetLastCaller() { 14 | 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 12 | 17 | 20 | 21 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /app/src/main/ic_launcher-playstore.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ReleaseStandard/ScriptManager/270aae7e68f9cdaf8ff3f293a49ca8408df3773d/app/src/main/ic_launcher-playstore.png -------------------------------------------------------------------------------- /app/src/main/java/com/releasestandard/scriptmanager/AlarmReceiver.java: -------------------------------------------------------------------------------- 1 | package com.releasestandard.scriptmanager; 2 | 3 | import android.content.BroadcastReceiver; 4 | import android.content.Context; 5 | import android.content.Intent; 6 | 7 | import com.releasestandard.scriptmanager.controller.JobData; 8 | import com.releasestandard.scriptmanager.model.Shell; 9 | import com.releasestandard.scriptmanager.model.StorageManager; 10 | import com.releasestandard.scriptmanager.model.TimeManager; 11 | import com.releasestandard.scriptmanager.tools.Logger; 12 | 13 | import java.io.IOException; 14 | import java.io.InputStreamReader; 15 | import java.io.OutputStreamWriter; 16 | import java.util.Calendar; 17 | import java.util.GregorianCalendar; 18 | import java.util.Random; 19 | 20 | /* 21 | * Class that handle the return of alarm and start the task. 22 | */ 23 | // This receiver is not exported so we dont have to secure it. 24 | public class AlarmReceiver extends BroadcastReceiver { 25 | 26 | public static int REQUEST_CODE = (new Random()).nextInt(); 27 | 28 | /** 29 | * compat 8 30 | * @param context 31 | * @param intent 32 | */ 33 | @Override 34 | public void onReceive(Context context, Intent intent) { 35 | String scriptname = intent.getStringExtra("script"); 36 | int [] sched = intent.getIntArrayExtra("sched"); 37 | 38 | Logger.debug("at:" + (new GregorianCalendar()).getTime().toString() + "scriptname="+scriptname+",sched="+ TimeManager.sched2str(sched)); 39 | StorageManager sm = new StorageManager(context, scriptname); 40 | JobData jd = new JobData(); 41 | sm.dump(); 42 | InputStreamReader isr = StorageManager.getISR(context,sm.getStateFileNameInPath()); 43 | if ( isr == null ) { 44 | Logger.debug("isr is null"); 45 | } 46 | Logger.debug(sm.getStateFileNameInPath()); 47 | 48 | jd.readState(isr); 49 | jd.dump("\t"); 50 | Shell s = new Shell(sm); 51 | Integer j = s.execScript(scriptname); 52 | if ( j != -1 ) { 53 | jd.processes.add(j); 54 | } 55 | // add listener (will be nulled at job stop) // 56 | jd.listeners.add(JavaEventsReceiver.listeners.size()-1); 57 | 58 | if ( jd.isSchedulded && TimeManager.isRepeated(sched)) { 59 | Integer i =s.scheduleScript(context,scriptname,sched); 60 | if ( i != -1) { 61 | jd.intents.add(i); 62 | } 63 | } 64 | try { 65 | isr.close(); 66 | } catch (IOException e) { 67 | e.printStackTrace(Logger.getTraceStream()); 68 | } 69 | 70 | OutputStreamWriter osw = StorageManager.getOSW(context,sm.getStateFileNameInPath()); 71 | jd.writeState(osw); 72 | try { 73 | osw.flush(); 74 | osw.close(); 75 | } catch (IOException e) { 76 | e.printStackTrace(Logger.getTraceStream()); 77 | } 78 | } 79 | 80 | } 81 | -------------------------------------------------------------------------------- /app/src/main/java/com/releasestandard/scriptmanager/BootReceiver.java: -------------------------------------------------------------------------------- 1 | package com.releasestandard.scriptmanager; 2 | 3 | import android.content.BroadcastReceiver; 4 | import android.content.Context; 5 | import android.content.Intent; 6 | 7 | import com.releasestandard.scriptmanager.controller.JobData; 8 | import com.releasestandard.scriptmanager.model.Shell; 9 | import com.releasestandard.scriptmanager.model.StorageManager; 10 | import com.releasestandard.scriptmanager.tools.Logger; 11 | 12 | import java.io.IOException; 13 | import java.io.InputStreamReader; 14 | import java.io.OutputStreamWriter; 15 | 16 | public class BootReceiver extends BroadcastReceiver { 17 | /** 18 | * compat 8 19 | * @param context 20 | * @param intent 21 | */ 22 | @Override 23 | public void onReceive(Context context, Intent intent) { 24 | 25 | // This is a privileged intent, we don't have to secure it. 26 | if (Intent.ACTION_BOOT_COMPLETED.equals(intent.getAction())) { 27 | 28 | Logger.debug("boot signal received, starting the scripts"); 29 | 30 | for ( String f : context.fileList()) { 31 | if ( ! StorageManager.isStateFile(f) ) { 32 | continue; 33 | } 34 | Logger.debug("statefile found : " + f); 35 | JobData jd = new JobData(); 36 | InputStreamReader isr = StorageManager.getISR(context,f); 37 | jd.readState(isr, true); 38 | jd.name_in_path = StorageManager.removeSuffix(StorageManager.getTerminalPart(f)); 39 | jd.dump(); 40 | if (jd.isStarted) { 41 | Shell shell = new Shell(new StorageManager(context, jd.name_in_path)); 42 | Logger.debug(jd.name_in_path + " is null"); 43 | Integer i = shell.scheduleScript(context, jd.name_in_path, jd.sched,!jd.isSchedulded); 44 | if ( i != -1 ) { 45 | jd.intents.add(i); 46 | } 47 | 48 | } 49 | try { 50 | isr.close(); 51 | } catch (IOException e) { 52 | e.printStackTrace(Logger.getTraceStream()); 53 | } 54 | OutputStreamWriter osw = StorageManager.getOSW(context,f); 55 | jd.writeState(osw); 56 | try { 57 | osw.flush(); 58 | osw.close(); 59 | } catch (IOException e) { 60 | e.printStackTrace(Logger.getTraceStream()); 61 | } 62 | } 63 | 64 | } 65 | } 66 | } -------------------------------------------------------------------------------- /app/src/main/java/com/releasestandard/scriptmanager/JavaCallbacksTrigger.java: -------------------------------------------------------------------------------- 1 | package com.releasestandard.scriptmanager; 2 | 3 | public class JavaCallbacksTrigger { 4 | } 5 | -------------------------------------------------------------------------------- /app/src/main/java/com/releasestandard/scriptmanager/JavaEventsReceiver.java: -------------------------------------------------------------------------------- 1 | package com.releasestandard.scriptmanager; 2 | 3 | 4 | import android.content.BroadcastReceiver; 5 | import android.content.Context; 6 | import android.content.Intent; 7 | import android.content.SharedPreferences; 8 | import android.content.res.Resources; 9 | import android.os.Build; 10 | import android.os.Bundle; 11 | import android.telephony.SmsMessage; 12 | 13 | import com.releasestandard.scriptmanager.model.Shell; 14 | import com.releasestandard.scriptmanager.tools.Logger; 15 | 16 | import java.util.ArrayList; 17 | 18 | public class JavaEventsReceiver extends BroadcastReceiver { 19 | 20 | private SharedPreferences preferences; 21 | public static ArrayList listeners = new ArrayList<>(); 22 | 23 | @Override 24 | public void onReceive(Context context, Intent intent) { 25 | /** 26 | * compat 19 27 | */ 28 | if(intent.getAction().equals("android.provider.Telephony.SMS_RECEIVED")){ 29 | Bundle bundle = intent.getExtras(); 30 | SmsMessage[] msgs = null; 31 | String msg_from; 32 | Resources r = context.getResources(); 33 | if (bundle != null){ 34 | try{ 35 | Object[] pdus = (Object[]) bundle.get("pdus"); 36 | msgs = new SmsMessage[pdus.length]; 37 | for(int i=0; i= 23) { 39 | msgs[i] = SmsMessage.createFromPdu((byte[]) pdus[i], bundle.getString("format")); 40 | } else { 41 | msgs[i] = SmsMessage.createFromPdu((byte[]) pdus[i]); 42 | } 43 | msg_from = msgs[i].getOriginatingAddress(); 44 | String msgBody = msgs[i].getMessageBody(); 45 | for (Shell s : listeners) { 46 | if ( s == null ) { continue ; } // listener has been disabled 47 | s.bi.triggerCallback(r.getString(R.string.callbackSmsReceived),msg_from,msgBody); 48 | } 49 | } 50 | }catch(Exception e){ 51 | e.printStackTrace(Logger.getTraceStream()); 52 | } 53 | } 54 | } 55 | } 56 | } -------------------------------------------------------------------------------- /app/src/main/java/com/releasestandard/scriptmanager/JobView.java: -------------------------------------------------------------------------------- 1 | package com.releasestandard.scriptmanager; 2 | 3 | import android.content.Context; 4 | import android.graphics.drawable.ColorDrawable; 5 | import android.os.Bundle; 6 | 7 | import androidx.annotation.NonNull; 8 | import androidx.annotation.Nullable; 9 | import androidx.appcompat.app.ActionBar; 10 | import androidx.fragment.app.Fragment; 11 | import androidx.fragment.app.FragmentTransaction; 12 | 13 | import android.os.Handler; 14 | import android.text.Editable; 15 | import android.text.TextWatcher; 16 | import android.view.LayoutInflater; 17 | import android.view.View; 18 | import android.view.ViewGroup; 19 | import android.widget.EditText; 20 | import android.widget.TextView; 21 | import android.widget.Toast; 22 | 23 | import com.google.android.material.floatingactionbutton.FloatingActionButton; 24 | import com.releasestandard.scriptmanager.controller.JobData; 25 | import com.releasestandard.scriptmanager.model.KSHEvent; 26 | import com.releasestandard.scriptmanager.model.Shell; 27 | import com.releasestandard.scriptmanager.model.StorageManager; 28 | import com.releasestandard.scriptmanager.model.TimeManager; 29 | import com.releasestandard.scriptmanager.tools.Logger; 30 | import com.releasestandard.scriptmanager.controller.TimeManagerView; 31 | 32 | import java.io.File; 33 | import java.io.IOException; 34 | import java.io.InputStreamReader; 35 | import java.io.OutputStreamWriter; 36 | import java.util.ArrayList; 37 | import java.util.Calendar; 38 | 39 | /** 40 | * compat 11 41 | * A simple {@link Fragment} subclass. 42 | * create an instance of this fragment. 43 | */ 44 | public class JobView extends Fragment { 45 | 46 | public static String WRONG_DATE_FORMAT = "Wrong date format"; 47 | public static Integer fragmentCount = 0; 48 | 49 | // is this fragment selected user 50 | public boolean isSelected = false; 51 | // données du modèle 52 | 53 | private View view = null; 54 | 55 | Shell shell = null; 56 | public JobData jd = new JobData(); 57 | 58 | public void dump() { 59 | Logger.debug(dump("")); 60 | } 61 | public String dump(String init) { 62 | String ninit = init + "\t"; 63 | return 64 | init + "JobFragment {\n"+ 65 | ninit + "fragmentCount="+fragmentCount+"\n"+ 66 | ninit + "WRONG_DATE_FORMAT="+WRONG_DATE_FORMAT+"\n"+ 67 | jd.dump(ninit) + 68 | shell.dump(ninit) + 69 | "}\n"; 70 | } 71 | 72 | public JobView(StorageManager ptr_sm, String scriptname) { 73 | this.shell = new Shell(ptr_sm); 74 | initializeInstance(); 75 | this.shell.sm.setScriptName(scriptname); 76 | } 77 | public JobView(StorageManager ptr_sm) { 78 | this.shell = new Shell(ptr_sm); 79 | initializeInstance(); 80 | } 81 | 82 | /** 83 | * compat 1 84 | * Used to initialize the JobFragment 85 | */ 86 | public void initializeInstance() { 87 | Logger.debug("initializeInstance"); 88 | Calendar rn = Calendar.getInstance(); 89 | jd.sched = TimeManager.packIn(rn.get(Calendar.MINUTE), 90 | rn.get(Calendar.HOUR), 91 | rn.get(Calendar.DAY_OF_MONTH), 92 | rn.get(Calendar.MONTH), 93 | rn.get(Calendar.YEAR)); 94 | rn.set(Calendar.SECOND,0); 95 | rn.set(Calendar.MILLISECOND,0); 96 | 97 | jd.id = fragmentCount++; 98 | String scriptname = "script_" + jd.id.toString(); 99 | jd.name = "Script n°" + jd.id.toString(); 100 | shell.sm.setScriptName(scriptname); 101 | jd.name_in_path = scriptname; 102 | jd.dump(); 103 | } 104 | 105 | /** 106 | * compat 11 107 | * @param view 108 | * @param savedInstanceState 109 | */ 110 | @Override 111 | public void onViewCreated(@NonNull View view, Bundle savedInstanceState) { 112 | super.onViewCreated(view, savedInstanceState); 113 | writeState(); 114 | // update the date view 115 | // and schedule icon 116 | TextView dateView = view.findViewById(R.id.job_date_input); 117 | if ( jd.isDateSet ) { 118 | dateView.setText(com.releasestandard.scriptmanager.model.TimeManager.sched2str(jd.sched)); 119 | } 120 | long delay = 500; // 1 seconds after user stops typing 121 | long last_text_edit = 0; 122 | Handler handler = new Handler(); 123 | Runnable input_finish_checker = new Runnable() { 124 | public void run() { 125 | if (System.currentTimeMillis() > (last_text_edit + delay - delay/2)) { 126 | int [] s = getDateFromView(); 127 | if ( s != null ) { 128 | jd.sched = s; 129 | setDate(); 130 | writeState(); 131 | } 132 | } 133 | } 134 | }; 135 | 136 | dateView.addTextChangedListener(new TextWatcher() { 137 | private Editable s; 138 | 139 | @Override 140 | public void beforeTextChanged (CharSequence s,int start, int count, 141 | int after){ 142 | } 143 | @Override 144 | public void onTextChanged ( final CharSequence s, int start, int before, 145 | int count){ 146 | //You need to remove this to run only once 147 | handler.removeCallbacks(input_finish_checker); 148 | 149 | } 150 | @Override 151 | public void afterTextChanged ( final Editable s){ 152 | //avoid triggering event when text is empty 153 | if (s.length() > 0) { 154 | handler.postDelayed(input_finish_checker, delay); 155 | } else { 156 | 157 | } 158 | } 159 | }); 160 | 161 | Logger.debug(""); 162 | } 163 | 164 | @Override 165 | public void onSaveInstanceState(@NonNull Bundle outState) { 166 | super.onSaveInstanceState(outState); 167 | Logger.debug(""); 168 | } 169 | 170 | @Override 171 | public void onViewStateRestored(@Nullable Bundle savedInstanceState) { 172 | super.onViewStateRestored(savedInstanceState); 173 | restoreView(); 174 | Logger.debug(""); 175 | } 176 | 177 | @Override 178 | public void onCreate(Bundle savedInstanceState) { 179 | super.onCreate(savedInstanceState); 180 | 181 | Logger.debug(""); 182 | } 183 | 184 | /** 185 | * compat 11 186 | * @param inflater 187 | * @param container 188 | * @param savedInstanceState 189 | * @return 190 | */ 191 | @Override 192 | public View onCreateView(LayoutInflater inflater, ViewGroup container, 193 | Bundle savedInstanceState) { 194 | Logger.debug(""); 195 | // Inflate the layout for this fragment 196 | View v = inflater.inflate(R.layout.job_view, container, false); 197 | TextView tv = v.findViewById(R.id.job_title); 198 | tv.setText(jd.name); 199 | shell.dump(); 200 | jd.dump(); 201 | TextView tv2 = v.findViewById(R.id.job_filename); 202 | tv2.setText(jd.name_in_path); 203 | 204 | v.setOnLongClickListener(new View.OnLongClickListener() { 205 | @Override 206 | public boolean onLongClick(View arg0) { 207 | selectView(arg0); 208 | return true; 209 | }}); 210 | v.setOnClickListener(new View.OnClickListener(){ 211 | @Override 212 | public void onClick(View v) { 213 | MainActivity main = (MainActivity)getActivity(); 214 | if ( main.jobs_view.getNumberSelected() > 0 ) { 215 | if (isSelected) { 216 | unselectView(v); 217 | } else { 218 | selectView(v); 219 | } 220 | } 221 | } 222 | }); 223 | 224 | View playpause_button = v.findViewById(R.id.job_trigger_button); 225 | playpause_button.setOnClickListener(new View.OnClickListener() { 226 | @Override 227 | public void onClick(View view) { 228 | EditText et = v.findViewById(R.id.job_date_input); 229 | int [] s = getDateFromView(); 230 | if ( !isDateSet() || (s != null && isDateSet())) { 231 | if ( isDateSet() ) { 232 | jd.sched = s; 233 | } 234 | // update image 235 | if (!isStarted()) { 236 | startJob(); 237 | } else { 238 | stopJob(); 239 | } 240 | } 241 | else { 242 | showErrorView(WRONG_DATE_FORMAT); 243 | } 244 | } 245 | }); 246 | if ( jd.isSchedulded ) { 247 | setViewWaitStartJob(playpause_button); 248 | } 249 | // set up event s for the date & time picker 250 | View vv = v.findViewById(R.id.job_date_picker_button); 251 | vv.setOnClickListener(new View.OnClickListener() { 252 | @Override 253 | public void onClick(View v) { 254 | showScheduleJob(v); 255 | } 256 | }); 257 | 258 | return v; 259 | } 260 | 261 | // Model // 262 | public boolean isStarted() { 263 | return jd.isStarted; 264 | } 265 | // // 266 | 267 | // Controller // 268 | /* 269 | * Check if the guy has modified the time when to launch the script. 270 | */ 271 | public void showScheduleJob(View v) { 272 | 273 | TimeManagerView tm = new TimeManagerView(){ 274 | @Override 275 | public void onPicked(int minute,int hourOfDay,int dayOfMonth,int monthOfYear,int year) { 276 | jd.sched = TimeManager.packIn(minute,hourOfDay,dayOfMonth,monthOfYear,year); 277 | setDate(); 278 | writeState(); 279 | } 280 | }; 281 | tm.show(v,jd.sched); 282 | } 283 | public boolean setDate() { 284 | jd.isDateSet = true; 285 | return jd.isDateSet; 286 | } 287 | public boolean isDateSet() { 288 | EditText et = getView().findViewById(R.id.job_date_input); 289 | String s = et.getText().toString(); 290 | this.jd.isDateSet = ! s.equals(new String("")) && s != null ; 291 | return this.jd.isDateSet; 292 | } 293 | public void setName(String name) { 294 | this.jd.name = name; 295 | TextView tv = getView().findViewById(R.id.job_title); 296 | tv.setText(name); 297 | } 298 | public void stopJob() { 299 | readState(getContext(),jd.name_in_path); 300 | Logger.debug("kill " + jd.intents.size() + " intents"); 301 | for (Integer i : jd.intents) { 302 | shell.terminateIntent(i); 303 | } 304 | Logger.debug("kill " + jd.processes.size() + " processes"); 305 | for (Integer i : jd.processes) { 306 | shell.terminateProcess(i); 307 | } 308 | for (Integer i : jd.listeners) { 309 | JavaEventsReceiver.listeners.set(i, null); 310 | } 311 | jd.listeners.clear();jd.processes.clear();jd.intents.clear(); 312 | Shell._execCmd("rm -rf " + shell.sm.getEventsAbsolutePath(jd.name_in_path)); 313 | 314 | jd.isSchedulded = false; 315 | jd.isStarted = false; 316 | setViewStopJob(); 317 | MainActivity main = (MainActivity)getActivity(); 318 | int i = main.jobs_view.getNumberStarted(); 319 | if ( i == 0 ) { 320 | main.ow_menu.leaveRunningMode(); 321 | } 322 | writeState(); 323 | } 324 | public void startJob() { 325 | Logger.debug(""); 326 | MainActivity main = (MainActivity)getActivity(); 327 | int i = main.jobs_view.getNumberStarted(); 328 | if ( i == 0) { 329 | main.ow_menu.enterRunningMode(); 330 | } 331 | 332 | jd.isStarted = true; 333 | 334 | int [] s = new int[]{}; 335 | boolean immediate = false; 336 | if ( isDateSet() ) { 337 | s = getDateFromView(); 338 | if ( s!=null) { 339 | jd.isSchedulded = true; 340 | setViewWaitStartJob(); 341 | } 342 | } 343 | else { 344 | immediate = true; 345 | setViewStartJob(); 346 | } 347 | 348 | if ( s != null ) { 349 | Integer intent_index = shell.scheduleScript(main, jd.name_in_path, s, immediate); 350 | if (intent_index != -1) { 351 | jd.intents.add(intent_index); 352 | } 353 | } 354 | 355 | writeState(); 356 | 357 | if( isSelected ) { 358 | main.ow_menu.callbackSelectAndRunning(main); 359 | } 360 | } 361 | public int[] getDateFromView() { 362 | EditText et = getView().findViewById(R.id.job_date_input); 363 | String date_input = et.getText().toString(); 364 | if (com.releasestandard.scriptmanager.model.TimeManager.validDate(date_input)) { 365 | return com.releasestandard.scriptmanager.model.TimeManager.str2sched(date_input); 366 | } 367 | return null; 368 | } 369 | // // 370 | 371 | // View // 372 | public void removeViewJob() { 373 | Logger.debug(""); 374 | FragmentTransaction ft = getFragmentManager().beginTransaction(); 375 | ft.remove(this); 376 | ft.commit(); 377 | 378 | // remove the script file 379 | ArrayList files = new ArrayList(); 380 | files.add(shell.sm.getScriptAbsolutePath()); //script file remove 381 | files.add(shell.sm.getLogAbsolutePath()); // remove log file 382 | getContext().deleteFile(shell.sm.getStateFileNameInPath()); // remove statefile in the private storage 383 | // remove state file 384 | for ( String s : files ) { 385 | Logger.log(s); 386 | File f = new File(s); 387 | if (f.exists()) { 388 | f.delete(); 389 | } 390 | } 391 | } 392 | public void setViewStartJob() { 393 | FloatingActionButton fab = this.getView().findViewById(R.id.job_trigger_button); 394 | fab.setImageDrawable( 395 | getResources().getDrawable(android.R.drawable.ic_media_pause) 396 | ); 397 | } 398 | public void setViewWaitStartJob(View v) { 399 | FloatingActionButton fab = (FloatingActionButton)v; 400 | fab.setImageDrawable( 401 | getResources().getDrawable(android.R.drawable.ic_menu_recent_history) 402 | ); 403 | } 404 | public void setViewWaitStartJob() { 405 | setViewWaitStartJob(this.getView().findViewById(R.id.job_trigger_button)); 406 | } 407 | public void setViewStopJob() { 408 | FloatingActionButton fab = this.getView().findViewById(R.id.job_trigger_button); 409 | fab.setImageDrawable( 410 | getResources().getDrawable(android.R.drawable.ic_media_play) 411 | ); 412 | } 413 | public void unselectView() { 414 | this.isSelected = false; 415 | if ( this.view != null) { 416 | MainActivity main = (MainActivity) getActivity(); 417 | int color =main.getColorFromId(main, R.attr.colorPrimary); 418 | view.setBackgroundColor(color); 419 | if( main.jobs_view.getNumberSelected() <= 0) { 420 | main.ow_menu.leaveSelectMode(); 421 | } 422 | if( main.jobs_view.getNumberSelected() == 1) { 423 | main.ow_menu.enterOneOnlySelectMode(); 424 | } 425 | } 426 | } 427 | public void unselectView(View v) { 428 | if ( v != null ) { 429 | this.view = v; 430 | } 431 | this.unselectView(); 432 | } 433 | public void selectView() { 434 | if ( this.view != null ) { 435 | this.selectView(this.view); 436 | } 437 | } 438 | public void selectView(View v) { 439 | this.isSelected = true; 440 | // The selection action 441 | // Hightlight the ActionBar 442 | MainActivity main = (MainActivity) getActivity(); 443 | ActionBar ab = main.getSupportActionBar(); 444 | int color =main.getColorFromId(main, android.R.attr.colorLongPressedHighlight); 445 | ab.setBackgroundDrawable(new ColorDrawable(color)); 446 | v.setBackgroundColor(color); 447 | if( main.jobs_view.getNumberSelected() == 1) { 448 | main.ow_menu.enterSelectMode(); 449 | } 450 | if( main.jobs_view.getNumberSelected() > 1) { 451 | main.ow_menu.leaveOneOnlySelectMode(); 452 | } 453 | if ( v != null ) { 454 | this.view = v; 455 | } 456 | main.ow_menu.callbackSelectAndRunning(main); 457 | } 458 | public void restoreView() { 459 | if (jd.isSchedulded) { 460 | setViewWaitStartJob(); 461 | } 462 | else { 463 | if (isStarted()) { 464 | setViewStartJob(); 465 | } else { 466 | setViewStopJob(); 467 | } 468 | } 469 | } 470 | public void showErrorView(String text) { 471 | Context context = getContext(); 472 | int duration = Toast.LENGTH_SHORT; 473 | 474 | Toast toast = Toast.makeText(context, text, duration); 475 | toast.show(); 476 | 477 | } 478 | // // 479 | 480 | 481 | /******************************************************************************* 482 | * Model = filesystem * 483 | *******************************************************************************/ 484 | /** 485 | * Write the state of user interface 486 | */ 487 | public void writeState() { 488 | jd.dump(); 489 | OutputStreamWriter osw = StorageManager.getOSW(getContext(),shell.sm.getStateFileNameInPath()); 490 | jd.writeState(osw); 491 | try { 492 | osw.flush(); 493 | osw.close(); 494 | } catch (IOException e) { 495 | e.printStackTrace(Logger.getTraceStream()); 496 | } 497 | } 498 | 499 | /** 500 | * Read the state at start 501 | * => pas d'update dans l'ui, c'est le probleme 502 | */ 503 | public void readState(Context context, String path_name) { 504 | Logger.debug(""); 505 | InputStreamReader isr = StorageManager.getISR(context,shell.sm.getStateFileNameInPath()); 506 | jd.readState(isr); 507 | try { 508 | isr.close(); 509 | } catch (IOException e) { 510 | e.printStackTrace(Logger.getTraceStream()); 511 | } 512 | String script_name_path = path_name; 513 | this.shell.sm.setScriptName(script_name_path); 514 | } 515 | } -------------------------------------------------------------------------------- /app/src/main/java/com/releasestandard/scriptmanager/JobsView.java: -------------------------------------------------------------------------------- 1 | package com.releasestandard.scriptmanager; 2 | 3 | import android.os.Bundle; 4 | import android.view.LayoutInflater; 5 | import android.view.View; 6 | import android.view.ViewGroup; 7 | 8 | import androidx.annotation.NonNull; 9 | import androidx.annotation.Nullable; 10 | import androidx.fragment.app.Fragment; 11 | import androidx.fragment.app.FragmentManager; 12 | import androidx.fragment.app.FragmentTransaction; 13 | 14 | import com.releasestandard.scriptmanager.model.StorageManager; 15 | import com.releasestandard.scriptmanager.tools.Logger; 16 | 17 | import java.util.ArrayList; 18 | import java.util.List; 19 | 20 | /** 21 | * Used to display many jobs on screen. 22 | * compat 11 23 | */ 24 | public class JobsView extends Fragment { 25 | 26 | // list of Jobs 27 | public List fragments = new ArrayList(); 28 | public List fragments_ss = new ArrayList(); 29 | 30 | public StorageManager ptr_sm = null; 31 | 32 | public JobsView() { 33 | // Required empty public constructor 34 | } 35 | public JobsView(StorageManager ptr_sm) { 36 | this.ptr_sm = ptr_sm; 37 | } 38 | 39 | public static SettingsView newInstance(String param1, String param2) { 40 | SettingsView fragment = new SettingsView(); 41 | Bundle args = new Bundle(); 42 | return fragment; 43 | } 44 | 45 | @Override 46 | public void onCreate(Bundle savedInstanceState) { 47 | if ( savedInstanceState == null ) { 48 | Logger.debug( "savedInstanceState is null"); 49 | } 50 | else{ 51 | Logger.debug("savedInstanceState is not null"); 52 | } 53 | super.onCreate(savedInstanceState); 54 | } 55 | 56 | /** 57 | * compat 1 58 | * @return 59 | */ 60 | public ArrayListgetSelecteds() { 61 | ArrayList list = new ArrayList<>(); 62 | for(JobView js : fragments) { 63 | if (js.isSelected) { 64 | list.add(js); 65 | } 66 | } 67 | return list; 68 | } 69 | 70 | /** 71 | * compat 1 72 | * @return 73 | */ 74 | public JobView getSelected() { 75 | for(JobView js : fragments) { 76 | if(js.isSelected) { 77 | return js; 78 | } 79 | } 80 | return null; 81 | } 82 | 83 | /** 84 | * @param inflater 85 | * @param container 86 | * @param savedInstanceState 87 | * @return 88 | */ 89 | @Override 90 | public View onCreateView( 91 | LayoutInflater inflater, ViewGroup container, 92 | Bundle savedInstanceState 93 | ) { 94 | Integer i = new Integer(fragments.size()); 95 | Logger.debug("size in fragments : "+i); 96 | if ( savedInstanceState == null ) { 97 | Logger.debug("saveInstanceNull"); 98 | } 99 | View v = inflater.inflate(R.layout.jobs_view, container, false); 100 | return v; 101 | } 102 | 103 | /** 104 | * @param view 105 | * @param savedInstanceState 106 | */ 107 | @Override 108 | public void onViewCreated(@NonNull View view, Bundle savedInstanceState) { 109 | super.onViewCreated(view, savedInstanceState); 110 | Logger.debug(""); 111 | } 112 | 113 | /** 114 | * compat 11 115 | * @param outState 116 | */ 117 | @Override 118 | public void onSaveInstanceState(@NonNull Bundle outState) { 119 | super.onSaveInstanceState(outState); 120 | Logger.debug(""); 121 | } 122 | 123 | @Override 124 | /** 125 | * compat 11 126 | */ 127 | public void onViewStateRestored(@Nullable Bundle savedInstanceState) { 128 | super.onViewStateRestored(savedInstanceState); 129 | Integer in = new Integer(getParentFragmentManager().getFragments().size()); 130 | Logger.debug("nb of frags "+in); 131 | restoreFragments(); 132 | } 133 | 134 | /** 135 | * compat 11 136 | */ 137 | public void restoreFragments() { 138 | // we need to restore the view for all childs 139 | FragmentManager fm = getChildFragmentManager(); 140 | FragmentTransaction ft = fm.beginTransaction(); 141 | for(JobView jf : fragments) { 142 | ft.add(R.id.view_jobs_linearlayout, jf); 143 | } 144 | ft.commit(); 145 | readState(); 146 | } 147 | 148 | // Tools 149 | public void stopAllFragments() { 150 | stopAllFragments(false); 151 | } 152 | 153 | /** 154 | * compat 1 155 | * @param onlySelected 156 | */ 157 | public void stopAllFragments(boolean onlySelected) { 158 | for (JobView jf : fragments) { 159 | if( onlySelected) { 160 | if(jf.isSelected) { 161 | jf.stopJob(); 162 | } 163 | } 164 | else 165 | { 166 | jf.stopJob(); 167 | } 168 | } 169 | } 170 | 171 | /** 172 | * compat 1 173 | */ 174 | public void unselectAllFragments() { 175 | for (JobView jf : fragments) { 176 | jf.unselectView(); 177 | } 178 | if ( getNumberSelected() == 0) { 179 | MainActivity main = (MainActivity) getActivity(); 180 | main.ow_menu.leaveSelectMode(); 181 | } 182 | } 183 | 184 | /** 185 | * compat 1 186 | * @return 187 | */ 188 | public int getNumberSelected() { 189 | int count = 0; 190 | for (JobView jf : fragments) { 191 | if ( jf.isSelected ) { 192 | count += 1; 193 | } 194 | } 195 | return count; 196 | } 197 | 198 | /** 199 | * compat 1 200 | * @return 201 | */ 202 | public int getNumberStartedAndSelected() { 203 | int count = 0; 204 | for (JobView jf : fragments) { 205 | if ( jf.isStarted() && jf.isSelected) { 206 | count += 1; 207 | } 208 | } 209 | return count; 210 | } 211 | 212 | /** 213 | * compat 1 214 | * @return 215 | */ 216 | public int getNumberStarted() { 217 | int count = 0; 218 | for (JobView jf : fragments) { 219 | if ( jf.isStarted()) { 220 | count += 1; 221 | } 222 | } 223 | return count; 224 | } 225 | 226 | 227 | /** 228 | * compat 11 229 | * @return 230 | */ 231 | public JobView addNewJob(){ return addNewJob(null); } 232 | public JobView addNewJob(String scriptname) { 233 | FragmentManager fm = getChildFragmentManager(); 234 | FragmentTransaction ft = fm.beginTransaction(); 235 | 236 | JobView f = null; 237 | if ( scriptname == null ) { 238 | f = new JobView(ptr_sm); 239 | } 240 | else { 241 | // don't create the files please 242 | f = new JobView(ptr_sm, scriptname); 243 | } 244 | ft.add(R.id.view_jobs_linearlayout, f); 245 | ft.commit(); 246 | 247 | fragments.add(f); 248 | return f; 249 | } 250 | 251 | /** 252 | * compat 1 253 | */ 254 | public void writeState() { 255 | for ( JobView jf : fragments) { 256 | jf.writeState(); 257 | } 258 | } 259 | 260 | /** 261 | * compat 1 262 | */ 263 | public void readState() { 264 | for(String scriptname : this.ptr_sm.getScriptsFromFilesystem(getContext())) { 265 | JobView alrdyIn = null; 266 | for(JobView jv : fragments) { 267 | if ( jv.jd.name_in_path.equals(scriptname ) ) { 268 | alrdyIn = jv; 269 | break; 270 | } 271 | } 272 | if ( alrdyIn != null ) { 273 | alrdyIn.readState(getActivity(),scriptname); 274 | alrdyIn.dump(); 275 | continue; 276 | } 277 | JobView jf = addNewJob(scriptname); 278 | jf.readState(getActivity(),scriptname); 279 | jf.dump(); 280 | } 281 | if ( fragments.size() > 0) { 282 | JobView.fragmentCount = fragments.get(fragments.size() - 1).jd.id + 1; 283 | } 284 | } 285 | 286 | } 287 | -------------------------------------------------------------------------------- /app/src/main/java/com/releasestandard/scriptmanager/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.releasestandard.scriptmanager; 2 | 3 | import androidx.appcompat.app.ActionBar; 4 | import androidx.core.app.ActivityCompat; 5 | import androidx.core.content.FileProvider; 6 | import androidx.fragment.app.Fragment; 7 | import androidx.fragment.app.FragmentManager; 8 | //import android.app.FragmentManager; 9 | import androidx.fragment.app.FragmentTransaction; 10 | //import android.app.FragmentTransaction; 11 | import android.Manifest; 12 | import android.app.AlertDialog; 13 | import android.content.Context; 14 | import android.content.DialogInterface; 15 | import android.content.Intent; 16 | import android.net.Uri; 17 | import android.os.Bundle; 18 | 19 | import com.google.android.material.floatingactionbutton.FloatingActionButton; 20 | import com.releasestandard.scriptmanager.controller.OverflowMenu; 21 | import com.releasestandard.scriptmanager.model.KSHEvent; 22 | import com.releasestandard.scriptmanager.model.StorageManager; 23 | import com.releasestandard.scriptmanager.tools.CompatAPI; 24 | import com.releasestandard.scriptmanager.tools.Logger; 25 | 26 | import androidx.appcompat.app.AppCompatActivity; 27 | import androidx.appcompat.widget.Toolbar; 28 | 29 | import android.util.TypedValue; 30 | import android.view.View; 31 | 32 | import android.view.Menu; 33 | import android.view.MenuItem; 34 | import android.widget.EditText; 35 | 36 | import java.io.File; 37 | import java.io.FileNotFoundException; 38 | import java.io.FileOutputStream; 39 | import java.io.IOException; 40 | import java.io.InputStream; 41 | import java.util.Hashtable; 42 | 43 | public class MainActivity extends AppCompatActivity { 44 | 45 | private Toolbar toolbar = null; 46 | 47 | private static boolean FIRST_CREATION = true; 48 | public static int ACTIVITY_REQUEST_CODE_IMPORT = 1; 49 | public boolean isInSelectMode = false; 50 | 51 | // Objects that encapsulate the data // 52 | public JobsView jobs_view = null; 53 | public SettingsView sf = null; 54 | public OverflowMenu ow_menu = null; 55 | private Hashtable views = null; 56 | 57 | StorageManager sm = null; 58 | 59 | @Override 60 | protected void onCreate(Bundle savedInstanceState) { 61 | 62 | super.onCreate(savedInstanceState); 63 | 64 | setContentView(R.layout.main_activity); 65 | 66 | toolbar = findViewById(R.id.toolbar); 67 | setSupportActionBar(toolbar); 68 | 69 | // Get Shell locations path 70 | this.sm = new StorageManager(getApplicationContext()); 71 | 72 | FloatingActionButton fab = findViewById(R.id.main_activity_fab); 73 | fab.setOnClickListener(new View.OnClickListener() { 74 | @Override 75 | public void onClick(View view) { 76 | handlerFabClick(); 77 | } 78 | }); 79 | 80 | this.views = new Hashtable<>(); 81 | jobs_view = new JobsView(this.sm); 82 | 83 | sf = new SettingsView(); 84 | views.put(R.id.nav_host_fragment, jobs_view); 85 | views.put(R.id.action_settings, sf); 86 | 87 | requestPermissions(); 88 | 89 | FragmentTransaction ft = getSupportFragmentManager().beginTransaction(); 90 | ft.replace(R.id.nav_host_fragment, jobs_view); 91 | ft.commit(); 92 | 93 | Logger.debug(""); 94 | } 95 | private void requestPermissions() { 96 | ActivityCompat.requestPermissions(MainActivity.this, 97 | new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE, 98 | Manifest.permission.RECEIVE_BOOT_COMPLETED, 99 | Manifest.permission.RECEIVE_SMS}, 1); 100 | } 101 | @Override 102 | protected void onStart() { 103 | super.onStart(); 104 | if ( FIRST_CREATION ) { 105 | jobs_view.readState(); // get the state from the storage 106 | FIRST_CREATION = false; 107 | } 108 | Logger.debug(""); 109 | } 110 | 111 | 112 | @Override 113 | public boolean onCreateOptionsMenu(Menu menu) { 114 | SettingsView.applySettings(this); 115 | getMenuInflater().inflate(R.menu.menu_main, menu); 116 | ow_menu = new OverflowMenu(this, menu); 117 | return true; 118 | } 119 | 120 | @Override 121 | public boolean onOptionsItemSelected(MenuItem item) { 122 | super.onOptionsItemSelected(item); 123 | 124 | MainActivity main = this; 125 | int id = item.getItemId(); 126 | 127 | if (id == R.id.action_stopselected) { 128 | jobs_view.stopAllFragments(true); 129 | jobs_view.unselectAllFragments(); 130 | } 131 | if (id == R.id.action_stopall) { 132 | jobs_view.stopAllFragments(); 133 | jobs_view.unselectAllFragments(); 134 | } 135 | if (id == R.id.action_unselectall) { 136 | jobs_view.unselectAllFragments(); 137 | } 138 | if (id == R.id.action_settings) { 139 | jobs_view.unselectAllFragments(); 140 | ow_menu.leaveSelectMode(); 141 | 142 | ActionBar ab = super.getSupportActionBar(); 143 | ab.setTitle(R.string.settings_page_title); 144 | ab.setDisplayHomeAsUpEnabled(true); 145 | ab.setDisplayUseLogoEnabled(false); 146 | 147 | findViewById(R.id.main_activity_fab).setVisibility(View.INVISIBLE); 148 | 149 | FragmentTransaction ft = getSupportFragmentManager().beginTransaction(); 150 | ft.replace(R.id.nav_host_fragment, sf); 151 | ft.commit(); 152 | 153 | ow_menu.setMenuVisibility(false); 154 | return true; 155 | } 156 | 157 | if (id == android.R.id.home) { 158 | if (isInSelectMode) { 159 | mainLeaveSelectMode(); 160 | } else { 161 | settings2main(); 162 | } 163 | } 164 | 165 | if (id == R.id.action_oneonly_edit) { 166 | JobView jf = jobs_view.getSelected(); 167 | showFileWithEditor(jf.shell.sm.getScriptAbsolutePath()); 168 | jobs_view.unselectAllFragments(); 169 | } 170 | 171 | if ( R.id.action_anyselection_delete == id ) { 172 | for( JobView jf : jobs_view.getSelecteds() ) { 173 | jf.removeViewJob(); 174 | jobs_view.fragments.remove(jf); 175 | } 176 | jobs_view.unselectAllFragments(); 177 | } 178 | if (R.id.action_oneonly_clear_log == id) { 179 | JobView jf = jobs_view.getSelected(); 180 | try { 181 | jf.shell.clearLog(jf.shell.sm.getLogAbsolutePath()); 182 | } catch (IOException e) { 183 | e.printStackTrace(Logger.getTraceStream()); 184 | } 185 | jobs_view.unselectAllFragments(); 186 | } 187 | if ( R.id.action_oneonly_show_log == id) { 188 | JobView jf = jobs_view.getSelected(); 189 | showFileWithEditor(jf.shell.sm.getLogAbsolutePath()); 190 | jobs_view.unselectAllFragments(); 191 | } 192 | if (R.id.action_oneonly_rename == id) { 193 | JobView jf = jobs_view.getSelected(); 194 | AlertDialog.Builder builder = new AlertDialog.Builder(this,R.style.AppTheme_RenameDialog); 195 | View customLayout = getLayoutInflater().inflate(R.layout.rename_dialog, null); 196 | builder.setView(customLayout); 197 | builder.setPositiveButton(R.string.action_oneonly_rename_ok, new DialogInterface.OnClickListener() { 198 | public void onClick(DialogInterface dialog, int id) { 199 | EditText et = (EditText) customLayout.findViewById(R.id.rename_dialog_input); 200 | jf.setName(et.getText().toString()); 201 | jf.writeState(); 202 | } 203 | }); 204 | builder.setNegativeButton(R.string.action_oneonly_rename_cancel, new DialogInterface.OnClickListener() { 205 | public void onClick(DialogInterface dialog, int id) { 206 | dialog.cancel(); 207 | } 208 | }); 209 | AlertDialog alert = builder.create(); 210 | alert.setTitle(R.string.action_oneonly_rename); 211 | alert.show(); 212 | jobs_view.unselectAllFragments(); 213 | } 214 | 215 | // not ready yet due to limitations to access the storage // 216 | if (R.id.action_browse_scripts == id) { 217 | JobView jf = jobs_view.getSelected(); 218 | Uri selectedUri = Uri.parse(sm.externalStorage ); 219 | CompatAPI.openDocument(this, selectedUri.toString()); 220 | jobs_view.unselectAllFragments(); 221 | } 222 | // 223 | // features : import multiple scripts (putExtra EXTRA_ALLOW_MULTIPLE true API 18), also ACTION_OPEN_DOCUMENT_TREE for a complete directory (API 21+) 224 | // 225 | if (R.id.action_import_script == id) { 226 | CompatAPI.openDocument(this); 227 | jobs_view.unselectAllFragments(); 228 | } 229 | if ( R.id.action_flush_events == id ) { 230 | KSHEvent.recvKsh2java(); 231 | } 232 | if ( R.id.action_test_button == id) { 233 | //showFileWithEditor("/data/data/com.releasestandard.scriptmanager/files/script_0.xml"); 234 | jobs_view.writeState(); 235 | } 236 | if ( R.id.action_test_button2 == id) { 237 | jobs_view.readState(); 238 | } 239 | 240 | // Start service at boot / enregistrement sur disk 241 | return true; 242 | } 243 | 244 | /** 245 | * compat 1 246 | * @param path 247 | */ 248 | public void showFileWithEditor(String path) { 249 | Context context = this; 250 | String pvd = context.getPackageName() + ".provider"; 251 | File f = new File(path); 252 | if (!f.exists()) { 253 | try { 254 | f.createNewFile(); 255 | } catch (IOException e) { 256 | return; 257 | } 258 | } 259 | Uri uri = FileProvider.getUriForFile(context, pvd, f); 260 | Intent myIntent = new Intent(Intent.ACTION_VIEW); 261 | myIntent.setData(uri); 262 | myIntent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION); 263 | context.startActivity(myIntent); 264 | } 265 | 266 | /* 267 | * Handle result of the import action. 268 | */ 269 | @Override 270 | protected void onActivityResult(int requestCode, int resultCode, Intent data) { 271 | super.onActivityResult(requestCode,resultCode,data); 272 | if ( data == null) { 273 | // user has cancelled the request 274 | return; 275 | } 276 | if ( requestCode == ACTIVITY_REQUEST_CODE_IMPORT) { 277 | Uri uri = data.getData(); 278 | InputStream is = null; 279 | try { 280 | is = getContentResolver().openInputStream(uri); 281 | } catch (FileNotFoundException e) { 282 | e.printStackTrace(Logger.getTraceStream()); 283 | } 284 | 285 | handlerFabClick(); 286 | // create a new fragment 287 | JobView jf = jobs_view.fragments.get(jobs_view.fragments.size() - 1); 288 | File f2 = new File(jf.shell.sm.getScriptAbsolutePath()); 289 | if (!f2.exists()) { 290 | try { 291 | f2.createNewFile(); 292 | } catch (IOException e) { 293 | e.printStackTrace(Logger.getTraceStream()); 294 | } 295 | } 296 | FileOutputStream fos = null; 297 | try { 298 | fos = new FileOutputStream(f2); 299 | } catch (FileNotFoundException e) { 300 | e.printStackTrace(Logger.getTraceStream()); 301 | } 302 | try { 303 | int c; 304 | while ((c = is.read()) != -1) { 305 | fos.write(c); 306 | } 307 | is.close(); 308 | fos.close(); 309 | } catch (IOException e) { 310 | e.printStackTrace(Logger.getTraceStream()); 311 | } 312 | } 313 | } 314 | /* 315 | * get color associated with the current theme. 316 | */ 317 | public static int getColorFromId(MainActivity main, int colorID) { 318 | TypedValue typedValue = new TypedValue(); 319 | main.getTheme().resolveAttribute(colorID, typedValue, true); 320 | return typedValue.data; 321 | } 322 | 323 | // Handlers for click on interface 324 | public void handlerFabClick() { 325 | jobs_view.addNewJob(); 326 | } 327 | 328 | public void settings2main() { 329 | ActionBar ab = super.getSupportActionBar(); 330 | ab.setTitle(R.string.app_name); 331 | ab.setDisplayHomeAsUpEnabled(false); 332 | ab.setDisplayUseLogoEnabled(true); 333 | 334 | findViewById(R.id.main_activity_fab).setVisibility(View.VISIBLE); 335 | FragmentTransaction ft = getSupportFragmentManager().beginTransaction(); 336 | ft.replace(R.id.nav_host_fragment, jobs_view); 337 | ft.commit(); 338 | 339 | ow_menu.setMenuVisibility(true); 340 | } 341 | public void mainLeaveSelectMode() { 342 | ActionBar ab = super.getSupportActionBar(); 343 | ab.setTitle(R.string.app_name); 344 | ab.setDisplayHomeAsUpEnabled(false); 345 | ab.setDisplayUseLogoEnabled(true); 346 | jobs_view.unselectAllFragments(); 347 | ow_menu.leaveSelectMode(); 348 | } 349 | @Override 350 | public void onBackPressed() { 351 | FragmentManager fm = getSupportFragmentManager(); 352 | for ( Fragment f : fm.getFragments()) { 353 | if ( f == sf) { 354 | settings2main(); 355 | return; 356 | } 357 | } 358 | if ( isInSelectMode ) { 359 | mainLeaveSelectMode(); 360 | } 361 | else { 362 | AlertDialog ad = new AlertDialog.Builder(this, R.style.AppTheme_AlertDialog) 363 | .setTitle(R.string.dialog_back_title) 364 | .setMessage(R.string.dialog_back_content) 365 | 366 | .setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() { 367 | public void onClick(DialogInterface dialog, int which) { 368 | finish(); 369 | } 370 | }) 371 | .setNegativeButton(android.R.string.no, null) 372 | .setIcon(android.R.drawable.ic_dialog_alert) 373 | .show(); 374 | } 375 | } 376 | } -------------------------------------------------------------------------------- /app/src/main/java/com/releasestandard/scriptmanager/SettingsView.java: -------------------------------------------------------------------------------- 1 | package com.releasestandard.scriptmanager; 2 | 3 | import android.app.AlarmManager; 4 | import android.content.Context; 5 | import android.content.SharedPreferences; 6 | import android.content.res.Resources; 7 | import android.os.Bundle; 8 | 9 | import androidx.preference.Preference; 10 | import androidx.preference.PreferenceFragmentCompat; 11 | import androidx.preference.PreferenceGroup; 12 | import androidx.preference.PreferenceManager; 13 | 14 | import com.releasestandard.scriptmanager.tools.CompatAPI; 15 | import com.releasestandard.scriptmanager.tools.Logger; 16 | 17 | public class SettingsView extends PreferenceFragmentCompat { 18 | 19 | private SharedPreferences.OnSharedPreferenceChangeListener listener = null; 20 | 21 | @Override 22 | public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { 23 | setPreferencesFromResource(R.xml.settings_fragment, rootKey); 24 | SharedPreferences sp = 25 | PreferenceManager.getDefaultSharedPreferences(getActivity()); 26 | 27 | listener = new SharedPreferences.OnSharedPreferenceChangeListener() { 28 | public void onSharedPreferenceChanged(SharedPreferences prefs, String key) { 29 | applySettings(prefs); 30 | } 31 | }; 32 | 33 | // load current values into view 34 | sp.registerOnSharedPreferenceChangeListener(listener); 35 | CompatAPI.modifySettings(this); 36 | hideUnusedGroups(); 37 | } 38 | 39 | private int getPreferenceCountShown(PreferenceGroup pg) { 40 | int c = 0; 41 | for( int i = 0; i < pg.getPreferenceCount(); i = i +1) { 42 | c += pg.getPreference(i).isShown()?1:0; 43 | } 44 | return c; 45 | } 46 | 47 | /** 48 | * Works only if at least one setting is defined in xml file 49 | * (eg: used to hide when all settings of given group are not usable) 50 | */ 51 | private void hideUnusedGroups() { 52 | SharedPreferences sp = 53 | PreferenceManager.getDefaultSharedPreferences(getActivity()); 54 | for(String k : sp.getAll().keySet()) { 55 | Preference pref = findPreference(k); 56 | if ( pref == null ) { 57 | continue; 58 | } 59 | PreferenceGroup pg = pref.getParent(); 60 | if ( pg != null ) { 61 | if (getPreferenceCountShown(pg) == 0) { 62 | pg.setVisible(false); 63 | } 64 | } 65 | } 66 | } 67 | 68 | public void applySettings() { applySettings(getActivity());} 69 | public void applySettings(SharedPreferences sp) { 70 | applySettings(getActivity(),sp); 71 | } 72 | public static void applySettings(Context ctx) { 73 | SharedPreferences sp = 74 | PreferenceManager.getDefaultSharedPreferences(ctx); 75 | applySettings(ctx,sp); 76 | } 77 | public static void applySettings(Context ctx, SharedPreferences sp) { 78 | Resources r = ctx.getResources(); 79 | Boolean preferences_developper_debug_mode = sp.getBoolean(r .getString(R.string.preferences_developper_debug_mode), r .getBoolean(R.bool.preferences_developper_debug_mode)); 80 | Logger.DEBUG = preferences_developper_debug_mode; 81 | Logger.debug(r .getString(R.string.preferences_developper_debug_mode) + "="+preferences_developper_debug_mode); 82 | 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /app/src/main/java/com/releasestandard/scriptmanager/controller/JobData.java: -------------------------------------------------------------------------------- 1 | package com.releasestandard.scriptmanager.controller; 2 | 3 | import com.releasestandard.scriptmanager.model.StorageManager; 4 | import com.releasestandard.scriptmanager.model.TimeManager; 5 | import com.releasestandard.scriptmanager.tools.Logger; 6 | 7 | import java.io.FileNotFoundException; 8 | import java.io.IOException; 9 | import java.io.InputStreamReader; 10 | import java.io.OutputStreamWriter; 11 | import java.util.ArrayList; 12 | import java.util.List; 13 | 14 | /** 15 | * Classe used to parse offline data, extract information and do the thing. 16 | */ 17 | public class JobData { 18 | 19 | public final static Integer EACH_TIME = -1; 20 | 21 | // id of the script 22 | public Integer id = 0; 23 | // name of the script 24 | public String name = ""; 25 | // boolean for the (if it is schedulded) 26 | public Boolean isSchedulded = false; 27 | // boolean for the (if it is started) 28 | public Boolean isStarted = false; 29 | // is the date graphically set 30 | public boolean isDateSet = false; 31 | // set The date 32 | public int sched[] = { 33 | EACH_TIME, // minutes 34 | EACH_TIME, // hours 35 | EACH_TIME, // day of month 36 | EACH_TIME, // month 37 | EACH_TIME }; // year 38 | // index in array (intents for schedulded and processes for started) 39 | public List processes = new ArrayList(); 40 | public List intents = new ArrayList<>(); 41 | public List listeners = new ArrayList<>(); 42 | 43 | // 44 | // Not stored 45 | public String name_in_path = ""; 46 | 47 | 48 | 49 | /** 50 | * Beware this method is used at boot time to set alarms, Object like Matcher, File 51 | * could cause crashes. 52 | * compat 1 53 | */ 54 | public void readState(InputStreamReader isr) { readState(isr,false); } 55 | public void readState(InputStreamReader isr, boolean ignore_intents_processes) { 56 | if ( isr == null ) { return; } 57 | try { 58 | // id of the script 59 | int id = isr.read(); 60 | this.id = id; 61 | // name of the script 62 | int script_name_size = isr.read(); 63 | char [] script_name = new char[script_name_size]; 64 | isr.read(script_name); 65 | name = new String(script_name); 66 | // boolean for the (if it is schedulded) 67 | isSchedulded = (isr.read() == 0)?false:true; 68 | // boolean for the (if it is started) 69 | isStarted = (isr.read() == 0)?false:true; 70 | // boolean for the (if it is date set or not) 71 | isDateSet = (isr.read() == 0)?false:true; 72 | // get The date 73 | sched = StorageManager.readIntArray(isr); 74 | if ( ! ignore_intents_processes ) { 75 | Logger.debug("read processes"); 76 | processes = StorageManager.readIntegerArray( isr); 77 | dump(); 78 | Logger.debug("read intents"); 79 | intents = StorageManager.readIntegerArray( isr); 80 | listeners = StorageManager.readIntegerArray( isr ); 81 | } 82 | Logger.debug("after read from internal storage"); 83 | dump(); 84 | } catch (FileNotFoundException e) { 85 | e.printStackTrace(Logger.getTraceStream()); 86 | } catch (IOException e) { 87 | e.printStackTrace(Logger.getTraceStream()); 88 | } 89 | } 90 | 91 | /** 92 | * Write the state of user interface 93 | * WARNING state_file is just the terminal part of the path 94 | * compat 1 95 | */ 96 | public void writeState(OutputStreamWriter osw) { 97 | if ( osw == null ) { return; } 98 | try { 99 | // id of the script 100 | osw.write(id.intValue()); 101 | // size of the string 102 | osw.write(name.length()); 103 | // name of the script 104 | osw.write(name); 105 | // boolean for the (if it is schedulded) 106 | osw.write((isSchedulded?1:0)); 107 | // boolean for the (if it is started) 108 | osw.write((isStarted?1:0)); 109 | // boolean for the (if it is date set or not) 110 | osw.write((isDateSet?1:0)); 111 | StorageManager.writeIntArray(osw,sched,5); 112 | // index of (intent, process) in array (intents, processes) 113 | Logger.debug("write processes"); 114 | StorageManager.writeIntegerArray(osw,processes); 115 | Logger.debug("write intents"); 116 | StorageManager.writeIntegerArray(osw,intents); 117 | StorageManager.writeIntegerArray(osw,listeners); 118 | osw.flush(); 119 | } catch (FileNotFoundException e) { 120 | e.printStackTrace(Logger.getTraceStream()); 121 | } catch (IOException e) { 122 | e.printStackTrace(Logger.getTraceStream()); 123 | } 124 | } 125 | 126 | /** 127 | * compat 1 128 | */ 129 | public void dump() { 130 | Logger.debug(dump("")); 131 | } 132 | public String dump(String init) { 133 | String sched_as_ints = ""; 134 | for ( int i : sched) { 135 | sched_as_ints = sched_as_ints + (new Integer(i)) + " "; 136 | } 137 | return 138 | Logger.SZERO + init + "JobData {\n" + 139 | Logger.SZERO + init + " id=" + id + "\n" + 140 | Logger.SZERO + init + " name=" + name + "\n" + 141 | Logger.SZERO + init + " isSchedulded=" + isSchedulded + "\n" + 142 | Logger.SZERO + init + " isStarted=" + isStarted + "\n" + 143 | Logger.SZERO + init + " isDateSet=" + isDateSet + "\n" + 144 | Logger.SZERO + init + " sched=" + TimeManager.sched2str(sched) + "\n" + 145 | Logger.SZERO + init + " (" + sched_as_ints + ")\n" + 146 | Logger.SZERO + init + " processes_sz=" + processes.size()+ "\n" + 147 | Logger.SZERO + init + " intent_sz=" + intents.size() + "\n" + 148 | Logger.SZERO + init + " name_in_path=" + name_in_path + "\n" + 149 | Logger.SZERO + init + "}\n" 150 | ; 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /app/src/main/java/com/releasestandard/scriptmanager/controller/OverflowMenu.java: -------------------------------------------------------------------------------- 1 | package com.releasestandard.scriptmanager.controller; 2 | 3 | import android.graphics.drawable.ColorDrawable; 4 | import android.view.Menu; 5 | import android.view.MenuItem; 6 | 7 | import androidx.appcompat.app.ActionBar; 8 | import androidx.appcompat.widget.Toolbar; 9 | 10 | import com.releasestandard.scriptmanager.MainActivity; 11 | import com.releasestandard.scriptmanager.R; 12 | 13 | import java.util.ArrayList; 14 | 15 | /** 16 | * Implement logic on the settings overflow menu. 17 | */ 18 | public class OverflowMenu { 19 | 20 | private Menu optionsMenu = null; 21 | 22 | public static int MODE_STANDARD = 0; 23 | public static int MODE_NO_EXT = 1; 24 | private static int MODE = MODE_STANDARD; 25 | 26 | private ArrayList optionsMenuItemBackup = new ArrayList(); 27 | 28 | // Collection off ids for the menu 29 | 30 | public static int any_selection_buttons_standard[] = {R.id.action_stopselected, 31 | R.id.action_unselectall,R.id.action_anyselection_delete}; 32 | public static int any_selection_buttons[] = any_selection_buttons_standard; 33 | public static int any_selection_buttons_compat_no_external[] = any_selection_buttons; 34 | 35 | public static int one_only_selection_buttons_standard[] = {R.id.action_oneonly_edit, 36 | R.id.action_oneonly_rename, R.id.action_oneonly_show_log , 37 | R.id.action_oneonly_clear_log 38 | }; 39 | public static int one_only_selection_buttons[] = one_only_selection_buttons_standard; 40 | public static int one_only_selection_buttons_no_external[] = { 41 | R.id.action_oneonly_rename 42 | }; 43 | 44 | int debug_mode[] = {/*R.id.settings_fragment_debug_mode*/}; 45 | 46 | int running_mode[] = {R.id.action_stopall}; 47 | 48 | private MainActivity main = null; 49 | 50 | public static void gotoMode() { gotoMode(MODE_STANDARD);} 51 | public static void gotoMode(int mod) { 52 | if ( MODE == mod ) { return; } 53 | if ( mod == MODE_NO_EXT) { 54 | any_selection_buttons = any_selection_buttons_compat_no_external; 55 | one_only_selection_buttons = one_only_selection_buttons_no_external; 56 | } 57 | if ( mod == MODE_STANDARD) { 58 | any_selection_buttons = any_selection_buttons_standard; 59 | one_only_selection_buttons = one_only_selection_buttons_standard; 60 | } 61 | MODE = mod; 62 | } 63 | public OverflowMenu(MainActivity main, Menu menu) { 64 | this.main = main; 65 | this.optionsMenu = menu; 66 | } 67 | 68 | // helpers 69 | /** 70 | * Hide or show the overflow menu. 71 | */ 72 | public void setMenuVisibility(boolean b) { 73 | Toolbar toolbar = (Toolbar) main.findViewById(R.id.toolbar); 74 | Menu m = toolbar.getMenu(); 75 | 76 | if( b ) { 77 | for (MenuItem mi : optionsMenuItemBackup) { 78 | mi.setVisible(true); 79 | } 80 | optionsMenuItemBackup.clear(); 81 | } 82 | else { 83 | for (int a = 0; a < m.size(); a = a + 1) { 84 | MenuItem mi = m.getItem(a); 85 | if( mi.isVisible() ) { 86 | optionsMenuItemBackup.add(mi); 87 | mi.setVisible(false); 88 | } 89 | } 90 | } 91 | } 92 | /* 93 | * At least one job is running 94 | */ 95 | public void enterRunningMode() { 96 | for (int id : running_mode) { 97 | MenuItem mi = optionsMenu.findItem(id); 98 | mi.setVisible(true); 99 | } 100 | } 101 | public void leaveRunningMode() { 102 | for (int id : running_mode) { 103 | MenuItem mi = optionsMenu.findItem(id); 104 | mi.setVisible(false); 105 | } 106 | } 107 | public void enterDebugMode() { 108 | for (int id : debug_mode) { 109 | MenuItem mi = optionsMenu.findItem(id); 110 | mi.setVisible(true); 111 | } 112 | } 113 | public void leaveDebugMode() { 114 | for (int id : debug_mode) { 115 | MenuItem mi = optionsMenu.findItem(id); 116 | mi.setVisible(false); 117 | } 118 | } 119 | public void enterOneOnlySelectMode() { 120 | for (int id : one_only_selection_buttons) { 121 | MenuItem mi = optionsMenu.findItem(id); 122 | mi.setVisible(true); 123 | } 124 | } 125 | public void leaveOneOnlySelectMode() { 126 | for (int id : one_only_selection_buttons) { 127 | MenuItem mi = optionsMenu.findItem(id); 128 | mi.setVisible(false); 129 | } 130 | } 131 | 132 | /** 133 | * compat 11 134 | */ 135 | public void enterSelectMode() { 136 | ActionBar ab = main.getSupportActionBar(); 137 | ab.setDisplayHomeAsUpEnabled(true); 138 | 139 | for (int id : any_selection_buttons) { 140 | MenuItem mi = optionsMenu.findItem(id); 141 | mi.setVisible(true); 142 | } 143 | main.isInSelectMode = true; 144 | enterOneOnlySelectMode(); 145 | } 146 | 147 | /** 148 | * compat 11 149 | */ 150 | public void leaveSelectMode() { 151 | ActionBar ab = main.getSupportActionBar(); 152 | int color = main.getColorFromId(main, R.attr.colorPrimaryVariant); 153 | ab.setBackgroundDrawable(new ColorDrawable(color)); 154 | ab.setDisplayHomeAsUpEnabled(false); 155 | 156 | for (int id : any_selection_buttons) { 157 | MenuItem mi = optionsMenu.findItem(id); 158 | mi.setVisible(false); 159 | } 160 | main.isInSelectMode = false; 161 | leaveOneOnlySelectMode(); 162 | } 163 | 164 | public void callbackSelectAndRunning(MainActivity main) { 165 | if ( main.jobs_view.getNumberStartedAndSelected() > 0) { 166 | optionsMenu.findItem(R.id.action_stopselected) 167 | .setVisible(true); 168 | } else { 169 | optionsMenu.findItem(R.id.action_stopselected) 170 | .setVisible(false); 171 | } 172 | } 173 | } 174 | -------------------------------------------------------------------------------- /app/src/main/java/com/releasestandard/scriptmanager/controller/TimeManagerView.java: -------------------------------------------------------------------------------- 1 | package com.releasestandard.scriptmanager.controller; 2 | 3 | import android.app.DatePickerDialog; 4 | import android.app.TimePickerDialog; 5 | import android.view.View; 6 | import android.view.ViewGroup; 7 | import android.widget.DatePicker; 8 | import android.widget.TextView; 9 | import android.widget.TimePicker; 10 | 11 | import com.releasestandard.scriptmanager.R; 12 | 13 | public class TimeManagerView { 14 | 15 | public TimeManagerView() { 16 | 17 | } 18 | 19 | /* 20 | * handler called at the end of time picking. 21 | */ 22 | public void onPicked(int minute,int hourOfDay,int dayOfMonth,int monthOfYear,int year) { 23 | 24 | } 25 | 26 | /** 27 | * compat 1 28 | * @param v 29 | * @param sched 30 | */ 31 | public void show(View v, int []sched) { 32 | show(v,sched[0],sched[1],sched[2],sched[3],sched[4]); 33 | } 34 | public void show(View v, int minute, int hourOfDay, int dayOfMonth, int monthOfYear, int year) { 35 | ViewGroup vpg = (ViewGroup)v.getParent(); 36 | TextView tv = vpg.findViewById(R.id.job_date_input); 37 | 38 | // Launch Time Picker Dialog 39 | TimePickerDialog timePickerDialog = new TimePickerDialog(v.getContext(),R.style.AppTheme_TimePicker, 40 | new TimePickerDialog.OnTimeSetListener() { 41 | @Override 42 | public void onTimeSet(TimePicker view, int hourOfDay, 43 | int minute) { 44 | DatePickerDialog datePickerDialog = new DatePickerDialog(v.getContext(),R.style.AppTheme_DatePicker, 45 | new DatePickerDialog.OnDateSetListener() { 46 | @Override 47 | public void onDateSet(DatePicker view, int year, 48 | int monthOfYear, int dayOfMonth) { 49 | int temp_sched[] = {minute,hourOfDay,dayOfMonth,monthOfYear,year}; 50 | tv.setText(com.releasestandard.scriptmanager.model.TimeManager.sched2str(temp_sched)); 51 | onPicked(minute,hourOfDay,dayOfMonth,monthOfYear,year); 52 | } 53 | }, year, monthOfYear, dayOfMonth); 54 | datePickerDialog.show(); 55 | } 56 | }, hourOfDay, minute, false); 57 | timePickerDialog.show(); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /app/src/main/java/com/releasestandard/scriptmanager/model/KSHEvent.java: -------------------------------------------------------------------------------- 1 | package com.releasestandard.scriptmanager.model; 2 | 3 | import android.util.Log; 4 | 5 | import com.releasestandard.scriptmanager.tools.Logger; 6 | 7 | import java.util.Random; 8 | 9 | /** 10 | * How do we handle events from and to java. 11 | * hint: fs 12 | */ 13 | public class KSHEvent { 14 | 15 | private static Integer id = (new Random().nextInt()); 16 | private Integer _id; // id of the event 17 | public String type; // type of event eg SmsReceived 18 | public String[] args; // values of arg0 arg1 eg +33600000000 19 | public String base; // base rep for output 20 | 21 | public KSHEvent(String base) { 22 | this.base = base + "/"; 23 | this._id=this.id; 24 | this.id+=1; 25 | } 26 | public KSHEvent(String base, String type) { 27 | this.base = base + "/"; 28 | this.type = type; 29 | this._id=this.id; 30 | this.id+=1; 31 | } 32 | public KSHEvent(String base, String type, String []args) { 33 | this.type = type; 34 | this.base = base + "/"; 35 | this.args = new String[args.length]; 36 | this._id=this.id; 37 | this.id += 1; 38 | for (int i = 0; i < args.length ; i = i + 1 ) { this.args[i] = args[i]; } 39 | } 40 | 41 | /** 42 | * Send the event from java to ksh 43 | * @return 44 | */ 45 | public String sendJava2ksh() { return sendJava2ksh(args); } 46 | public String sendJava2ksh(String[] args) { 47 | Logger.debug("sendJava2ksh::args"); 48 | for ( String arg : args ) { Logger.debug("sendJava2ksh::args -> " + arg); } 49 | String res = "echo \""+ type +"\" > " + getFunctNameFile() + " "; 50 | Integer i = 0; 51 | for ( String arg : args ) { 52 | String argfile = getArgFile(i); 53 | res += " && echo \"" + arg + "\" > " + argfile + " "; 54 | i += 1; 55 | } 56 | res += ""; 57 | return res; 58 | } 59 | 60 | /** 61 | * Recv events (ALL events that are in the events directory) from java to ksh 62 | * @return 63 | */ 64 | public String recvJava2ksh() { return recvJava2ksh("");} 65 | public String recvJava2ksh(String off) { 66 | String res = "" + 67 | off + "for event in $(ls " + this.base + ") ; do\n" + 68 | off + " eventp=" + this.base + "/$event/;\n" + 69 | off + " functname=$(cat \"" + getFunctNameFile(this.base + "/$event/") + "\" );\n" + 70 | off + " ARGS=($(ls $eventp/arg*));\n" + 71 | off + " i=0;\n" + 72 | off + " while [ \"$i\" -lt \"${#ARGS[@]}\" ] ; do \n" + 73 | off + " ARGS[$i]=\"$(cat ${ARGS[$i]})\";\n" + 74 | off + " let i=i+1;\n" + 75 | off + " done\n" + 76 | off + " type $functname &> /dev/null && " + 77 | off + " $functname \"${ARGS[@]}\" \n" + 78 | off + " clearEvent $event\n" + 79 | off + "done\n" + 80 | off + "" ; 81 | 82 | Logger.debug("recvJava2ksh::res="+res); 83 | return res; 84 | } 85 | 86 | /** 87 | * Send event from ksh to java 88 | * @return 89 | */ 90 | public String sendKsh2java() { 91 | Logger.debug("sendKsh2java not impemented"); 92 | return ""; 93 | } 94 | 95 | /** 96 | * Receive event from ksh to java 97 | * @return 98 | */ 99 | public static boolean recvKsh2java() { 100 | Logger.debug("recvKsh2java not impemented"); 101 | return false; 102 | } 103 | private String getFunctNameFile() { return getFunctNameFile(null); } 104 | private String getFunctNameFile(String path) { 105 | if ( path == null ) { 106 | path = base + "/" + _id; 107 | } 108 | return _getFunctNameFile(path); 109 | } 110 | private static String _getFunctNameFile(String path) { 111 | String name = "/functnamefile"; 112 | return path + name; 113 | } 114 | 115 | private String getArgFile(Integer i) { 116 | return base + "/" + _id + "/arg" + i; 117 | } 118 | public String[] getArgFiles() { 119 | String [] args = new String[this.args.length]; 120 | for(int i = 0; i < this.args.length; i = i +1) { 121 | args[i] = getArgFile(i); 122 | } 123 | return args; 124 | } 125 | 126 | /** 127 | * clear the event from ksh. 128 | */ 129 | public String clearEvent() { return clearEvent(""); } 130 | public String clearEvent(String off) { 131 | return off + "function clearEvent() {\n" + 132 | off + " id=$1;\n" + 133 | off + " path=" + this.base + "/$id/;\n" + 134 | off + " if [ -e \"$path\" ] ; then \n" + 135 | off + " rm -rf \"$path\";\n" + 136 | off + " fi\n" + 137 | off + "}\n"; 138 | } 139 | 140 | /** 141 | * clear the event from java. 142 | */ 143 | public void clear() { 144 | Shell._execCmd("rm -rf " + this.base + "/" + this._id + "/"); 145 | } 146 | 147 | /** 148 | * Check prerequisites (folder creation, ...) from ksh 149 | * @return 150 | */ 151 | public String checkPrereq() { return checkPrereq(""); } 152 | public String checkPrereq(String off) { 153 | return off + "function checkPrereq() {\n" + 154 | off + " id=$1\n" + 155 | off + " mkdir -p \""+this.base+"/$id\"\n" + 156 | off + "}\n"; 157 | } 158 | /** 159 | * check prerequisite from java. 160 | */ 161 | public String prereq() { 162 | Logger.debug("prereq : " + this._id); 163 | String cmd= "mkdir -p \""+this.base+"/" + this._id + "\""; 164 | return cmd; 165 | } 166 | 167 | /** 168 | * Package function in a lib. 169 | * @return 170 | */ 171 | public String packLib() { return packLib(""); } 172 | public String packLib(String off) { 173 | String res = "" + 174 | clearEvent(off) + "\n" + 175 | checkPrereq(off) + "\n" + 176 | sendKsh2java() + "\n"; 177 | 178 | return res; 179 | } 180 | } 181 | -------------------------------------------------------------------------------- /app/src/main/java/com/releasestandard/scriptmanager/model/KornShellInterface.java: -------------------------------------------------------------------------------- 1 | package com.releasestandard.scriptmanager.model; 2 | 3 | import android.util.Log; 4 | 5 | import com.releasestandard.scriptmanager.R; 6 | import com.releasestandard.scriptmanager.tools.Logger; 7 | 8 | import java.io.File; 9 | import java.lang.reflect.InvocationTargetException; 10 | import java.lang.reflect.Method; 11 | import java.util.HashMap; 12 | import java.util.Random; 13 | import java.util.concurrent.TimeUnit; 14 | 15 | /** 16 | * By convention, we use prefix _scriptmanager_ for our internal bash variables. 17 | */ 18 | public class KornShellInterface { 19 | 20 | // these files are used only for this classe so we don't use the StorageManager // 21 | private static String SUFFIX_PID = ".pid"; 22 | private String pidFile = null; 23 | private String evts_path; 24 | private String signal = "USR1"; 25 | private String events_path; 26 | 27 | // public API definition 28 | public HashMap API = new HashMap() {{ 29 | 30 | put(R.string.ioctlSmsReceived,"smsReceived"); // $1 : from $2 : body 31 | 32 | }}; 33 | 34 | 35 | /** 36 | * compat 1 37 | */ 38 | public void dump() { Logger.debug(dump("")); } 39 | public String dump(String off) { 40 | String noff = off + "\t"; 41 | return Logger.SZERO + off + "KornShellInterface {\n"+ 42 | Logger.SZERO + noff + "signal="+signal+"\n" + 43 | Logger.SZERO + off + "}\n" 44 | ; 45 | } 46 | 47 | public KornShellInterface(StorageManager sm) { 48 | } 49 | 50 | /** 51 | * This is a wrapper allow a script to handle events from android. 52 | * compat 23 53 | * (printf) 54 | * @return 55 | */ 56 | public String wrappScript(String in,String out) { 57 | Logger.debug("Transform "+in+" > "+out); 58 | pidFile = out + SUFFIX_PID; 59 | evts_path = StorageManager.getEventsAbsolutePath(StorageManager.getTerminalPart(in)); 60 | KSHEvent kse = new KSHEvent(evts_path); 61 | kse.prereq(); 62 | 63 | String header = "" + 64 | " _scriptmanager_pidf=\"" + pidFile + "\" ; \n" + 65 | " _scriptmanager_SIG=\""+signal+"\" ; \n" + 66 | "\n" + 67 | kse.packLib() + "\n" + 68 | "events_interface () { \n" + 69 | kse.recvJava2ksh(" ") + "\n" + 70 | "}\n" + 71 | "trap \"events_interface\" $_scriptmanager_SIG; \n" + 72 | "\n" + 73 | "echo \"$$\" > " + pidFile + " ; \n" + 74 | "# \n" + 75 | "# user part\n" + 76 | "# \n" + 77 | "\n" + 78 | "\n"; 79 | 80 | String footer = "" + 81 | "\n" + 82 | "\n" + 83 | "#\n" + 84 | "#\n" + 85 | "#\n" + 86 | "while true ; do\n" + 87 | "\tsleep 2 &\n" + 88 | "\twait $!\n" + 89 | "done\n"; 90 | 91 | 92 | String cmd = "" + 93 | "{ printf '" + header + "' > " + out + ";" + 94 | "cat '" + in + "' >> " + out + ";" + 95 | "printf '" + footer + "' >> " + out + "; }"; 96 | 97 | 98 | try { 99 | Shell._execCmd(cmd).waitFor(); 100 | } catch (InterruptedException e) { 101 | Logger.debug("Wrapping has failed"); 102 | e.printStackTrace(Logger.getTraceStream()); 103 | } 104 | 105 | return out; 106 | } 107 | 108 | /** 109 | * Trigger a script callback method 110 | * compat 14 111 | * @param methodToCall 112 | * @param args 113 | */ 114 | public void triggerCallback(String methodToCall,String... args) { 115 | String cmd =""; 116 | Method method = null; 117 | try { 118 | method = KornShellInterface.class.getDeclaredMethod(methodToCall, String.class, String.class); 119 | cmd += method.invoke(this, args[0], args[1]); 120 | cmd += " && "; 121 | } catch (NoSuchMethodException e) { 122 | e.printStackTrace(Logger.getTraceStream()); 123 | } catch (IllegalAccessException e) { 124 | e.printStackTrace(Logger.getTraceStream()); 125 | } catch (InvocationTargetException e) { 126 | e.printStackTrace(Logger.getTraceStream()); 127 | } 128 | cmd += "kill -s " + signal + " $(cat " + pidFile + ");"; 129 | 130 | Logger.debug(cmd); 131 | Shell._execCmd(cmd); 132 | 133 | } 134 | /** 135 | * React to events 136 | * compat 14 137 | * @return 138 | */ 139 | public String triggerRecvMsg(String from, String body) { 140 | 141 | Logger.debug("from="+from+",body="+body); 142 | KSHEvent kshe = new KSHEvent(evts_path, 143 | API.get(R.string.ioctlSmsReceived), 144 | new String[]{from,body}); 145 | 146 | return kshe.prereq() + " && " + kshe.sendJava2ksh(); 147 | } 148 | 149 | /** 150 | * Offer a way to attach process to root (by cmd wrapping) (don't be killed by application when user close it) 151 | * @return 152 | */ 153 | public static String attachToRoot(String cmd) { 154 | // Working example to attach to PPID 1 but get killed anyway :/ 155 | // return "" + cmd + " &"; 156 | return cmd; 157 | } 158 | 159 | /** 160 | * compat 14 161 | * @param cmd 162 | * @param log 163 | * @return 164 | */ 165 | public static String outputToLog(String cmd, String log) { 166 | return "&>> " + log + " " + cmd; 167 | } 168 | 169 | /** 170 | * compat 1 171 | * @param cmd 172 | * @return 173 | */ 174 | public static String[] packIn(String cmd) { 175 | return new String[]{"sh", "-c", cmd}; 176 | } 177 | 178 | } 179 | -------------------------------------------------------------------------------- /app/src/main/java/com/releasestandard/scriptmanager/model/Shell.java: -------------------------------------------------------------------------------- 1 | package com.releasestandard.scriptmanager.model; 2 | 3 | import android.app.AlarmManager; 4 | import android.app.PendingIntent; 5 | import android.content.Context; 6 | import android.content.Intent; 7 | import android.os.SystemClock; 8 | 9 | import com.releasestandard.scriptmanager.AlarmReceiver; 10 | import com.releasestandard.scriptmanager.JavaEventsReceiver; 11 | import com.releasestandard.scriptmanager.tools.CompatAPI; 12 | import com.releasestandard.scriptmanager.tools.Logger; 13 | 14 | import java.io.IOException; 15 | import java.util.ArrayList; 16 | import java.util.Calendar; 17 | import java.util.List; 18 | 19 | /* 20 | * This class use {KornShellInterface,StorageManager} to provide interface with Java. 21 | * all JobView got a Shell associated with it. 22 | */ 23 | public class Shell { 24 | 25 | // The object is used by recursive alarms, so many processes can be setup 26 | private static List processes = new ArrayList(); 27 | private static List intents = new ArrayList(); 28 | 29 | public StorageManager sm = null; 30 | public KornShellInterface bi = null; 31 | 32 | public boolean eventReceiverRegistered = false; 33 | 34 | /** 35 | * This is the shell for a given JobView (one line on screen). 36 | */ 37 | public Shell(StorageManager sm) { 38 | this.sm = new StorageManager(sm); 39 | this.bi = new KornShellInterface(sm); 40 | } 41 | 42 | /** 43 | * parameter could be the name of the script or an absolute path. 44 | * @param scriptname scriptname (last part of pathname) 45 | * @return index in the array of processes 46 | */ 47 | public Integer execScript(String scriptname) { 48 | sm.setScriptName(scriptname); 49 | Logger.debug("<======== State before execScript =======>"); 50 | sm.dump(); 51 | Logger.debug("<========= =========>"); 52 | String output = sm.getOutputAbsolutePath(); 53 | bi.wrappScript( sm.getScriptAbsolutePath(),output); 54 | if ( !eventReceiverRegistered ) { 55 | Logger.debug("eventReceiverRegistered"); 56 | JavaEventsReceiver.listeners.add(this); 57 | eventReceiverRegistered = true; 58 | } 59 | 60 | Logger.log("Job execution : " + output + "\n log=" + sm.getLogAbsolutePath()); 61 | Process p = _execScript(output,sm.getLogAbsolutePath()); 62 | if ( p != null ) { 63 | Logger.debug("Shell: Process has started"); 64 | if ( ! processes.add(p) ) { 65 | return -1; 66 | } 67 | return processes.size()-1; 68 | } 69 | Logger.debug("Shell: Process failed to start"); 70 | return -1; 71 | } 72 | 73 | /** 74 | * Execute script & cmd and return a Process 75 | * compat 1 76 | * @param script 77 | * @return 78 | */ 79 | public static Process _execScript(String script) { 80 | return _execScript(script,null); 81 | } 82 | public static Process _execScript(String script, String log) { return _execCmd(". "+script,log,true); } 83 | public static Process _execCmd(String cmd) { 84 | return _execCmd(cmd,null); 85 | } 86 | public static Process _execCmd(String cmd,String log) { 87 | return _execCmd(cmd,log,false); 88 | } 89 | public static Process _execCmd(String cmd, String log,boolean attachToRoot) { 90 | try { 91 | String real_cmd = cmd; 92 | if ( log != null) { 93 | if ( attachToRoot ) { 94 | real_cmd=KornShellInterface.attachToRoot( 95 | KornShellInterface.outputToLog(cmd, log)); 96 | } else { 97 | real_cmd=KornShellInterface.outputToLog(cmd, log); 98 | } 99 | } else { 100 | if ( attachToRoot ) { 101 | real_cmd=KornShellInterface.attachToRoot(cmd); 102 | } 103 | } 104 | Logger.debug("real_cmd="+real_cmd); 105 | return Runtime.getRuntime().exec(KornShellInterface.packIn(real_cmd)); 106 | } catch (IOException e) { 107 | return null; 108 | } 109 | } 110 | 111 | /** 112 | * Clear logs 113 | * compat 14 114 | * @throws IOException 115 | */ 116 | public void clearLog() throws IOException { clearLog(this.sm.script_name); } 117 | public void clearLog(String logpath) throws IOException { 118 | Shell._execCmd("> "+logpath); 119 | } 120 | 121 | /** 122 | * compat 1 123 | * @param context 124 | * @param scriptname 125 | * @param sched 126 | * @return 127 | */ 128 | public Integer scheduleScript(Context context, String scriptname, int sched[]) { return scheduleScript(context, scriptname, sched, false);} 129 | public Integer scheduleScript(Context context, String scriptname, int sched[], boolean immediate) { 130 | if ( ! intents.add(_scheduleScript(context,scriptname, sched,immediate)) ) { 131 | return -1; 132 | } 133 | return intents.size()-1; 134 | } 135 | /** 136 | * Schedule a script for execution. 137 | * compat 1 138 | */ 139 | public static PendingIntent _scheduleScript(Context context, String scriptname, int sched[]) { return _scheduleScript(context,scriptname,sched,false);} 140 | public static PendingIntent _scheduleScript(Context context, String scriptname, int sched[], boolean immediate) { 141 | // need to get the time here 142 | Calendar next = null; 143 | if (immediate) { 144 | Logger.debug("We got an immediate time here"); 145 | next = TimeManager.getImmediate(); 146 | } 147 | else { 148 | next = TimeManager.nextSched(sched); 149 | } 150 | 151 | Logger.log("[" + (new Integer(AlarmReceiver.REQUEST_CODE + 1)) + "] Script " + scriptname + " scheduled for " + next.getTime().toString()); 152 | long t = next.getTimeInMillis(); 153 | 154 | Intent intent = new Intent(context, AlarmReceiver.class); 155 | intent.putExtra("script",scriptname); 156 | intent.putExtra("sched",sched); 157 | PendingIntent alarmIntent = PendingIntent.getBroadcast(context, AlarmReceiver.REQUEST_CODE++, intent, PendingIntent.FLAG_UPDATE_CURRENT); 158 | CompatAPI.setAlarmIntentTime(context,t,alarmIntent); 159 | Logger.debug("official end"); 160 | return alarmIntent; 161 | } 162 | 163 | /** 164 | * compat 1 165 | */ 166 | public boolean terminateProcess(Integer i) { 167 | if ( i < 0 || i >= processes.size()) { 168 | Logger.debug("invalid index"); 169 | return false; 170 | } 171 | processes.get(i).destroy(); 172 | return true; 173 | } 174 | 175 | /** 176 | * compat 1 177 | * @param i 178 | * @return 179 | */ 180 | public boolean terminateIntent(Integer i) { 181 | if ( i < 0 || i >= intents.size()) { 182 | Logger.debug("invalid index"); 183 | return false; 184 | } 185 | intents.get(i).cancel(); 186 | return true; 187 | } 188 | 189 | /** 190 | * compat 1 191 | */ 192 | public void dump() { Logger.debug(dump("")); } 193 | public String dump(String offset) { 194 | return "" + 195 | Logger.SZERO + offset + "Shell { \n" + 196 | Logger.SZERO + offset + "\tprocesses=" + processes.size() + "\n" + 197 | Logger.SZERO + offset + "\tintents=" + intents.size() + "\n" + 198 | Logger.SZERO + bi.dump(offset + "\t" ) + 199 | Logger.SZERO + sm.dump(offset + "\t") + 200 | Logger.SZERO + offset + "}\n"; 201 | } 202 | } 203 | -------------------------------------------------------------------------------- /app/src/main/java/com/releasestandard/scriptmanager/model/StorageManager.java: -------------------------------------------------------------------------------- 1 | package com.releasestandard.scriptmanager.model; 2 | 3 | import android.content.Context; 4 | import android.util.Log; 5 | 6 | import com.releasestandard.scriptmanager.controller.OverflowMenu; 7 | import com.releasestandard.scriptmanager.tools.CompatAPI; 8 | import com.releasestandard.scriptmanager.tools.Logger; 9 | import com.releasestandard.scriptmanager.R; 10 | 11 | import java.io.BufferedReader; 12 | import java.io.File; 13 | import java.io.FileInputStream; 14 | import java.io.FileNotFoundException; 15 | import java.io.IOException; 16 | import java.io.InputStreamReader; 17 | import java.io.OutputStreamWriter; 18 | import java.util.ArrayList; 19 | import java.util.Arrays; 20 | import java.util.List; 21 | import java.util.regex.Matcher; 22 | import java.util.regex.Pattern; 23 | 24 | /** 25 | * Manage where the data is stored on phone (different places, suffixes, etc). 26 | */ 27 | public class StorageManager { 28 | 29 | public static String externalStorage = "/sdcard/Android/data/" + R.string.app_packageid + "/files/"; 30 | public static String internalStorage = "/data/data/" + R.string.app_packageid + "/files/"; 31 | 32 | public static String SUFFIX_LOG = ".log.txt"; 33 | public static String SUFFIX_SCRIPT = ".txt"; 34 | public static String SUFFIX_STATE = ".xml"; 35 | public static String SUFFIX_OUTPUT= ".out"; 36 | 37 | 38 | public String script_name = ""; 39 | /** 40 | * compat 1 41 | */ 42 | public static void writeIntArray(OutputStreamWriter osw, int tab[], int sz) throws IOException { 43 | Logger.debug("sz="+(new Integer(sz)).toString()); 44 | osw.write(sz); 45 | for(int i = 0; i < sz ; i += 1){ 46 | osw.write(tab[i]); 47 | } 48 | } 49 | 50 | /** 51 | * compat 1 52 | * @param osw 53 | * @param tab 54 | * @throws IOException 55 | */ 56 | public static void writeIntegerArray(OutputStreamWriter osw, List tab) throws IOException { 57 | int[] tabi = new int[tab.size()]; 58 | for (int i = 0; i < tab.size(); i = i +1 ) { 59 | tabi[i]=tab.get(i); 60 | } 61 | writeIntArray(osw,tabi,tab.size()); 62 | } 63 | /** 64 | * Read an int array from input stream. 65 | * compat 1 66 | * @param isr input stream 67 | * @return array readed 68 | * @throws IOException 69 | */ 70 | public static List readIntegerArray(InputStreamReader isr) throws IOException { 71 | List tab = new ArrayList(); 72 | for( int j : readIntArray(isr) ) { tab.add(j); } 73 | return tab; 74 | } 75 | public static int[] readIntArray(InputStreamReader isr) throws IOException { 76 | int j = isr.read(); 77 | short jj = (short)j; 78 | return readIntArray(isr,new Integer(jj)); 79 | } 80 | private static int[] readIntArray(InputStreamReader isr, Integer i) throws IOException { 81 | if ( i < 0 ) { return null; } 82 | int[] tab = new int[i]; 83 | for(int ii = 0; ii < i ; ii += 1) { 84 | int j = isr.read(); 85 | // since any of secondes, minutes, hours, day, month year will go to much high we stop here 86 | short jj = (short)j; 87 | tab[ii]=jj; 88 | } 89 | return tab; 90 | } 91 | 92 | /** 93 | * compat 1 94 | * @param sm 95 | */ 96 | public StorageManager(StorageManager sm) { 97 | setInstance(sm); 98 | } 99 | public StorageManager(Context ctx, String script_name) { StorageManager sm = newInstance(ctx,script_name); setInstance(sm);} 100 | public StorageManager(Context ctx) { StorageManager sm = newInstance(ctx); setInstance(sm);} 101 | public static StorageManager newInstance(Context ctx) { return newInstance(ctx,null); } 102 | public static StorageManager newInstance(Context ctx,String script_name) { 103 | StorageManager sm = new StorageManager(); 104 | if ( script_name != null ) { 105 | sm.script_name = script_name; 106 | } 107 | sm.internalStorage = ctx.getFilesDir().getAbsolutePath(); 108 | sm.externalStorage = CompatAPI.getExternalStorage(ctx); 109 | if ( sm.externalStorage == null ) { 110 | sm.externalStorage = sm.internalStorage; 111 | OverflowMenu.gotoMode(OverflowMenu.MODE_NO_EXT); 112 | } 113 | return sm; 114 | } 115 | public void setInstance(StorageManager sm) { 116 | this.externalStorage = sm.externalStorage; 117 | this.internalStorage = sm.internalStorage; 118 | this.script_name = sm.script_name; 119 | } 120 | public StorageManager() { 121 | 122 | } 123 | 124 | 125 | public String getScriptName() { 126 | return this.script_name; 127 | } 128 | public void setScriptName(String script) { 129 | this.script_name = script; 130 | } 131 | 132 | 133 | /** 134 | * Input : script name or abs path 135 | * Output : log path 136 | * compat 137 | */ 138 | private String getLogPath() { return getLogPath(this.script_name); } 139 | private String getLogPath(String script_name) { return addSuffixeIfNeeded(script_name , SUFFIX_LOG); } 140 | public String getLogAbsolutePath() { return getLogAbsolutePath(this.script_name); } 141 | public String getLogAbsolutePath(String script_name) { return getExternalAbsolutePath(getLogPath(script_name)); } 142 | private String getScriptPath() { return getScriptPath(this.script_name);} 143 | private String getScriptPath(String script_name) { return addSuffixeIfNeeded(script_name,SUFFIX_SCRIPT);} 144 | public String getScriptAbsolutePath() { return this.getScriptAbsolutePath(this.script_name); } 145 | public String getScriptAbsolutePath(String scriptname) { return getExternalAbsolutePath(getScriptPath(scriptname)); } 146 | private String getOutputPath() { return getOutputPath(this.script_name); } 147 | private String getOutputPath(String scriptname) { return addSuffixeIfNeeded(scriptname,SUFFIX_OUTPUT); } 148 | public String getOutputAbsolutePath() { return this.getOutputAbsolutePath(this.script_name); } 149 | public String getOutputAbsolutePath(String scriptname) { return getInternalAbsolutePath(getOutputPath(scriptname)); } 150 | public String getStateFileNameInPath() { return this.getStateFileNameInPath(this.script_name); } 151 | public String getStateFileNameInPath(String script_name) { return script_name + SUFFIX_STATE ; } 152 | public static String getEventsAbsolutePath(String script_name) { 153 | String name_wo_suf = removeSuffix(script_name); 154 | return internalStorage + "/" + name_wo_suf + "/events/"; 155 | } 156 | public static String getEventsRelativePath(String script_name) { 157 | String name_wo_suf = removeSuffix(script_name); 158 | return name_wo_suf + "/events/"; 159 | } 160 | public static String getEventRelativePath(String script_name, String eid) { 161 | return getEventsRelativePath(script_name) + "/"+eid+"/"; 162 | } 163 | public static Boolean isArgFile(String path) { 164 | return getTerminalPart(path).lastIndexOf("arg") != -1; 165 | } 166 | 167 | /** 168 | * Remove any suffix from script_name. 169 | * @return 170 | */ 171 | public String removeSuffix() { return removeSuffix(this.script_name); } 172 | public static String removeSuffix(String script_name) { 173 | String [] suffixes = new String[]{ 174 | StorageManager.SUFFIX_LOG, 175 | StorageManager.SUFFIX_SCRIPT, 176 | StorageManager.SUFFIX_OUTPUT, 177 | StorageManager.SUFFIX_STATE 178 | }; 179 | for(String suffixe : suffixes) { 180 | int i = script_name.lastIndexOf(suffixe); 181 | if (i < 0) { 182 | continue; 183 | } 184 | String name_wo_suf = script_name.substring(0, i); 185 | return name_wo_suf; 186 | } 187 | return script_name; 188 | } 189 | 190 | /** 191 | * Input : script name or abs path 192 | * Output : script abs path to external storage 193 | * compat 1 194 | */ 195 | private String getExternalAbsolutePath() { return getExternalAbsolutePath(this.script_name); } 196 | public String getExternalAbsolutePath(String name) { return getResolvedPath(name,this.externalStorage); } 197 | private String getInternalAbsolutePath() { return getInternalAbsolutePath(this.script_name); } 198 | public String getInternalAbsolutePath(String name) { return getResolvedPath(name,this.internalStorage); } 199 | private String getResolvedPath(String name, String path) { 200 | Pattern p = Pattern.compile("^/"); 201 | Matcher m = p.matcher(name); 202 | if (!m.find()) { 203 | name = path + "/" + name; 204 | } 205 | return name; 206 | } 207 | /** 208 | * Check if a filename has a suffix and add it if needed. 209 | * compat 1 210 | */ 211 | private String addSuffixeIfNeeded(String string, String suf) { 212 | Pattern p = Pattern.compile(suf+"$"); 213 | Matcher m = p.matcher(script_name); 214 | if (!m.find()) { 215 | return string + suf; 216 | } 217 | return string; 218 | } 219 | /** 220 | * Output : script names (usables by get*AbsolutePath) 221 | * compat 1 222 | */ 223 | public ArrayList getScriptsFromFilesystem(Context c) { 224 | ArrayList l = new ArrayList<>(); 225 | File directory = c.getFilesDir(); 226 | File[] files = directory.listFiles(); 227 | Arrays.sort(files); 228 | for (int i = 0; i < files.length; i++) { 229 | File file = files[i]; 230 | String n = file.getName(); 231 | Pattern p = Pattern.compile("([^/]+)" + SUFFIX_STATE + "$"); 232 | Matcher m = p.matcher(n); 233 | if (m.matches()) { 234 | l.add(m.group(1)); 235 | } 236 | } 237 | return l; 238 | } 239 | 240 | /** 241 | * Extract filename from file path. 242 | * compat 1 243 | * @param pathname 244 | * @return 245 | */ 246 | public static String getTerminalPart(String pathname) { 247 | String [] tab = pathname.split("/"); 248 | if ( tab.length <= 0) { 249 | return null; 250 | } 251 | return tab[tab.length-1]; 252 | } 253 | 254 | // This function will be executed when no access to Regex object 255 | 256 | /** 257 | * Check if a file is a "state file" of a given job. 258 | * compat 1 259 | * @param fname 260 | * @return 261 | */ 262 | public static boolean isStateFile(String fname) { 263 | int i = fname.lastIndexOf(SUFFIX_STATE); 264 | if ( i < 0 ) { 265 | return false; 266 | } 267 | return (SUFFIX_STATE.length() + i ) == fname.length(); 268 | } 269 | 270 | /** 271 | * compat 1 272 | */ 273 | public static OutputStreamWriter getOSW(Context ctx, String name) { 274 | try { 275 | return new OutputStreamWriter(ctx.openFileOutput(name,Context.MODE_PRIVATE)); 276 | } catch (FileNotFoundException e) { 277 | return null; 278 | } 279 | } 280 | 281 | /** 282 | * compat 1 283 | */ 284 | public static InputStreamReader getISR(Context ctx, String name) { 285 | try { 286 | FileInputStream fis = ctx.openFileInput(name); 287 | InputStreamReader isr = new InputStreamReader(fis); 288 | return isr; 289 | } catch (FileNotFoundException e) { 290 | return null; 291 | } 292 | } 293 | /** 294 | * better implementation 295 | * @param ctx 296 | * @param path 297 | * @return 298 | */ 299 | public static InputStreamReader newGetISR(Context ctx, String path) { 300 | FileInputStream fis = null; 301 | try { 302 | fis = new FileInputStream(new File(path)); 303 | } catch (FileNotFoundException e) { 304 | e.printStackTrace(Logger.getTraceStream()); 305 | return null; 306 | } 307 | InputStreamReader isr = new InputStreamReader(fis); 308 | return isr; 309 | } 310 | public static String fileAsText(Context ctx, String path) { 311 | String res = ""; 312 | InputStreamReader isr = newGetISR(ctx,path); 313 | BufferedReader buffr = new BufferedReader(isr); 314 | while (true) { 315 | String l = null; 316 | try { 317 | l = buffr.readLine(); 318 | } catch (IOException e) { 319 | e.printStackTrace(Logger.getTraceStream()); 320 | break; 321 | } 322 | if ( l == null ) { 323 | break; 324 | } 325 | res += l; 326 | } 327 | return res; 328 | } 329 | 330 | /** 331 | * compat 1 332 | */ 333 | public void dump() { Logger.debug(dump("")); } 334 | public String dump(String off) { 335 | String noff = off + "\t"; 336 | return "" + 337 | Logger.SZERO + off + "StorageManager {\n" + 338 | Logger.SZERO + noff + "externalStorage="+externalStorage+"\n" + 339 | Logger.SZERO + noff + "internalStorage="+internalStorage+"\n"+ 340 | Logger.SZERO + noff + "SUFFIX_LOG=" + SUFFIX_LOG + "\n" + 341 | Logger.SZERO + noff + "SUFFIX_SCRIPT=" + SUFFIX_SCRIPT + "\n" + 342 | Logger.SZERO + noff + "SUFFIX_STATE=" + SUFFIX_STATE + "\n" + 343 | Logger.SZERO + noff + "SUFFIX_OUTPUT=" + SUFFIX_OUTPUT + "\n" + 344 | Logger.SZERO + noff + "script_name=" + script_name + "\n" + 345 | Logger.SZERO + off + "}\n" 346 | ; 347 | } 348 | } 349 | -------------------------------------------------------------------------------- /app/src/main/java/com/releasestandard/scriptmanager/model/TimeManager.java: -------------------------------------------------------------------------------- 1 | package com.releasestandard.scriptmanager.model; 2 | 3 | import java.util.Calendar; 4 | import java.util.GregorianCalendar; 5 | import java.util.regex.Matcher; 6 | import java.util.regex.Pattern; 7 | 8 | /** 9 | * Handle time conversions between different formats. 10 | * compat 1 11 | */ 12 | public class TimeManager { 13 | 14 | public final static Integer EACH_TIME = -1; 15 | 16 | public static int[] packIn(int minute, int hourOfDay, int dayOfMonth, int monthOfYear, int year) { int sched[]={minute,hourOfDay,dayOfMonth,monthOfYear,year} ; return sched ; } 17 | public static int[] str2sched(String s) { 18 | int sched[] = {EACH_TIME, EACH_TIME, EACH_TIME, EACH_TIME, EACH_TIME}; 19 | int i = 0; 20 | String [] parts = s.split(" "); 21 | if ( parts.length == sched.length ) { 22 | for (String ss : parts) { 23 | if (ss.equals(new String("*"))) { 24 | sched[i] = EACH_TIME; 25 | } else { 26 | sched[i] = (int) Integer.parseInt(ss); 27 | } 28 | i = i + 1; 29 | } 30 | } 31 | return sched; 32 | } 33 | 34 | public static String sched2str(int [] s) { 35 | String res = ""; 36 | for (int i : s) { 37 | if ( i < 0 ) { 38 | res += "* "; 39 | } 40 | else { 41 | Integer ii = new Integer(i); 42 | res += ii + " "; 43 | } 44 | } 45 | return res; 46 | } 47 | 48 | public static boolean isRepeated(int [] sched) { 49 | for ( int i = 0 ; i < 5 ; i = i +1 ) { 50 | if ( sched[i] == EACH_TIME ) { 51 | return true; 52 | } 53 | } 54 | return false; 55 | } 56 | 57 | public static Calendar getImmediate() { 58 | Calendar c = new GregorianCalendar(); 59 | c.setTimeInMillis(System.currentTimeMillis()); 60 | return c; 61 | } 62 | public static Calendar nextSched(int [] sched) { 63 | Calendar c = new GregorianCalendar(); 64 | int parts[]={ Calendar.MINUTE,Calendar.HOUR, 65 | Calendar.DAY_OF_MONTH,Calendar.MONTH, 66 | Calendar.YEAR}; 67 | c.set(Calendar.MILLISECOND,0); 68 | c.set(Calendar.SECOND,0); 69 | 70 | // if we count in 71 | boolean areWeInEachThing = false; 72 | for ( int i = 0; i < 5 ; i = i + 1 ) { 73 | if ( sched[i] == EACH_TIME ) { 74 | // we need to apply this only on the first each 75 | if ( areWeInEachThing == false ) { 76 | c.set(parts[i], c.get(parts[i]) + 1); 77 | areWeInEachThing = true; 78 | } 79 | else { 80 | c.set(parts[i], c.get(parts[i])); 81 | } 82 | } 83 | else { 84 | c.set(parts[i], sched[i]); 85 | } 86 | } 87 | return c; 88 | } 89 | 90 | public static boolean validDate(String s) { 91 | String family = "0-9\\*"; 92 | Pattern p = Pattern.compile( 93 | "^[ ]*["+family+ 94 | "]+[ ]+["+family+"]+[ ]+["+family+ 95 | "]+[ ]+["+family+"]+[ ]+["+family+"]+[ ]*$"); 96 | Matcher m = p.matcher(s); 97 | return m.matches(); 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /app/src/main/java/com/releasestandard/scriptmanager/tools/CallStack.java: -------------------------------------------------------------------------------- 1 | package com.releasestandard.scriptmanager.tools; 2 | 3 | import android.util.Log; 4 | 5 | import java.util.Map; 6 | import java.util.Set; 7 | 8 | public class CallStack { 9 | 10 | 11 | public static String getLastCaller() { 12 | return getLastCaller(Thread.getAllStackTraces().entrySet()); 13 | } 14 | 15 | public static String getLastCaller(Integer off) { 16 | return getLastCaller(Thread.getAllStackTraces().entrySet(), off); 17 | } 18 | public static String getLastCaller(Set> set) { 19 | return getLastCaller(set, 3); 20 | } 21 | 22 | private static String getLastCaller(Set> set, Integer offset) { 23 | for (Map.Entry entry : set) { 24 | Integer n = offset; 25 | if (n < 0 || !entry.getKey().getName().equals("main")) { 26 | continue; 27 | } 28 | StackTraceElement[] es = entry.getValue(); 29 | for( int a = 0; a < es.length; a = a + 1) { 30 | StackTraceElement ste = es[a]; 31 | String s; 32 | if(ste.getClassName().lastIndexOf(Logger.packageid) != -1 && 33 | ste.getClassName().lastIndexOf(Logger.packageid + ".tools.Logger") == -1 && 34 | ste.getClassName().lastIndexOf(Logger.packageid + ".tools.CallStack") == -1 ) { 35 | return clearName(ste.getClassName()) + "#" + clearName(ste.getMethodName()); 36 | } 37 | } 38 | } 39 | return null; 40 | } 41 | 42 | private static String clearName(String name) { 43 | Integer i = name.lastIndexOf(".") + 1; 44 | if (i == -1) { 45 | return name; 46 | } 47 | return name.substring(i); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /app/src/main/java/com/releasestandard/scriptmanager/tools/CompatAPI.java: -------------------------------------------------------------------------------- 1 | package com.releasestandard.scriptmanager.tools; 2 | 3 | import android.app.AlarmManager; 4 | import android.app.PendingIntent; 5 | import android.content.Context; 6 | import android.content.Intent; 7 | import android.content.res.Resources; 8 | import android.os.Build; 9 | import android.os.Environment; 10 | import android.provider.DocumentsContract; 11 | 12 | import androidx.core.app.AlarmManagerCompat; 13 | import androidx.preference.PreferenceFragmentCompat; 14 | 15 | import com.releasestandard.scriptmanager.MainActivity; 16 | 17 | import java.io.File; 18 | 19 | import static android.app.AlarmManager.RTC_WAKEUP; 20 | 21 | /** 22 | * Handle API differences in Android. 23 | */ 24 | public class CompatAPI { 25 | 26 | private static AlarmManager am = null; 27 | /** 28 | * Open a file or directory 29 | * compat 1 30 | */ 31 | public static boolean openDocument(MainActivity main) { return openDocument(main,null); } 32 | public static boolean openDocument(MainActivity main, String selectedUri) { 33 | Intent intent; 34 | intent = new Intent(Intent.ACTION_GET_CONTENT); 35 | intent.addCategory(Intent.CATEGORY_OPENABLE); 36 | intent.setType("text/*"); 37 | if ( Build.VERSION.SDK_INT >= 26 && selectedUri != null ) { 38 | intent.putExtra(DocumentsContract.EXTRA_INITIAL_URI, selectedUri); 39 | } 40 | main.startActivityForResult(intent, main.ACTIVITY_REQUEST_CODE_IMPORT); 41 | return true; 42 | } 43 | 44 | /** 45 | * Handle alarm set time. 46 | * compat 1 47 | */ 48 | public static boolean setAlarmIntentTime(Context context, long t, PendingIntent alarmIntent) { 49 | if ( am == null ) { 50 | am = (AlarmManager)context.getSystemService(Context.ALARM_SERVICE); 51 | } 52 | AlarmManagerCompat.setExactAndAllowWhileIdle(am, RTC_WAKEUP, t, alarmIntent); 53 | return true; 54 | } 55 | 56 | /** 57 | * Modify settings when features are not avaliable. 58 | */ 59 | public static boolean modifySettings(PreferenceFragmentCompat settings) { 60 | Resources r = settings.getContext().getResources(); 61 | return true; 62 | } 63 | /** 64 | * compat 1 65 | * Return a path to external storage or null, if unmounted or doesn't exists. 66 | */ 67 | public static String getExternalStorage(Context ctx) { 68 | Logger.debug("decision:"); 69 | String state = Environment.getExternalStorageState(); 70 | String bad_states[] = new String[]{Environment.MEDIA_REMOVED, Environment.MEDIA_UNMOUNTED, Environment.MEDIA_NOFS, Environment.MEDIA_MOUNTED_READ_ONLY, 71 | Environment.MEDIA_BAD_REMOVAL, Environment.MEDIA_UNMOUNTABLE}; 72 | for(String bs : bad_states) { 73 | if( state.equals(bs) ) { 74 | Logger.debug("external storage not valid (" + bs + ")"); 75 | return null; 76 | } 77 | } 78 | 79 | if (Build.VERSION.SDK_INT < 8) { 80 | String sf = Environment.getExternalStorageDirectory() + "/" + Logger.appname; 81 | File f = new File(sf); 82 | if ( f != null ) { 83 | f.mkdir(); 84 | if (f.exists()) { 85 | Logger.debug("directory created in external storage"); 86 | return f.getAbsolutePath(); 87 | } 88 | } 89 | Logger.debug("something has failed"); 90 | return null; 91 | } 92 | else { 93 | File f = ctx.getExternalFilesDir(null); 94 | Logger.debug("success"); 95 | return f.getAbsolutePath(); 96 | } 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /app/src/main/java/com/releasestandard/scriptmanager/tools/Logger.java: -------------------------------------------------------------------------------- 1 | package com.releasestandard.scriptmanager.tools; 2 | 3 | import android.util.Log; 4 | 5 | import java.io.ByteArrayOutputStream; 6 | import java.io.PrintStream; 7 | 8 | /** 9 | * Show messages on stdout (logcat) 10 | * compat 1 11 | */ 12 | public class Logger { 13 | 14 | public static boolean DEBUG = true; 15 | public static final String appname = "scriptmanager"; 16 | public static final String packageid = "com.releasestandard.scriptmanager"; 17 | private static String RED = "31"; 18 | private static String GREEN = "32"; 19 | private static String YELLOW = "33"; 20 | public static String SRED = "\033[" + RED + "m"; 21 | public static String SGREEN = "\033[" + GREEN + "m"; 22 | public static String SYELLOW = "\033[" + YELLOW + "m"; 23 | public static String SZERO = "\033[0m"; 24 | public static void debug(String msg) { debug(msg,SGREEN); } 25 | public static void debug(String msg, String color) { 26 | if ( DEBUG ) { 27 | String tag = color + appname+"/" + CallStack.getLastCaller(6) ; 28 | Log.v(tag, SZERO + msg); 29 | } 30 | } 31 | public static void log(String msg) { 32 | Log.v(appname,msg); 33 | } 34 | public static void unsupported(Integer min) { 35 | unsupported(min,-1); 36 | } 37 | public static void unsupported(Integer min, Integer max) { 38 | if ( max < 0) { 39 | debug("API < " + min.toString() + " are not supported",SRED); 40 | } else { 41 | debug("API < " + min.toString() + " or API > " + max.toString() + " are not supported",SRED); 42 | } 43 | } 44 | public static PrintStream getTraceStream() { 45 | if ( DEBUG ) { 46 | return System.out; 47 | } else { 48 | ByteArrayOutputStream buffer = new ByteArrayOutputStream(); 49 | PrintStream os = new PrintStream(buffer); 50 | return os; 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | 15 | 18 | 21 | 22 | 23 | 24 | 30 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 10 | 12 | 14 | 16 | 18 | 20 | 22 | 24 | 26 | 28 | 30 | 32 | 34 | 36 | 38 | 40 | 42 | 44 | 46 | 48 | 50 | 52 | 54 | 56 | 58 | 60 | 62 | 64 | 66 | 68 | 70 | 72 | 74 | 75 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ReleaseStandard/ScriptManager/270aae7e68f9cdaf8ff3f293a49ca8408df3773d/app/src/main/res/drawable/logo.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/logo2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ReleaseStandard/ScriptManager/270aae7e68f9cdaf8ff3f293a49ca8408df3773d/app/src/main/res/drawable/logo2.png -------------------------------------------------------------------------------- /app/src/main/res/layout/job_view.xml: -------------------------------------------------------------------------------- 1 | 2 | 12 | 13 | 14 | 22 | 23 | 36 | 37 | 41 | 42 | 49 | 50 | 56 | 57 | 58 | 63 | 64 | 73 | 74 |