diff --git a/src/jni/JsDebugger.cpp b/src/jni/JsDebugger.cpp index 9595c7b84..db4a4bb58 100644 --- a/src/jni/JsDebugger.cpp +++ b/src/jni/JsDebugger.cpp @@ -2,6 +2,7 @@ #include "V8GlobalHelpers.h" #include "JniLocalRef.h" #include +#include using namespace std; using namespace tns; @@ -10,26 +11,33 @@ JsDebugger::JsDebugger() { } -void JsDebugger::Init(v8::Isolate *isolate, const string& packageName) +void JsDebugger::Init(v8::Isolate *isolate, const string& packageName, jobject jsDebugger) { + //__android_log_print(ANDROID_LOG_INFO, "TNS.JsDebugger", "JsDebugger::Init called"); + s_isolate = isolate; s_packageName = packageName; JEnv env; + s_jsDebugger = env.NewGlobalRef(jsDebugger); + s_JsDebuggerClass = env.FindClass("com/tns/JsDebugger"); assert(s_JsDebuggerClass != nullptr); //what is enqueue message used for - s_EnqueueMessage = env.GetStaticMethodID(s_JsDebuggerClass, "enqueueMessage", "(Ljava/lang/String;)V"); + s_EnqueueMessage = env.GetMethodID(s_JsDebuggerClass, "enqueueMessage", "(Ljava/lang/String;)V"); assert(s_EnqueueMessage != nullptr); //enableAgent.start(.stop) what is it used for ? - s_EnableAgent = env.GetStaticMethodID(s_JsDebuggerClass, "enableAgent", "(Ljava/lang/String;IZ)V"); - assert(s_EnqueueMessage != nullptr); + s_EnableAgent = env.GetMethodID(s_JsDebuggerClass, "enableAgent", "()V"); + assert(s_EnableAgent != nullptr); + + } string JsDebugger::GetPackageName() { + //__android_log_print(ANDROID_LOG_INFO, "TNS.JsDebugger", "JsDebugger::GetPackageName"); return s_packageName; } @@ -39,13 +47,21 @@ string JsDebugger::GetPackageName() */ void JsDebugger::MyMessageHandler(const v8::Debug::Message& message) { + //__android_log_print(ANDROID_LOG_INFO, "TNS.JsDebugger", "JsDebugger::MyMessageHandler"); + + if (s_jsDebugger == nullptr) + { + //__android_log_print(ANDROID_LOG_INFO, "TNS.JsDebugger", "JsDebugger s_jsDebugger is null...returning...."); + return; + } + auto json = message.GetJSON(); auto str = ConvertToString(json); JEnv env; JniLocalRef s(env.NewStringUTF(str.c_str())); - env.CallStaticVoidMethod(s_JsDebuggerClass, s_EnqueueMessage, (jstring)s); + env.CallVoidMethod(s_jsDebugger, s_EnqueueMessage, (jstring)s); } /* * @@ -53,6 +69,7 @@ void JsDebugger::MyMessageHandler(const v8::Debug::Message& message) */ void JsDebugger::Enable() { + //__android_log_print(ANDROID_LOG_INFO, "TNS.JsDebugger", "JsDebugger::Enable called"); auto isolate = s_isolate; v8::Isolate::Scope isolate_scope(isolate); v8::HandleScope handleScope(isolate); @@ -65,6 +82,7 @@ void JsDebugger::Enable() */ void JsDebugger::Disable() { + //__android_log_print(ANDROID_LOG_INFO, "TNS.JsDebugger", "JsDebugger::Disable called"); auto isolate = s_isolate; v8::Isolate::Scope isolate_scope(isolate); v8::HandleScope handleScope(isolate); @@ -78,6 +96,7 @@ void JsDebugger::Disable() */ void JsDebugger::DebugBreak() { + //__android_log_print(ANDROID_LOG_INFO, "TNS.JsDebugger", "JsDebugger::DebugBreak called"); auto isolate = s_isolate; v8::Isolate::Scope isolate_scope(isolate); v8::HandleScope handleScope(isolate); @@ -87,6 +106,7 @@ void JsDebugger::DebugBreak() void JsDebugger::ProcessDebugMessages() { + //__android_log_print(ANDROID_LOG_INFO, "TNS.JsDebugger", "JsDebugger::ProcessDebugMessages called"); auto isolate = s_isolate; v8::Isolate::Scope isolate_scope(isolate); v8::HandleScope handleScope(isolate); @@ -96,6 +116,7 @@ void JsDebugger::ProcessDebugMessages() void JsDebugger::SendCommand(uint16_t *cmd, int length) { + //__android_log_print(ANDROID_LOG_INFO, "TNS.JsDebugger", "JsDebugger::SendCommand called"); auto isolate = s_isolate; v8::Debug::SendCommand(isolate, cmd, length, nullptr); @@ -103,23 +124,24 @@ void JsDebugger::SendCommand(uint16_t *cmd, int length) void JsDebugger::DebugBreakCallback(const v8::FunctionCallbackInfo& args) { - JEnv env; - JniLocalRef packageName(env.NewStringUTF(s_packageName.c_str())); - jint port = 8181; - if ((args.Length() > 0) && args[0]->IsInt32()) + //__android_log_print(ANDROID_LOG_INFO, "TNS.JsDebugger", "JsDebugger::DebugBreakCallback called"); + + if (s_jsDebugger == nullptr) { - port = args[0]->Int32Value(); + //__android_log_print(ANDROID_LOG_INFO, "TNS.JsDebugger", "JsDebugger s_jsDebugger is null...returning"); + return; } - jboolean jniFalse = JNI_FALSE; - env.CallStaticVoidMethod(s_JsDebuggerClass, s_EnableAgent, (jstring)packageName, port, jniFalse); + JEnv env; + env.CallVoidMethod(s_jsDebugger, s_EnableAgent); DebugBreak(); } v8::Isolate* JsDebugger::s_isolate = nullptr; +jobject JsDebugger::s_jsDebugger = nullptr; string JsDebugger::s_packageName = ""; jclass JsDebugger::s_JsDebuggerClass = nullptr; jmethodID JsDebugger::s_EnqueueMessage = nullptr; diff --git a/src/jni/JsDebugger.h b/src/jni/JsDebugger.h index 177e93343..9bcf749dc 100644 --- a/src/jni/JsDebugger.h +++ b/src/jni/JsDebugger.h @@ -11,7 +11,7 @@ namespace tns class JsDebugger { public: - static void Init(v8::Isolate *isolate, const std::string& packageName); + static void Init(v8::Isolate *isolate, const std::string& packageName, jobject jsDebugger); static void ProcessDebugMessages(); @@ -34,6 +34,7 @@ namespace tns static std::string s_packageName; static jclass s_JsDebuggerClass; + static jobject s_jsDebugger; static jmethodID s_EnqueueMessage; static jmethodID s_EnableAgent; static v8::Isolate *s_isolate; diff --git a/src/jni/com_tns_JsDebugger.cpp b/src/jni/com_tns_JsDebugger.cpp index d5c447ed1..64023d7e9 100644 --- a/src/jni/com_tns_JsDebugger.cpp +++ b/src/jni/com_tns_JsDebugger.cpp @@ -2,6 +2,7 @@ #include "JsDebugger.h" #include "ArgConverter.h" #include +#include extern "C" void Java_com_tns_JsDebugger_processDebugMessages(JNIEnv *env, jobject obj) { @@ -10,22 +11,26 @@ extern "C" void Java_com_tns_JsDebugger_processDebugMessages(JNIEnv *env, jobjec extern "C" void Java_com_tns_JsDebugger_enable(JNIEnv *env, jobject obj) { + //__android_log_print(ANDROID_LOG_INFO, "TNS.JsDebugger", "Java_com_tns_JsDebugger_enable called"); tns::JsDebugger::Enable(); } extern "C" void Java_com_tns_JsDebugger_disable(JNIEnv *env, jobject obj) { + //__android_log_print(ANDROID_LOG_INFO, "TNS.JsDebugger", "Java_com_tns_JsDebugger_disable called"); tns::JsDebugger::Disable(); } extern "C" void Java_com_tns_JsDebugger_debugBreak(JNIEnv *env, jobject obj) { + //__android_log_print(ANDROID_LOG_INFO, "TNS.JsDebugger", "Java_com_tns_JsDebugger_debugBreak called"); tns::JsDebugger::DebugBreak(); } extern "C" void Java_com_tns_JsDebugger_sendCommand(JNIEnv *_env, jobject obj, jbyteArray command, jint length) { + //__android_log_print(ANDROID_LOG_INFO, "TNS.JsDebugger", "Java_com_tns_JsDebugger_sendCommand called"); tns::JEnv env(_env); auto buf = new jbyte[length]; diff --git a/src/jni/com_tns_Platform.cpp b/src/jni/com_tns_Platform.cpp index e20ae47c7..399bf16d7 100644 --- a/src/jni/com_tns_Platform.cpp +++ b/src/jni/com_tns_Platform.cpp @@ -101,7 +101,7 @@ void PrepareExtendFunction(Isolate *isolate, jstring filesPath) DEBUG_WRITE("Executed prepareExtend.js script"); } -void PrepareV8Runtime(Isolate *isolate, JEnv& env, jstring filesPath, jstring packageName, jstring jsoptions) +void PrepareV8Runtime(Isolate *isolate, JEnv& env, jstring filesPath, jstring packageName, jstring jsoptions, jobject jsDebugger) { string v8flags = ArgConverter::jstringToString(jsoptions); V8::SetFlagsFromString(v8flags.c_str(), v8flags.size()); @@ -154,7 +154,7 @@ void PrepareV8Runtime(Isolate *isolate, JEnv& env, jstring filesPath, jstring pa string pckName = ArgConverter::jstringToString(packageName); Profiler::Init(pckName); - JsDebugger::Init(isolate, pckName); + JsDebugger::Init(isolate, pckName, jsDebugger); //PrepareExtendFunction(isolate, filesPath); @@ -163,7 +163,7 @@ void PrepareV8Runtime(Isolate *isolate, JEnv& env, jstring filesPath, jstring pa NativeScriptRuntime::CreateTopLevelNamespaces(global); } -extern "C" void Java_com_tns_Platform_initNativeScript(JNIEnv *_env, jobject obj, jstring filesPath, jint appJavaObjectId, jboolean verboseLoggingEnabled, jstring packageName, jstring jsoptions) +extern "C" void Java_com_tns_Platform_initNativeScript(JNIEnv *_env, jobject obj, jstring filesPath, jint appJavaObjectId, jboolean verboseLoggingEnabled, jstring packageName, jstring jsoptions, jobject jsDebugger) { AppJavaObjectID = appJavaObjectId; tns::LogEnabled = verboseLoggingEnabled; @@ -184,7 +184,7 @@ extern "C" void Java_com_tns_Platform_initNativeScript(JNIEnv *_env, jobject obj ExceptionUtil::GetInstance()->Init(g_jvm, g_objectManager); JEnv env(_env); - PrepareV8Runtime(isolate, env, filesPath, packageName, jsoptions); + PrepareV8Runtime(isolate, env, filesPath, packageName, jsoptions, jsDebugger); NativeScriptRuntime::APP_FILES_DIR = ArgConverter::jstringToString(filesPath); Constants::APP_ROOT_FOLDER_PATH = NativeScriptRuntime::APP_FILES_DIR + "/app/"; diff --git a/src/src/com/tns/ErrorReport.java b/src/src/com/tns/ErrorReport.java index 239d9ea52..077bb828e 100644 --- a/src/src/com/tns/ErrorReport.java +++ b/src/src/com/tns/ErrorReport.java @@ -24,28 +24,28 @@ class ErrorReport { public static final String ERROR_FILE_NAME = "hasError"; private final Activity activity; - + private final static String EXTRA_NATIVESCRIPT_ERROR_REPORT = "NativeScriptErrorMessage"; private final static String EXTRA_ERROR_REPORT_MSG = "msg"; private final static int EXTRA_ERROR_REPORT_VALUE = 1; - + public ErrorReport(Activity activity) { this.activity = activity; } - + static boolean startActivity(final Context context, String errorMessage) { final Intent intent = getIntent(context); - if(intent == null) + if (intent == null) { - return false; //(if in release mode) don't do anything + return false; // (if in release mode) don't do anything } - + intent.putExtra(EXTRA_ERROR_REPORT_MSG, errorMessage); - + createErrorFile(context); - + try { startPendingErrorActivity(context, intent); @@ -55,29 +55,30 @@ static boolean startActivity(final Context context, String errorMessage) Log.d("ErrorReport", "Couldn't send pending intent! Exception: " + e.getMessage()); } - killProcess(context); - + killProcess(context); + return true; } - + static void killProcess(Context context) { // finish current activity and all below it first - if (context instanceof Activity) { - ((Activity)context).finishAffinity(); + if (context instanceof Activity) + { + ((Activity) context).finishAffinity(); } - //kill process + // kill process android.os.Process.killProcess(android.os.Process.myPid()); } - + static void startPendingErrorActivity(Context context, Intent intent) throws CanceledException { PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT); - + pendingIntent.send(context, 0, intent); } - + static String getErrorMessage(Throwable ex) { String content; @@ -103,53 +104,57 @@ static String getErrorMessage(Throwable ex) if (ps != null) ps.close(); } - + return content; } - + static Intent getIntent(Context context) { - Class errorActivityClass = Platform.getErrorActivityClass(); //can be null or can be provided beforehand - - //if in debug and errorActivityClass is not provided use ErrorReportActivity class - if(errorActivityClass == null && Util.isDebuggableApp(context)){ + // can be null or can be provided beforehand + Class errorActivityClass = Platform.getErrorActivityClass(); + + // if in debug and errorActivityClass is not provided use + // ErrorReportActivity class + if (errorActivityClass == null && JsDebugger.isDebuggableApp(context)) + { errorActivityClass = ErrorReportActivity.class; } - //if not in debug mode should return null and use the errorActivityClass implementation provided - if(errorActivityClass == null) + // if not in debug mode should return null and use the + // errorActivityClass implementation provided + if (errorActivityClass == null) { return null; } - + Intent intent = new Intent(context, errorActivityClass); - + intent.putExtra(EXTRA_NATIVESCRIPT_ERROR_REPORT, EXTRA_ERROR_REPORT_VALUE); intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK); - + return intent; } - + static boolean hasIntent(Intent intent) { int value = intent.getIntExtra(EXTRA_NATIVESCRIPT_ERROR_REPORT, 0); - + return value == EXTRA_ERROR_REPORT_VALUE; } void buildUI() { Context context = activity; - + LinearLayout layout = new LinearLayout(context); layout.setOrientation(LinearLayout.VERTICAL); activity.setContentView(layout); - + TextView txtHeader = new TextView(context); txtHeader.setText("Callstack"); layout.addView(txtHeader); - + Intent intent = activity.getIntent(); String msg = intent.getStringExtra(EXTRA_ERROR_REPORT_MSG); @@ -157,15 +162,15 @@ void buildUI() txtErrorMsg.setText(msg); txtErrorMsg.setHeight(1000); txtErrorMsg.setMovementMethod(new ScrollingMovementMethod()); - - GradientDrawable gd = new GradientDrawable(); - gd.setColor(0xFFFFFFFF); - gd.setCornerRadius(5); - gd.setStroke(1, 0xFF000000); - txtErrorMsg.setBackground(gd); - + + GradientDrawable gd = new GradientDrawable(); + gd.setColor(0xFFFFFFFF); + gd.setCornerRadius(5); + gd.setStroke(1, 0xFF000000); + txtErrorMsg.setBackground(gd); + layout.addView(txtErrorMsg); - + Button btnClose = new Button(context); btnClose.setText("Close"); btnClose.setOnClickListener(new OnClickListener() @@ -179,7 +184,7 @@ public void onClick(View v) layout.addView(btnClose); } - + private static void createErrorFile(final Context context) { try diff --git a/src/src/com/tns/JsDebugger.java b/src/src/com/tns/JsDebugger.java index d0e6d9cee..dfd7d84a0 100644 --- a/src/src/com/tns/JsDebugger.java +++ b/src/src/com/tns/JsDebugger.java @@ -1,6 +1,7 @@ package com.tns; import java.io.BufferedReader; +import java.io.Closeable; import java.io.File; import java.io.FileOutputStream; import java.io.FileReader; @@ -21,10 +22,20 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager.NameNotFoundException; +import android.net.LocalServerSocket; +import android.net.LocalSocket; import android.os.Bundle; +import android.os.Handler; +import android.os.HandlerThread; +import android.util.Log; public class JsDebugger { + public static boolean enableDebuggingOverride; + + private static native void processDebugMessages(); private static native void enable(); @@ -35,122 +46,163 @@ public class JsDebugger private static native void sendCommand(byte[] command, int length); - private static ThreadScheduler threadScheduler; + private ThreadScheduler threadScheduler; - private static final int INVALID_PORT = -1; - - private static final String portEnvInputFile = "envDebug.in"; + private LinkedBlockingQueue dbgMessages = new LinkedBlockingQueue(); - private static final String portEnvOutputFile = "envDebug.out"; - - private static final String DEBUG_BREAK_FILENAME = "debugbreak"; + private Logger logger; + private Context context; + private DebugLocalServerSocketThread debugServerThread; - private static int currentPort = INVALID_PORT; - - private static LinkedBlockingQueue dbgMessages = new LinkedBlockingQueue(); - - private final File debuggerSetupDirectory; - - private Boolean shouldDebugBreakFlag = null; + private final static String STOP_MESSAGE = "STOP_MESSAGE"; - private final Logger logger; + private byte[] LINE_END_BYTES; - public JsDebugger(Logger logger, ThreadScheduler threadScheduler, File debuggerSetupDirectory) + private HandlerThread handlerThread; + + public JsDebugger(Context context, Logger logger, ThreadScheduler threadScheduler) { + this.context = context; this.logger = logger; - JsDebugger.threadScheduler = threadScheduler; - this.debuggerSetupDirectory = debuggerSetupDirectory; + this.threadScheduler = threadScheduler; + + LINE_END_BYTES = new byte[2]; + LINE_END_BYTES[0] = (byte) '\r'; + LINE_END_BYTES[1] = (byte) '\n'; } - private static ServerSocket serverSocket; - private static ServerThread serverThread = null; - private static Thread javaServerThread = null; + private class DebugLocalServerSocketThread extends Thread + { + private volatile boolean running; + private final String name; - private static class ServerThread implements Runnable - { - private volatile boolean running; - private final int port; - private ResponseWorker responseWorker; - private ListenerWorker commThread; + private RequestHandler requestHandler; + private LocalServerSocket serverSocket; + private ResponseHandler responseHandler; + + public DebugLocalServerSocketThread(String name) + { + this.name = name; + this.running = false; + } - public ServerThread(int port) + public void stopResponseHandler() { - this.port = port; - this.running = false; + this.responseHandler.stop(); } + + + public void run() + { + Closeable requestHandlerCloseable = new Closeable() + { + @Override + public void close() throws IOException + { + requestHandler.stop(); + } + }; + + Closeable responseHandlerCloseable = new Closeable() + { + @Override + public void close() throws IOException + { + responseHandler.stop(); + } + }; + + running = true; + try + { + serverSocket = new LocalServerSocket(this.name); + try + { + while (running) + { + try + { + LocalSocket socket = serverSocket.accept(); + logger.write("Debugger new connection on: " + socket.getFileDescriptor().toString()); + + dbgMessages.clear(); + + //out (send messages to node inspector) + this.responseHandler = new ResponseHandler(socket, requestHandlerCloseable); + Thread responseThread = new Thread(this.responseHandler); + responseThread.start(); + + //in (recieve messages from node inspector) + requestHandler = new RequestHandler(socket, responseHandlerCloseable); + Thread requestThread = new Thread(requestHandler); + requestThread.start(); + requestThread.join(); + + this.responseHandler.stop(); + socket.close(); + } + catch (IOException | InterruptedException e) + { + e.printStackTrace(); + } + } + } + finally + { + try + { + serverSocket.close(); + } + catch (IOException e) + { + e.printStackTrace(); + } + } + } + catch (IOException e) + { + e.printStackTrace(); + } + } + } + + private enum State + { + Header, Message + } + + private class RequestHandler implements Runnable + { + private BufferedReader input; + private Scanner scanner; - public void stop() + Runnable dispatchProcessDebugMessages = new Runnable() { - this.running = false; - this.responseWorker.stop(); - try - { - serverSocket.close(); - } - catch (IOException e) - { - e.printStackTrace(); - } - } - - //when someone runs our server we do: - public void run() - { - try - { - //open server port to run on - serverSocket = new ServerSocket(this.port); - running = true; - } - catch (IOException e) - { - running = false; - e.printStackTrace(); - } - - //start listening and responding through that socket - while (running) + @Override + public void run() { - try - { - //wait for someone to connect to port and if he does ... open a socket - Socket socket = serverSocket.accept(); - - //out (send messages to node inspector) - this.responseWorker = new ResponseWorker(socket); - new Thread(this.responseWorker).start(); - - //in (recieve messages from node inspector) - commThread = new ListenerWorker(socket.getInputStream()); - new Thread(commThread).start(); - } - catch (IOException e) - { - e.printStackTrace(); - } + processDebugMessages(); } - } - } + }; - private static class ListenerWorker implements Runnable - { - private enum State + private boolean stop; + private Closeable responseHandlerCloseable; + + public RequestHandler(LocalSocket socket, Closeable responseHandlerCloseable) throws IOException { - Header, Message + this.responseHandlerCloseable = responseHandlerCloseable; + this.input = new BufferedReader(new InputStreamReader(socket.getInputStream())); } - private BufferedReader input; - - public ListenerWorker(InputStream inputStream) + public void stop() { - this.input = new BufferedReader(new InputStreamReader(inputStream)); + this.stop = true; + this.scanner.close(); } - - private volatile boolean running = true; - + public void run() { - Scanner scanner = new Scanner(this.input); + scanner = new Scanner(this.input); scanner.useDelimiter("\r\n"); ArrayList headers = new ArrayList(); @@ -159,18 +211,9 @@ public void run() int messageLength = -1; String leftOver = null; - Runnable dispatchProcessDebugMessages = new Runnable() - { - @Override - public void run() - { - processDebugMessages(); - } - }; - try { - while (running && ((line = (leftOver != null) ? leftOver : scanner.nextLine()) != null)) + while (!stop && ((line = (leftOver != null) ? leftOver : scanner.nextLine()) != null)) { switch (state) { @@ -187,33 +230,27 @@ public void run() String strLen = line.substring(15).trim(); messageLength = Integer.parseInt(strLen); } + if (leftOver != null) + { leftOver = null; + } } break; + case Message: if ((-1 < messageLength) && (messageLength <= line.length())) { String msg = line.substring(0, messageLength); if (messageLength < line.length()) + { leftOver = line.substring(messageLength); + } + state = State.Header; headers.clear(); - try - { - byte[] cmdBytes = msg.getBytes("UTF-16LE"); - int cmdLength = cmdBytes.length; - sendCommand(cmdBytes, cmdLength); - - boolean success = JsDebugger.threadScheduler.post(dispatchProcessDebugMessages); - assert success; - } - catch (UnsupportedEncodingException e) - { - // TODO Auto-generated catch block - e.printStackTrace(); - } + sendMessageToV8(msg); } else { @@ -233,266 +270,181 @@ public void run() catch (NoSuchElementException e) { e.printStackTrace(); + + try + { + responseHandlerCloseable.close(); + } + catch (IOException e1) + { + e1.printStackTrace(); + } } finally { + this.stop = true; + logger.write("sending disconnect to v8 debugger"); + sendMessageToV8("{\"seq\":0,\"type\":\"request\",\"command\":\"disconnect\"}"); scanner.close(); } } - } - private static class ResponseWorker implements Runnable - { - private Socket socket; + private void sendMessageToV8(String message) + { + try + { + //Log.d("TNS.JAVA.JsDebugger", "Sending message to v8:" + message); + + byte[] cmdBytes = message.getBytes("UTF-16LE"); + int cmdLength = cmdBytes.length; + sendCommand(cmdBytes, cmdLength); - private final static String END_MSG = "#end#"; + threadScheduler.post(dispatchProcessDebugMessages); + } + catch (UnsupportedEncodingException e) + { + e.printStackTrace(); + } + } + } + private class ResponseHandler implements Runnable + { private OutputStream output; + private boolean stop; + private Closeable requestHandlerCloseable; - public ResponseWorker(Socket clientSocket) throws IOException + public ResponseHandler(LocalSocket socket, Closeable requestHandlerCloseable) throws IOException { - this.socket = clientSocket; - this.output = this.socket.getOutputStream(); + this.requestHandlerCloseable = requestHandlerCloseable; + this.output = socket.getOutputStream(); } public void stop() { - dbgMessages.add(END_MSG); + this.stop = true; + dbgMessages.add(STOP_MESSAGE); } @Override public void run() { - byte[] LINE_END_BYTES = new byte[2]; - LINE_END_BYTES[0] = (byte) '\r'; - LINE_END_BYTES[1] = (byte) '\n'; - while (true) + while (!stop) { try { - String msg = dbgMessages.take(); - - if (msg.equals(END_MSG)) - break; - - byte[] utf8; - try - { - utf8 = msg.getBytes("UTF8"); - } - catch (UnsupportedEncodingException e1) + String message = dbgMessages.take(); + if (message.equals(STOP_MESSAGE)) { - utf8 = null; - e1.printStackTrace(); - } - - if (utf8 != null) - { - try - { - String s = "Content-Length: " + utf8.length; - byte[] arr = s.getBytes("UTF8"); - output.write(arr, 0, arr.length); - output.write(LINE_END_BYTES, 0, LINE_END_BYTES.length); - output.write(LINE_END_BYTES, 0, LINE_END_BYTES.length); - output.write(utf8, 0, utf8.length); - output.flush(); - } - catch (IOException e) - { - // TODO Auto-generated catch block - e.printStackTrace(); - } + break; } + + //Log.d("TNS.JAVA.JsDebugger", "Sending message to inspector:" + message); + + sendMessageToInspector(message); } catch (InterruptedException e) { e.printStackTrace(); } } - } - } - - int getDebuggerPortFromEnvironment() - { - if (logger.isEnabled()) logger.write("getDebuggerPortFromEnvironment"); - int port = INVALID_PORT; - - File envOutFile = new File(debuggerSetupDirectory, portEnvOutputFile); - OutputStreamWriter w = null; - try - { - w = new OutputStreamWriter(new FileOutputStream(envOutFile, false)); - String currentPID = "PID=" + android.os.Process.myPid() + "\n"; - w.write(currentPID); - } - catch (IOException e1) - { - e1.printStackTrace(); - } - finally - { - if (w != null) - { - try - { - w.close(); - } - catch (IOException e) - { - e.printStackTrace(); - } - } - w = null; - } - - boolean shouldDebugBreakFlag = shouldDebugBreak(); - - if (logger.isEnabled()) logger.write("shouldDebugBreakFlag=" + shouldDebugBreakFlag); - - if (shouldDebugBreakFlag) - { try { - Thread.sleep(3 * 1000); + this.output.close(); } - catch (InterruptedException e1) + catch (IOException e) { - e1.printStackTrace(); + e.printStackTrace(); } } - File envInFile = new File(debuggerSetupDirectory, portEnvInputFile); - - boolean envInFileFlag = envInFile.exists(); - - if (logger.isEnabled()) logger.write("envInFileFlag=" + envInFileFlag); - - if (envInFileFlag) + private void sendMessageToInspector(String msg) { - BufferedReader reader = null; + byte[] utf8; try { - reader = new BufferedReader(new FileReader(envInFile)); - String line = reader.readLine(); - int requestedPort; - try - { - requestedPort = Integer.parseInt(line); - } - catch (NumberFormatException e) - { - requestedPort = INVALID_PORT; - } - - w = new OutputStreamWriter(new FileOutputStream(envOutFile, true)); - int localPort = (requestedPort != INVALID_PORT) ? requestedPort : getAvailablePort(); - String strLocalPort = "PORT=" + localPort + "\n"; - w.write(strLocalPort); - port = currentPort = localPort; - // - enable(); - debugBreak(); - serverThread = new ServerThread(port); - javaServerThread = new Thread(serverThread); - javaServerThread.start(); + utf8 = msg.getBytes("UTF8"); } - catch (IOException e1) + catch (UnsupportedEncodingException e1) { + utf8 = null; e1.printStackTrace(); } - finally + + if (utf8 != null) { - if (reader != null) + try { - try - { - reader.close(); - } - catch (IOException e) - { - e.printStackTrace(); - } + String s = "Content-Length: " + utf8.length; + byte[] arr = s.getBytes("UTF8"); + output.write(arr); + output.write(LINE_END_BYTES); + output.write(LINE_END_BYTES); + output.write(utf8); + output.flush(); + + //Log.d("TNS.JAVA.JsDebugger", "Sent message to inspector:" + msg); } - if (w != null) + catch (IOException e) { + e.printStackTrace(); + try { - w.close(); + requestHandlerCloseable.close(); } - catch (IOException e) + catch (IOException e1) { - e.printStackTrace(); + e1.printStackTrace(); } } - envInFile.delete(); } } - - return port; } - private static int getAvailablePort() - { - int port = 0; - ServerSocket s = null; - try - { - s = new ServerSocket(0); - port = s.getLocalPort(); - } - catch (IOException e) - { - port = INVALID_PORT; - } - finally - { - if (s != null) - { - try - { - s.close(); - } - catch (IOException e) - { - } - } - } - return port; - } - @RuntimeCallable - private static void enqueueMessage(String message) + private void enqueueMessage(String message) { + //logger.write("Debug msg:" + message); + dbgMessages.add(message); } @RuntimeCallable - private static void enableAgent(String packageName, int port, boolean waitForConnection) + private void enableAgent() { + logger.write("Enabling Debugger Agent"); enable(); - if (serverThread == null) - { - serverThread = new ServerThread(port); - } - javaServerThread = new Thread(serverThread); - javaServerThread.start(); } @RuntimeCallable - private static void disableAgent() + private void disableAgent() { + logger.write("Disabling Debugger Agent"); disable(); - if (serverThread != null) + + + String message = "{\"seq\":0,\"type\":\"request\",\"command\":\"disconnect\"}"; + + byte[] cmdBytes; + try { - serverThread.stop(); + cmdBytes = message.getBytes("UTF-16LE"); + int cmdLength = cmdBytes.length; + sendCommand(cmdBytes, cmdLength); } + catch (UnsupportedEncodingException e) + { + e.printStackTrace(); + } + + debugServerThread.stopResponseHandler(); } - static void registerEnableDisableDebuggerReceiver(Context context) + private void registerEnableDisableDebuggerReceiver(Handler handler) { - String debugAction = context.getPackageName() + "-Debug"; + String debugAction = context.getPackageName() + "-debug"; context.registerReceiver(new BroadcastReceiver() { @Override @@ -504,59 +456,84 @@ public void onReceive(Context context, Intent intent) boolean enable = bundle.getBoolean("enable", false); if (enable) { - int port = bundle.getInt("debuggerPort", INVALID_PORT); - if (port == INVALID_PORT) - { - if(currentPort == INVALID_PORT) { - currentPort = getAvailablePort(); - } - port = currentPort; - } - String packageName = bundle.getString("packageName", context.getPackageName()); - boolean waitForDebugger = bundle.getBoolean("waitForDebugger", false); - JsDebugger.enableAgent(packageName, port, waitForDebugger); - currentPort = port; - this.setResultCode(currentPort); + enableAgent(); } else { - // keep socket on the same port when we disable - JsDebugger.disableAgent(); + disableAgent(); } } } - }, new IntentFilter(debugAction)); + }, new IntentFilter(debugAction), null, handler); } + + - static void registerGetDebuggerPortReceiver(Context context) + private boolean getDebugBreakFlagAndClearIt() { - String getDebuggerPortAction = context.getPackageName() + "-GetDbgPort"; - context.registerReceiver(new BroadcastReceiver() + File debugBreakFile = new File("/data/local/tmp", context.getPackageName() + "-debugbreak"); + if (debugBreakFile.exists() && !debugBreakFile.isDirectory() && debugBreakFile.length() == 0) { - @Override - public void onReceive(Context context, Intent intent) + try { - this.setResultCode(currentPort); + java.io.FileWriter fileWriter = new java.io.FileWriter(debugBreakFile); + fileWriter.write("used"); + fileWriter.close(); } - }, new IntentFilter(getDebuggerPortAction)); + catch (IOException e) + { + Log.e("TNS", "Debug break temp file can not be marked as used. Debug sessions may not work correctly. file: " + debugBreakFile.getAbsolutePath()); + e.printStackTrace(); + } + + return true; + } + + return false; } - private boolean shouldDebugBreak() + public void start() { - if (shouldDebugBreakFlag != null) - { - return shouldDebugBreakFlag; - } + handlerThread = new HandlerThread("debugAgentBroadCastReceiverHandler"); + handlerThread.start(); + Handler handler = new Handler(handlerThread.getLooper()); + + registerEnableDisableDebuggerReceiver(handler); + + boolean shouldDebugBrake = getDebugBreakFlagAndClearIt(); - File debugBreakFile = new File(debuggerSetupDirectory, DEBUG_BREAK_FILENAME); + logger.write("Enabling Debugger Agent"); + enable(); - shouldDebugBreakFlag = debugBreakFile.exists(); + if (shouldDebugBrake) + { + debugBreak(); + } + - if (shouldDebugBreakFlag) + debugServerThread = new DebugLocalServerSocketThread(context.getPackageName() + "-debug"); + debugServerThread.start(); + } + + public static boolean isDebuggableApp(Context context) + { + if (enableDebuggingOverride) { - debugBreakFile.delete(); + return true; } - return shouldDebugBreakFlag; + int flags; + try + { + flags = context.getPackageManager().getPackageInfo(context.getPackageName(), 0).applicationInfo.flags; + } + catch (NameNotFoundException e) + { + flags = 0; + e.printStackTrace(); + } + + boolean isDebuggableApp = ((flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0); + return isDebuggableApp; } } diff --git a/src/src/com/tns/LogcatLogger.java b/src/src/com/tns/LogcatLogger.java index e8494dd0e..6fd271b48 100644 --- a/src/src/com/tns/LogcatLogger.java +++ b/src/src/com/tns/LogcatLogger.java @@ -10,6 +10,8 @@ public final class LogcatLogger implements Logger { + private final static String DEFAULT_LOG_TAG = "TNS.Java"; + private boolean enabled; public LogcatLogger(boolean isEnabled, Context context) @@ -44,57 +46,16 @@ public final void write(String tag, String msg) private void initLogging(Context context) { - boolean isDebuggableApp = Util.isDebuggableApp(context); + boolean isDebuggableApp = JsDebugger.isDebuggableApp(context); if (isDebuggableApp) { - String verboseLoggingProp = readSystemProperty("nativescript.verbose.logging"); + String verboseLoggingProp = Util.readSystemProperty("nativescript.verbose.logging"); - if (verboseLoggingProp.equals("true") || verboseLoggingProp.equals("TRUE") || - verboseLoggingProp.equals("yes") || verboseLoggingProp.equals("YES") || - verboseLoggingProp.equals("enabled") || verboseLoggingProp.equals("ENABLED")) + if (Util.isPositive(verboseLoggingProp)) { setEnabled(true); } } } - - private String readSystemProperty(String name) - { - InputStreamReader in = null; - BufferedReader reader = null; - try - { - Process proc = Runtime.getRuntime().exec(new String[] { "/system/bin/getprop", name }); - in = new InputStreamReader(proc.getInputStream()); - reader = new BufferedReader(in); - return reader.readLine(); - } - catch (IOException e) - { - return null; - } - finally - { - silentClose(in); - silentClose(reader); - } - } - - private void silentClose(Closeable closeable) - { - if (closeable == null) - { - return; - } - try - { - closeable.close(); - } - catch (IOException ignored) - { - } - } - - private final static String DEFAULT_LOG_TAG = "TNS.Java"; } diff --git a/src/src/com/tns/NativeScriptApplication.java b/src/src/com/tns/NativeScriptApplication.java index acc884094..17601fd80 100644 --- a/src/src/com/tns/NativeScriptApplication.java +++ b/src/src/com/tns/NativeScriptApplication.java @@ -734,14 +734,30 @@ public void onCreate() { appBuilderCallbackImpl.onCreate(this); } - NativeScriptSyncHelper.sync(logger, this); + if (NativeScriptSyncService.isSyncEnabled(this)) + { + NativeScriptSyncService syncService = new NativeScriptSyncService(logger, this); + + syncService.sync(); + syncService.startServer(); + + //preserve this instance as strong reference + //do not preserve in NativeScriptApplication field inorder to make the code more portable + Platform.getOrCreateJavaObjectID(syncService); + } + else + { + if (logger.isEnabled()) + { + logger.write("NativeScript LiveSync is not enabled."); + } + } + String appName = this.getPackageName(); File rootDir = new File(this.getApplicationInfo().dataDir); File appDir = this.getFilesDir(); - File debuggerSetupDir = Util.isDebuggableApp(this) - ? getExternalFilesDir(null) - : null; + ClassLoader classLoader = this.getClassLoader(); File dexDir = new File(rootDir, "code_cache/secondary-dexes"); String dexThumb = null; @@ -755,7 +771,7 @@ public void onCreate() { e.printStackTrace(); } ThreadScheduler workThreadScheduler = new WorkThreadScheduler(new Handler(Looper.getMainLooper())); - Platform.init(this, workThreadScheduler, logger, appName, null, rootDir, appDir, debuggerSetupDir, classLoader, dexDir, dexThumb); + Platform.init(this, workThreadScheduler, logger, appName, null, rootDir, appDir, classLoader, dexDir, dexThumb); Platform.runScript(new File(appDir, "internal/prepareExtend.js")); Platform.run(); @@ -811,14 +827,9 @@ private void prepareAppBuilderCallbackImpl(Logger logger) Thread.setDefaultUncaughtExceptionHandler(exHandler); - boolean shouldEnableDebugging = (appBuilderCallbackImpl != null) - ? appBuilderCallbackImpl.shouldEnableDebugging(this) - : Util.isDebuggableApp(this); - - if (shouldEnableDebugging) + if (appBuilderCallbackImpl != null) { - JsDebugger.registerEnableDisableDebuggerReceiver(this); - JsDebugger.registerGetDebuggerPortReceiver(this); + JsDebugger.enableDebuggingOverride = appBuilderCallbackImpl.shouldEnableDebugging(this); } } diff --git a/src/src/com/tns/NativeScriptSyncHelper.java b/src/src/com/tns/NativeScriptSyncHelper.java deleted file mode 100644 index 89c8152c2..000000000 --- a/src/src/com/tns/NativeScriptSyncHelper.java +++ /dev/null @@ -1,320 +0,0 @@ -package com.tns; - -import java.io.File; -import java.io.FileFilter; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.FileOutputStream; -import java.io.IOException; - -import android.content.Context; -import android.content.pm.ApplicationInfo; -import android.content.pm.PackageManager.NameNotFoundException; - -public class NativeScriptSyncHelper -{ - private static String SYNC_ROOT_SOURCE_DIR = "/data/local/tmp/"; - private static final String SYNC_SOURCE_DIR = "/sync/"; - private static final String FULL_SYNC_SOURCE_DIR = "/fullsync/"; - private static final String REMOVED_SYNC_SOURCE_DIR = "/removedsync/"; - - private static Logger logger; - - public static void sync(Logger logger, Context context) - { - NativeScriptSyncHelper.logger = logger; - - boolean shouldExecuteSync = getShouldExecuteSync(context); - - if (!shouldExecuteSync) - { - if (logger.isEnabled()) - { - logger.write("Sync is not enabled."); - } - return; - } - - String syncPath = SYNC_ROOT_SOURCE_DIR + context.getPackageName() + SYNC_SOURCE_DIR; - String fullSyncPath = SYNC_ROOT_SOURCE_DIR + context.getPackageName() + FULL_SYNC_SOURCE_DIR; - String removedSyncPath = SYNC_ROOT_SOURCE_DIR + context.getPackageName() + REMOVED_SYNC_SOURCE_DIR; - - if (logger.isEnabled()) - { - logger.write("Sync is enabled:"); - logger.write("Sync path : " + syncPath); - logger.write("Full sync path : " + fullSyncPath); - logger.write("Removed files sync path: " + removedSyncPath); - } - - - File fullSyncDir = new File(fullSyncPath); - if (fullSyncDir.exists()) - { - executeFullSync(context, fullSyncDir); - deleteRecursive(fullSyncDir); - return; - } - - File syncDir = new File(syncPath); - if (syncDir.exists()) - { - executePartialSync(context, syncDir); - deleteRecursive(syncDir); - } - - File removedSyncDir = new File(removedSyncPath); - if (removedSyncDir.exists()) - { - executeRemovedSync(context, removedSyncDir); - deleteRecursive(removedSyncDir); - } - } - - private static void deleteRecursive(File fileOrDirectory) { - - if (fileOrDirectory.isDirectory()){ - for (File child : fileOrDirectory.listFiles()){ - deleteRecursive(child); - } - } - - fileOrDirectory.delete(); - } - - private static boolean getShouldExecuteSync(Context context) - { - int flags; - boolean shouldExecuteSync = false; - try - { - flags = context.getPackageManager().getPackageInfo(context.getPackageName(), 0).applicationInfo.flags; - shouldExecuteSync = ((flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0); - } - catch (NameNotFoundException e) - { - e.printStackTrace(); - return false; - } - - return shouldExecuteSync; - } - - final static FileFilter deletingFilesFilter = new FileFilter() - { - @Override - public boolean accept(File pathname) - { - if (pathname.isDirectory()) - { - return true; - } - - boolean success = pathname.delete(); - if (!success) - { - logger.write("Syncing: file not deleted: " + pathname.getAbsolutePath().toString()); - } - return false; - } - }; - - private static void deleteDir(File directory) - { - File[] subDirectories = directory.listFiles(deletingFilesFilter); - if (subDirectories != null) - { - for (int i = 0; i < subDirectories.length; i++) - { - File subDir = subDirectories[i]; - deleteDir(subDir); - } - } - - boolean success = directory.delete(); - if (!success && directory.exists()) - { - logger.write("Syncing: directory not deleted: " + directory.getAbsolutePath().toString()); - } - } - - private static void moveFiles(File sourceDir, String sourceRootAbsolutePath, String targetRootAbsolutePath) - { - File[] files = sourceDir.listFiles(); - - if (files != null) - { - for (int i = 0; i < files.length; i++) - { - File file = files[i]; - if (file.isFile()) - { - if (logger.isEnabled()) - { - logger.write("Syncing: " + file.getAbsolutePath().toString()); - } - - String targetFilePath = file.getAbsolutePath().replace(sourceRootAbsolutePath, targetRootAbsolutePath); - File targetFileDir = new File(targetFilePath); - - File targetParent = targetFileDir.getParentFile(); - if (targetParent != null) - { - targetParent.mkdirs(); - } - - boolean success = copyFile(file.getAbsolutePath(), targetFilePath); - if (!success) - { - logger.write("Sync failed: " + file.getAbsolutePath().toString()); - } - } - else - { - moveFiles(file, sourceRootAbsolutePath, targetRootAbsolutePath); - } - } - } - } - - // this removes only the app directory from the device to preserve - // any existing files in /files directory on the device - private static void executeFullSync(Context context, final File sourceDir) - { - String appPath = context.getFilesDir().getAbsolutePath() + "/app"; - final File appDir = new File(appPath); - - if (appDir.exists()) - { - deleteDir(appDir); - moveFiles(sourceDir, sourceDir.getAbsolutePath(), appDir.getAbsolutePath()); - } - } - - private static void executePartialSync(Context context, File sourceDir) - { - String appPath = context.getFilesDir().getAbsolutePath() + "/app"; - final File appDir = new File(appPath); - - if (appDir.exists()) - { - moveFiles(sourceDir, sourceDir.getAbsolutePath(), appDir.getAbsolutePath()); - } - } - - private static void deleteRemovedFiles(File sourceDir, String sourceRootAbsolutePath, String targetRootAbsolutePath) - { - File[] files = sourceDir.listFiles(); - - if (files != null) - { - for (int i = 0; i < files.length; i++) - { - File file = files[i]; - if (file.isFile()) - { - if (logger.isEnabled()) - { - logger.write("Syncing removed file: " + file.getAbsolutePath().toString()); - } - - String targetFilePath = file.getAbsolutePath().replace(sourceRootAbsolutePath, targetRootAbsolutePath); - File targetFile = new File(targetFilePath); - targetFile.delete(); - } - else - { - deleteRemovedFiles(file, sourceRootAbsolutePath, targetRootAbsolutePath); - } - } - } - } - - private static void executeRemovedSync(final Context context, final File sourceDir) - { - String appPath = context.getFilesDir().getAbsolutePath() + "/app"; - deleteRemovedFiles(sourceDir, sourceDir.getAbsolutePath(), appPath); - } - - private static boolean copyFile(String sourceFile, String destinationFile) - { - FileInputStream fis = null; - FileOutputStream fos = null; - - try - { - fis = new FileInputStream(sourceFile); - fos = new FileOutputStream(destinationFile, false); - - byte[] buffer = new byte[4096]; - int read = 0; - - while ((read = fis.read(buffer)) != -1) - { - fos.write(buffer, 0, read); - } - } - catch (FileNotFoundException e) - { - logger.write("Error copying file " + sourceFile); - e.printStackTrace(); - return false; - } - catch (IOException e) - { - logger.write("Error copying file " + sourceFile); - e.printStackTrace(); - return false; - } - finally - { - try - { - if (fis != null) - { - fis.close(); - } - if (fos != null) - { - fos.close(); - } - } - catch (IOException e) - { - } - } - - return true; - } - -// private static String getSyncThumb(String syncThumbFilePath) -// { -// try -// { -// File syncThumbFile = new File(syncThumbFilePath); -// if (syncThumbFile.exists()) -// { -// return null; -// } -// -// FileInputStream in = new FileInputStream(syncThumbFile); -// BufferedReader reader = new BufferedReader(new InputStreamReader(in)); -// String syncThumb = reader.readLine(); -// reader.close(); -// in.close(); -// return syncThumb; -// } -// catch (FileNotFoundException e) -// { -// Log.e(Platform.DEFAULT_LOG_TAG, "Error while reading sync command"); -// e.printStackTrace(); -// } -// catch (IOException e) -// { -// Log.e(Platform.DEFAULT_LOG_TAG, "Error while reading sync command"); -// e.printStackTrace(); -// } -// -// return null; -// } -} diff --git a/src/src/com/tns/NativeScriptSyncService.java b/src/src/com/tns/NativeScriptSyncService.java new file mode 100644 index 000000000..054987b41 --- /dev/null +++ b/src/src/com/tns/NativeScriptSyncService.java @@ -0,0 +1,423 @@ +package com.tns; + +import java.io.BufferedReader; +import java.io.Closeable; +import java.io.DataInputStream; +import java.io.File; +import java.io.FileFilter; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.UnsupportedEncodingException; +import java.net.ServerSocket; +import java.net.Socket; +import java.util.ArrayList; +import java.util.NoSuchElementException; +import java.util.Scanner; + +import android.content.Context; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager.NameNotFoundException; +import android.net.LocalServerSocket; +import android.net.LocalSocket; + +public class NativeScriptSyncService +{ + private static String SYNC_ROOT_SOURCE_DIR = "/data/local/tmp/"; + private static final String SYNC_SOURCE_DIR = "/sync/"; + private static final String FULL_SYNC_SOURCE_DIR = "/fullsync/"; + private static final String REMOVED_SYNC_SOURCE_DIR = "/removedsync/"; + + private static Logger logger; + private final Context context; + + private final String syncPath; + private final String fullSyncPath; + private final String removedSyncPath; + private final File fullSyncDir; + private final File syncDir; + private final File removedSyncDir; + + private LocalServerSocketThread localServerThread; + private Thread localServerJavaThread; + + public NativeScriptSyncService(Logger logger, Context context) + { + this.logger = logger; + this.context = context; + + syncPath = SYNC_ROOT_SOURCE_DIR + context.getPackageName() + SYNC_SOURCE_DIR; + fullSyncPath = SYNC_ROOT_SOURCE_DIR + context.getPackageName() + FULL_SYNC_SOURCE_DIR; + removedSyncPath = SYNC_ROOT_SOURCE_DIR + context.getPackageName() + REMOVED_SYNC_SOURCE_DIR; + fullSyncDir = new File(fullSyncPath); + syncDir = new File(syncPath); + removedSyncDir = new File(removedSyncPath); + } + + public void sync() + { + if (logger != null && logger.isEnabled()) + { + logger.write("Sync is enabled:"); + logger.write("Sync path : " + syncPath); + logger.write("Full sync path : " + fullSyncPath); + logger.write("Removed files sync path: " + removedSyncPath); + } + + if (fullSyncDir.exists()) + { + executeFullSync(context, fullSyncDir); + deleteRecursive(fullSyncDir); + return; + } + + if (syncDir.exists()) + { + executePartialSync(context, syncDir); + deleteRecursive(syncDir); + } + + if (removedSyncDir.exists()) + { + executeRemovedSync(context, removedSyncDir); + deleteRecursive(removedSyncDir); + } + } + + private class LocalServerSocketThread implements Runnable + { + private volatile boolean running; + private final String name; + + private ListenerWorker commThread; + private LocalServerSocket serverSocket; + + public LocalServerSocketThread(String name) + { + this.name = name; + this.running = false; + } + + public void stop() + { + this.running = false; + try + { + serverSocket.close(); + } + catch (IOException e) + { + e.printStackTrace(); + } + } + + public void run() + { + running = true; + try + { + serverSocket = new LocalServerSocket(this.name); + while (running) + { + LocalSocket socket = serverSocket.accept(); + commThread = new ListenerWorker(socket.getInputStream(), socket); + new Thread(commThread).start(); + } + } + catch (IOException e) + { + e.printStackTrace(); + } + } + } + + private class ListenerWorker implements Runnable + { + private final DataInputStream input; + private Closeable socket; + + public ListenerWorker(InputStream inputStream, Closeable socket) + { + this.socket = socket; + input = new DataInputStream(inputStream); + } + + public void run() + { + try + { + int length = input.readInt(); + input.readFully(new byte[length]); //ignore the payload + executePartialSync(context, syncDir); + + Platform.callJSMethod(NativeScriptApplication.getInstance(), "onLiveSync", Void.class); + + socket.close(); + } + catch (IOException e) + { + e.printStackTrace(); + } + } + } + + public void startServer() + { + localServerThread = new LocalServerSocketThread(context.getPackageName() + "-livesync"); + localServerJavaThread = new Thread(localServerThread); + localServerJavaThread.start(); + } + + private void deleteRecursive(File fileOrDirectory) + { + + if (fileOrDirectory.isDirectory()) + { + for (File child : fileOrDirectory.listFiles()) + { + deleteRecursive(child); + } + } + + fileOrDirectory.delete(); + } + + public static boolean isSyncEnabled(Context context) + { + int flags; + boolean shouldExecuteSync = false; + try + { + flags = context.getPackageManager().getPackageInfo(context.getPackageName(), 0).applicationInfo.flags; + shouldExecuteSync = ((flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0); + } + catch (NameNotFoundException e) + { + e.printStackTrace(); + return false; + } + + return shouldExecuteSync; + } + + final FileFilter deletingFilesFilter = new FileFilter() + { + @Override + public boolean accept(File pathname) + { + if (pathname.isDirectory()) + { + return true; + } + + boolean success = pathname.delete(); + if (!success) + { + logger.write("Syncing: file not deleted: " + pathname.getAbsolutePath().toString()); + } + return false; + } + }; + + private void deleteDir(File directory) + { + File[] subDirectories = directory.listFiles(deletingFilesFilter); + if (subDirectories != null) + { + for (int i = 0; i < subDirectories.length; i++) + { + File subDir = subDirectories[i]; + deleteDir(subDir); + } + } + + boolean success = directory.delete(); + if (!success && directory.exists()) + { + logger.write("Syncing: directory not deleted: " + directory.getAbsolutePath().toString()); + } + } + + private void moveFiles(File sourceDir, String sourceRootAbsolutePath, String targetRootAbsolutePath) + { + File[] files = sourceDir.listFiles(); + + if (files != null) + { + for (int i = 0; i < files.length; i++) + { + File file = files[i]; + if (file.isFile()) + { + if (logger.isEnabled()) + { + logger.write("Syncing: " + file.getAbsolutePath().toString()); + } + + String targetFilePath = file.getAbsolutePath().replace(sourceRootAbsolutePath, targetRootAbsolutePath); + File targetFileDir = new File(targetFilePath); + + File targetParent = targetFileDir.getParentFile(); + if (targetParent != null) + { + targetParent.mkdirs(); + } + + boolean success = copyFile(file.getAbsolutePath(), targetFilePath); + if (!success) + { + logger.write("Sync failed: " + file.getAbsolutePath().toString()); + } + } + else + { + moveFiles(file, sourceRootAbsolutePath, targetRootAbsolutePath); + } + } + } + } + + // this removes only the app directory from the device to preserve + // any existing files in /files directory on the device + private void executeFullSync(Context context, final File sourceDir) + { + String appPath = context.getFilesDir().getAbsolutePath() + "/app"; + final File appDir = new File(appPath); + + if (appDir.exists()) + { + deleteDir(appDir); + moveFiles(sourceDir, sourceDir.getAbsolutePath(), appDir.getAbsolutePath()); + } + } + + private void executePartialSync(Context context, File sourceDir) + { + String appPath = context.getFilesDir().getAbsolutePath() + "/app"; + final File appDir = new File(appPath); + + if (appDir.exists()) + { + moveFiles(sourceDir, sourceDir.getAbsolutePath(), appDir.getAbsolutePath()); + } + } + + private void deleteRemovedFiles(File sourceDir, String sourceRootAbsolutePath, String targetRootAbsolutePath) + { + File[] files = sourceDir.listFiles(); + + if (files != null) + { + for (int i = 0; i < files.length; i++) + { + File file = files[i]; + if (file.isFile()) + { + if (logger.isEnabled()) + { + logger.write("Syncing removed file: " + file.getAbsolutePath().toString()); + } + + String targetFilePath = file.getAbsolutePath().replace(sourceRootAbsolutePath, targetRootAbsolutePath); + File targetFile = new File(targetFilePath); + targetFile.delete(); + } + else + { + deleteRemovedFiles(file, sourceRootAbsolutePath, targetRootAbsolutePath); + } + } + } + } + + private void executeRemovedSync(final Context context, final File sourceDir) + { + String appPath = context.getFilesDir().getAbsolutePath() + "/app"; + deleteRemovedFiles(sourceDir, sourceDir.getAbsolutePath(), appPath); + } + + private boolean copyFile(String sourceFile, String destinationFile) + { + FileInputStream fis = null; + FileOutputStream fos = null; + + try + { + fis = new FileInputStream(sourceFile); + fos = new FileOutputStream(destinationFile, false); + + byte[] buffer = new byte[4096]; + int read = 0; + + while ((read = fis.read(buffer)) != -1) + { + fos.write(buffer, 0, read); + } + } + catch (FileNotFoundException e) + { + logger.write("Error copying file " + sourceFile); + e.printStackTrace(); + return false; + } + catch (IOException e) + { + logger.write("Error copying file " + sourceFile); + e.printStackTrace(); + return false; + } + finally + { + try + { + if (fis != null) + { + fis.close(); + } + if (fos != null) + { + fos.close(); + } + } + catch (IOException e) + { + } + } + + return true; + } + +// private static String getSyncThumb(String syncThumbFilePath) +// { +// try +// { +// File syncThumbFile = new File(syncThumbFilePath); +// if (syncThumbFile.exists()) +// { +// return null; +// } +// +// FileInputStream in = new FileInputStream(syncThumbFile); +// BufferedReader reader = new BufferedReader(new InputStreamReader(in)); +// String syncThumb = reader.readLine(); +// reader.close(); +// in.close(); +// return syncThumb; +// } +// catch (FileNotFoundException e) +// { +// Log.e(Platform.DEFAULT_LOG_TAG, "Error while reading sync command"); +// e.printStackTrace(); +// } +// catch (IOException e) +// { +// Log.e(Platform.DEFAULT_LOG_TAG, "Error while reading sync command"); +// e.printStackTrace(); +// } +// +// return null; +// } +} diff --git a/src/src/com/tns/Platform.java b/src/src/com/tns/Platform.java index dc00445ac..85c088ff7 100644 --- a/src/src/com/tns/Platform.java +++ b/src/src/com/tns/Platform.java @@ -18,6 +18,7 @@ import org.json.JSONObject; +import android.app.Application; import android.util.SparseArray; import com.tns.bindings.ProxyGenerator; @@ -25,7 +26,7 @@ public class Platform { - private static native void initNativeScript(String filesPath, int appJavaObjectId, boolean verboseLoggingEnabled, String packageName, String jsOptions); + private static native void initNativeScript(String filesPath, int appJavaObjectId, boolean verboseLoggingEnabled, String packageName, String jsOptions, JsDebugger jsDebugger); private static native void runNativeScript(String appModuleName); @@ -97,12 +98,7 @@ public static void setErrorActivityClass(Class clazz) errorActivityClass = clazz; } - public static int init(ThreadScheduler threadScheduler, Logger logger, String appName, File runtimeLibPath, File rootDir, File appDir, File debuggerSetupDir, ClassLoader classLoader, File dexDir, String dexThumb) throws RuntimeException - { - return init(null, threadScheduler, logger, appName, runtimeLibPath, rootDir, appDir, debuggerSetupDir, classLoader, dexDir, dexThumb); - } - - public static int init(Object application, ThreadScheduler threadScheduler, Logger logger, String appName, File runtimeLibPath, File rootDir, File appDir, File debuggerSetupDir, ClassLoader classLoader, File dexDir, String dexThumb) throws RuntimeException + public static int init(Application application, ThreadScheduler threadScheduler, Logger logger, String appName, File runtimeLibPath, File rootDir, File appDir, ClassLoader classLoader, File dexDir, String dexThumb) throws RuntimeException { if (initialized) { @@ -115,13 +111,16 @@ public static int init(Object application, ThreadScheduler threadScheduler, Logg Platform.dexFactory = new DexFactory(logger, classLoader, dexDir, dexThumb); - int appJavaObjectId = -1; - if (application != null) + if (logger.isEnabled()) + { + logger.write("Initializing NativeScript JAVA"); + } + + int appJavaObjectId = generateNewObjectId(); + makeInstanceStrong(application, appJavaObjectId); + if (logger.isEnabled()) { - if (logger.isEnabled()) logger.write("Initializing NativeScript JAVA"); - appJavaObjectId = generateNewObjectId(); - makeInstanceStrong(application, appJavaObjectId); - if (logger.isEnabled()) logger.write("Initialized app instance id:" + appJavaObjectId); + logger.write("Initialized app instance id:" + appJavaObjectId); } try @@ -132,18 +131,20 @@ public static int init(Object application, ThreadScheduler threadScheduler, Logg { throw new RuntimeException("Fail to initialize Require class", ex); } + + if (JsDebugger.isDebuggableApp(application)) + { + jsDebugger = new JsDebugger(application, logger, threadScheduler); + } + String jsOptions = readJsOptions(appDir); - Platform.initNativeScript(Require.getApplicationFilesPath(), appJavaObjectId, logger.isEnabled(), appName, jsOptions); + Platform.initNativeScript(Require.getApplicationFilesPath(), appJavaObjectId, logger.isEnabled(), appName, jsOptions, jsDebugger); - if (debuggerSetupDir != null) + if (jsDebugger != null) { - jsDebugger = new JsDebugger(logger, threadScheduler, debuggerSetupDir); - //also runs javaServerThread with resolved port - int debuggerPort = jsDebugger.getDebuggerPortFromEnvironment(); - if (logger.isEnabled()) logger.write("port=" + debuggerPort); + jsDebugger.start(); } - // if (logger.isEnabled()) { Date d = new Date(); @@ -152,7 +153,7 @@ public static int init(Object application, ThreadScheduler threadScheduler, Logg Date lastModDate = new Date(f.lastModified()); logger.write("init time=" + (d.getTime() - lastModDate.getTime())); } - // + initialized = true; return appJavaObjectId; @@ -517,7 +518,10 @@ private static void makeInstanceStrong(Object instance, int objectId) } } - if (logger.isEnabled()) logger.write("MakeInstanceStrong (" + key + ", " + instance.getClass().toString() + ")"); + if (logger != null && logger.isEnabled()) + { + logger.write("MakeInstanceStrong (" + key + ", " + instance.getClass().toString() + ")"); + } } private static void makeInstanceWeak(int javaObjectID, boolean keepAsWeak) diff --git a/src/src/com/tns/Util.java b/src/src/com/tns/Util.java index 26d9d629e..894793b83 100644 --- a/src/src/com/tns/Util.java +++ b/src/src/com/tns/Util.java @@ -1,5 +1,10 @@ package com.tns; +import java.io.BufferedReader; +import java.io.Closeable; +import java.io.IOException; +import java.io.InputStreamReader; + import com.tns.internal.Plugin; import android.content.Context; @@ -23,23 +28,6 @@ public static String getDexThumb(Context context) throws NameNotFoundException return String.valueOf(updateTime) + "-" + String.valueOf(code); } - public static boolean isDebuggableApp(Context context) - { - int flags; - try - { - flags = context.getPackageManager().getPackageInfo(context.getPackageName(), 0).applicationInfo.flags; - } - catch (NameNotFoundException e) - { - flags = 0; - e.printStackTrace(); - } - - boolean isDebuggableApp = ((flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0); - return isDebuggableApp; - } - static boolean runPlugin(Logger logger, Context context) { boolean success = false; @@ -70,4 +58,48 @@ static boolean runPlugin(Logger logger, Context context) } return success; } + + public static String readSystemProperty(String name) + { + InputStreamReader in = null; + BufferedReader reader = null; + try + { + Process proc = Runtime.getRuntime().exec(new String[] { "/system/bin/getprop", name }); + in = new InputStreamReader(proc.getInputStream()); + reader = new BufferedReader(in); + return reader.readLine(); + } + catch (IOException e) + { + return null; + } + finally + { + silentClose(in); + silentClose(reader); + } + } + + private static void silentClose(Closeable closeable) + { + if (closeable == null) + { + return; + } + try + { + closeable.close(); + } + catch (IOException ignored) + { + } + } + + public static Boolean isPositive(String value) + { + return (value.equals("true") || value.equals("TRUE") || + value.equals("yes") || value.equals("YES") || + value.equals("enabled") || value.equals("ENABLED")); + } } diff --git a/test-app/assets/app/mainpage.js b/test-app/assets/app/mainpage.js index 70fa9e2b9..4f365cfb1 100644 --- a/test-app/assets/app/mainpage.js +++ b/test-app/assets/app/mainpage.js @@ -1,5 +1,6 @@ __disableVerboseLogging(); __log("starting tests"); + require("./tests/testWeakRef"); require("./tests/tests"); require("./tests/testMethodResolution"); @@ -64,5 +65,11 @@ app.init({ onCreate: function() { __log("Application on create called"); + }, + + onLiveSync: function() { + __enableVerboseLogging(); + __log("LiveSync called"); + __disableVerboseLogging(); } }); \ No newline at end of file