diff --git a/gradlew.bat b/gradlew.bat index 8a0b282aa..aec99730b 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -1,90 +1,90 @@ -@if "%DEBUG%" == "" @echo off -@rem ########################################################################## -@rem -@rem Gradle startup script for Windows -@rem -@rem ########################################################################## - -@rem Set local scope for the variables with windows NT shell -if "%OS%"=="Windows_NT" setlocal - -@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS= - -set DIRNAME=%~dp0 -if "%DIRNAME%" == "" set DIRNAME=. -set APP_BASE_NAME=%~n0 -set APP_HOME=%DIRNAME% - -@rem Find java.exe -if defined JAVA_HOME goto findJavaFromJavaHome - -set JAVA_EXE=java.exe -%JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto init - -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:findJavaFromJavaHome -set JAVA_HOME=%JAVA_HOME:"=% -set JAVA_EXE=%JAVA_HOME%/bin/java.exe - -if exist "%JAVA_EXE%" goto init - -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:init -@rem Get command-line arguments, handling Windowz variants - -if not "%OS%" == "Windows_NT" goto win9xME_args -if "%@eval[2+2]" == "4" goto 4NT_args - -:win9xME_args -@rem Slurp the command line arguments. -set CMD_LINE_ARGS= -set _SKIP=2 - -:win9xME_args_slurp -if "x%~1" == "x" goto execute - -set CMD_LINE_ARGS=%* -goto execute - -:4NT_args -@rem Get arguments from the 4NT Shell from JP Software -set CMD_LINE_ARGS=%$ - -:execute -@rem Setup the command line - -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar - -@rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% - -:end -@rem End local scope for the variables with windows NT shell -if "%ERRORLEVEL%"=="0" goto mainEnd - -:fail -rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of -rem the _cmd.exe /c_ return code! -if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 -exit /b 1 - -:mainEnd -if "%OS%"=="Windows_NT" endlocal - -:omega +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windowz variants + +if not "%OS%" == "Windows_NT" goto win9xME_args +if "%@eval[2+2]" == "4" goto 4NT_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* +goto execute + +:4NT_args +@rem Get arguments from the 4NT Shell from JP Software +set CMD_LINE_ARGS=%$ + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/widgets/build.gradle b/widgets/build.gradle index 94916f825..aba386bd2 100644 --- a/widgets/build.gradle +++ b/widgets/build.gradle @@ -52,9 +52,11 @@ android { } dependencies { - compile fileTree(dir: 'libs', include: ['*.jar']) - testCompile 'junit:junit:4.12' - compile 'com.android.support:support-v4:+' + compile fileTree(include: ['*.jar'], dir: 'libs') + testCompile 'junit:junit:4.12' + compile 'com.android.support:support-v4:+' + compile files('libs/android-runtime.jar') + compile files('libs/android-binding-generator.jar') } task cleanDistDir (type: Delete) { diff --git a/widgets/libs/android-binding-generator.jar b/widgets/libs/android-binding-generator.jar new file mode 100644 index 000000000..d2e2ed159 Binary files /dev/null and b/widgets/libs/android-binding-generator.jar differ diff --git a/widgets/libs/android-runtime.jar b/widgets/libs/android-runtime.jar new file mode 100644 index 000000000..fd767acc5 Binary files /dev/null and b/widgets/libs/android-runtime.jar differ diff --git a/widgets/src/main/java/com/tns/Async.java b/widgets/src/main/java/com/tns/Async.java new file mode 100644 index 000000000..64159a503 --- /dev/null +++ b/widgets/src/main/java/com/tns/Async.java @@ -0,0 +1,414 @@ +package com.tns; + +import java.io.BufferedInputStream; +import java.io.ByteArrayOutputStream; +import java.io.Closeable; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.net.CookieHandler; +import java.net.HttpURLConnection; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Stack; + +import android.app.Application; +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.os.AsyncTask; +import android.util.DisplayMetrics; + +import java.net.CookieManager; + +public class Async +{ + public interface CompleteCallback + { + void onComplete(Object result, Object context); + } + + public static void DownloadImage(String url, CompleteCallback callback, Object context) + { + new ImageDownloadTask(callback, context).execute(url); + } + + public static class Http + { + private static final String DeleteMethod = "DELETE"; + private static final String GetMethod = "GET"; + private static CookieManager cookieManager; + + public static class KeyValuePair + { + public String key; + public String value; + + public KeyValuePair(String key, String value) + { + this.key = key; + this.value = value; + } + } + + public static class RequestOptions + { + public String url; + public String method; + public ArrayList headers; + public String content; + public int timeout = -1; + public int screenWidth = -1; + public int screenHeight = -1; + + public void addHeaders(HttpURLConnection connection) + { + if (this.headers == null) + { + return; + } + + for (KeyValuePair pair : this.headers) + { + connection.addRequestProperty(pair.key.toString(), pair.value.toString()); + } + } + + public void writeContent(HttpURLConnection connection, Stack openedStreams) throws IOException + { + if (this.content == null || this.content.getClass() != String.class) + { + return; + } + + OutputStream outStream = connection.getOutputStream(); + openedStreams.push(outStream); + + OutputStreamWriter writer = new java.io.OutputStreamWriter(outStream); + openedStreams.push(writer); + + writer.write((String) this.content); + } + } + + public static class RequestResult + { + public static final class ByteArrayOutputStream2 extends java.io.ByteArrayOutputStream + { + public ByteArrayOutputStream2() + { + super(); + } + + public ByteArrayOutputStream2(int size) + { + super(size); + } + + /** Returns the internal buffer of this ByteArrayOutputStream, without copying. */ + public synchronized byte[] buf() + { + return this.buf; + } + } + + public ByteArrayOutputStream raw; + public ArrayList headers = new ArrayList(); + public int statusCode; + public String responseAsString; + public Bitmap responseAsImage; + public Exception error; + + public void getHeaders(HttpURLConnection connection) + { + Map> headers = connection.getHeaderFields(); + if (headers == null) + { + // no headers, this may happen if there is no internet connection currently available + return; + } + + int size = headers.size(); + if (size == 0) + { + return; + } + + for (int i = 0; i < size - 1; i++) + { + String key = connection.getHeaderFieldKey(i); + String value = connection.getHeaderField(key); + this.headers.add(new KeyValuePair(key, value)); + } + } + + public void readResponseStream(HttpURLConnection connection, Stack openedStreams, RequestOptions options) throws IOException + { + this.statusCode = connection.getResponseCode(); + + int contentLength = connection.getContentLength(); + + InputStream inStream; + if (this.statusCode >= 400) + { + inStream = connection.getErrorStream(); + } + else + { + inStream = connection.getInputStream(); + } + + if (inStream == null) + { + // inStream is null when receiving status code 401 or 407 + // see this thread for more information http://stackoverflow.com/a/24986433 + return; + } + + openedStreams.push(inStream); + + BufferedInputStream buffer = new java.io.BufferedInputStream(inStream, 4096); + openedStreams.push(buffer); + + ByteArrayOutputStream2 responseStream = contentLength != -1 ? new ByteArrayOutputStream2(contentLength) : new ByteArrayOutputStream2(); + openedStreams.push(responseStream); + + byte[] buff = new byte[4096]; + int read = -1; + while ((read = buffer.read(buff, 0, buff.length)) != -1) + { + responseStream.write(buff, 0, read); + } + + this.raw = responseStream; + buff = null; + + // make the byte array conversion here, not in the JavaScript + // world for better performance + // since we do not have some explicit way to determine whether + // the content-type is image + try + { + // TODO: Generally this approach will not work for very + // large files + BitmapFactory.Options bitmapOptions = new BitmapFactory.Options(); + bitmapOptions.inJustDecodeBounds = true; + + // check the size of the bitmap first + BitmapFactory.decodeByteArray(responseStream.buf(), 0, responseStream.size(), bitmapOptions); + if (bitmapOptions.outWidth > 0 && bitmapOptions.outHeight > 0) + { + int scale = 1; + final int height = bitmapOptions.outHeight; + final int width = bitmapOptions.outWidth; + + if (bitmapOptions.outWidth > options.screenWidth || bitmapOptions.outHeight > options.screenHeight) + { + final int halfHeight = height / 2; + final int halfWidth = width / 2; + + // scale down the image since it is larger than the + // screen resolution + while ((halfWidth / scale) > options.screenWidth && (halfHeight / scale) > options.screenHeight) + { + scale *= 2; + } + } + + bitmapOptions.inJustDecodeBounds = false; + bitmapOptions.inSampleSize = scale; + this.responseAsImage = BitmapFactory.decodeByteArray(responseStream.buf(), 0, responseStream.size(), bitmapOptions); + } + } + catch (Exception e) + { + // bitmap decoding failed, the stream is not an image + e.printStackTrace(); + } + + if (this.responseAsImage == null) + { + // convert to string + this.responseAsString = responseStream.toString(); + } + } + } + + public static void MakeRequest(RequestOptions options, CompleteCallback callback, Object context) + { + if (options.screenWidth < 0 || options.screenHeight < 0) + { + DisplayMetrics metrics = appContext.getResources().getDisplayMetrics(); + options.screenWidth = metrics.widthPixels; + options.screenHeight = metrics.heightPixels; + } + + if (cookieManager == null) + { + cookieManager = new CookieManager(); + CookieHandler.setDefault(cookieManager); + } + + new HttpRequestTask(callback, context).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, options); + } + + private static Application appContext; + + public static void setApplicationContext(Application app) + { + appContext = app; + } + + static class HttpRequestTask extends AsyncTask + { + private CompleteCallback callback; + private Object context; + + public HttpRequestTask(CompleteCallback callback, Object context) + { + this.callback = callback; + this.context = context; + } + + @Override + protected RequestResult doInBackground(RequestOptions... params) + { + RequestResult result = new RequestResult(); + Stack openedStreams = new Stack(); + + try + { + RequestOptions options = params[0]; + URL url = new URL(options.url); + HttpURLConnection connection = (HttpURLConnection) url.openConnection(); + + // set the request method + String requestMethod = options.method != null ? options.method.toUpperCase() : GetMethod; + connection.setRequestMethod(requestMethod); + + // add the headers + options.addHeaders(connection); + + // apply timeout + if (options.timeout > 0) + { + connection.setConnectTimeout(options.timeout); + } + + // Do not attempt to write the content (body) for DELETE method, Java will throw directly + if (requestMethod != DeleteMethod) + { + options.writeContent(connection, openedStreams); + } + + // close the opened streams (saves copy-paste implementation + // in each method that throws IOException) + this.closeOpenedStreams(openedStreams); + + connection.connect(); + + // build the result + result.getHeaders(connection); + result.readResponseStream(connection, openedStreams, options); + + // close the opened streams (saves copy-paste implementation + // in each method that throws IOException) + this.closeOpenedStreams(openedStreams); + + connection.disconnect(); + + return result; + } + catch (Exception e) // TODO: Catch all exceptions? + { + e.printStackTrace(); + result.error = e; + + return result; + } + finally + { + try + { + this.closeOpenedStreams(openedStreams); + } + catch (IOException e) + { + e.printStackTrace(); + // TODO: Java rules - what to do here??? + } + } + } + + protected void onPostExecute(final RequestResult result) + { + this.callback.onComplete(result, this.context); + } + + private void closeOpenedStreams(Stack streams) throws IOException + { + while (streams.size() > 0) + { + Closeable stream = streams.pop(); + stream.close(); + } + } + } + } + + static class ImageDownloadTask extends AsyncTask + { + private CompleteCallback callback; + private Object context; + + public ImageDownloadTask(CompleteCallback callback, Object context) + { + this.callback = callback; + this.context = context; + } + + protected Bitmap doInBackground(String... params) + { + InputStream stream = null; + try + { + stream = new java.net.URL(params[0]).openStream(); + Bitmap bmp = BitmapFactory.decodeStream(stream); + return bmp; + } + catch (MalformedURLException e) + { + e.printStackTrace(); + return null; + } + catch (IOException e) + { + e.printStackTrace(); + return null; + } + finally + { + if (stream != null) + { + try + { + stream.close(); + } + catch (IOException e) + { + e.printStackTrace(); + } + } + } + } + + protected void onPostExecute(final Bitmap result) + { + this.callback.onComplete(result, this.context); + } + } +} diff --git a/widgets/src/main/java/com/tns/DefaultExtractPolicy.java b/widgets/src/main/java/com/tns/DefaultExtractPolicy.java new file mode 100644 index 000000000..908e6ea73 --- /dev/null +++ b/widgets/src/main/java/com/tns/DefaultExtractPolicy.java @@ -0,0 +1,137 @@ +package com.tns; + +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.OutputStreamWriter; + +import android.content.Context; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; + +import com.tns.Logger; +import com.tns.ExtractPolicy; +import com.tns.FileExtractor; + +public class DefaultExtractPolicy implements ExtractPolicy +{ + private final Logger logger; + + private final static String ASSETS_THUMB_FILENAME = "assetsThumb"; + + public DefaultExtractPolicy(Logger logger) + { + this.logger = logger; + } + + public boolean shouldExtract(android.content.Context context) + { + String assetsThumb = generateAssetsThumb(context); + if (assetsThumb != null) + { + String assetsThumbFilePath = context.getFilesDir().getPath() + File.separatorChar + ASSETS_THUMB_FILENAME; + String oldAssetsThumb = getCachedAssetsThumb(assetsThumbFilePath); + if (oldAssetsThumb == null || !assetsThumb.equals(oldAssetsThumb)) + { + saveNewAssetsThumb(assetsThumb, assetsThumbFilePath); + return true; + } + } + + return false; + } + + public boolean forceOverwrite() + { + return true; + } + + public FileExtractor extractor() + { + return null; + } + + private String generateAssetsThumb(Context context) + { + try + { + PackageInfo packageInfo = context.getPackageManager().getPackageInfo(context.getPackageName(), 0); + int code = packageInfo.versionCode; + long updateTime = packageInfo.lastUpdateTime; + return String.valueOf(updateTime) + "-" + String.valueOf(code); + } + catch (PackageManager.NameNotFoundException e) + { + logger.write("Error while getting current assets thumb"); + e.printStackTrace(); + } + + return null; + } + + private String getCachedAssetsThumb(String assetsThumbFilePath) + { + try + { + File cachedThumbFile = new File(assetsThumbFilePath); + if (cachedThumbFile.exists()) + { + FileInputStream in = new FileInputStream(cachedThumbFile); + BufferedReader reader = new BufferedReader(new InputStreamReader(in)); + String cachedThumb = reader.readLine(); + reader.close(); + in.close(); + return cachedThumb; + } + } + catch (FileNotFoundException e) + { + logger.write("Error while getting current assets thumb"); + e.printStackTrace(); + } + catch (IOException e) + { + logger.write("Error while getting current asstes thumb"); + e.printStackTrace(); + } + + return null; + } + + private void saveNewAssetsThumb(String newThumb, String assetsThumbFile) + { + File cachedThumbFile = new File(assetsThumbFile); + try + { + FileOutputStream out = new FileOutputStream(cachedThumbFile, false); + BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(out)); + try + { + writer.write(newThumb); + writer.newLine(); + writer.flush(); + } + finally + { + writer.close(); + out.close(); + } + } + catch (FileNotFoundException e) + { + logger.write("Error while writting current assets thumb"); + e.printStackTrace(); + } + catch (IOException e) + { + logger.write("Error while writting current assets thumb"); + e.printStackTrace(); + } + } + +} diff --git a/widgets/src/main/java/com/tns/ErrorReport.java b/widgets/src/main/java/com/tns/ErrorReport.java new file mode 100644 index 000000000..13446678f --- /dev/null +++ b/widgets/src/main/java/com/tns/ErrorReport.java @@ -0,0 +1,197 @@ +package com.tns; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.IOException; +import java.io.PrintStream; +import java.io.UnsupportedEncodingException; + +import android.app.Activity; +import android.app.PendingIntent; +import android.app.PendingIntent.CanceledException; +import android.content.ClipData; +import android.content.ClipboardManager; +import android.content.Context; +import android.content.Intent; +import android.graphics.drawable.GradientDrawable; +import android.text.method.ScrollingMovementMethod; +import android.util.Log; +import android.view.View; +import android.view.View.OnClickListener; +import android.widget.Button; +import android.widget.LinearLayout; +import android.widget.TextView; + +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) + { + return false; // (if in release mode) don't do anything + } + + intent.putExtra(EXTRA_ERROR_REPORT_MSG, errorMessage); + + createErrorFile(context); + + try + { + startPendingErrorActivity(context, intent); + } + catch (CanceledException e) + { + Log.d("ErrorReport", "Couldn't send pending intent! Exception: " + e.getMessage()); + } + + killProcess(context); + + return true; + } + + static void killProcess(Context context) + { + // finish current activity and all below it first + if (context instanceof Activity) + { + ((Activity) context).finishAffinity(); + } + + // 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; + PrintStream ps = null; + + try + { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + ps = new PrintStream(baos); + ex.printStackTrace(ps); + + try + { + content = baos.toString("US-ASCII"); + } + catch (UnsupportedEncodingException e) + { + content = e.getMessage(); + } + } + finally + { + 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 && JsDebugger.isDebuggableApp(context)) + { + errorActivityClass = ErrorReportActivity.class; + } + + // 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; + Intent intent = activity.getIntent(); + final String msg = intent.getStringExtra(EXTRA_ERROR_REPORT_MSG); + + // container + LinearLayout layout = new LinearLayout(context); + layout.setOrientation(LinearLayout.VERTICAL); + activity.setContentView(layout); + + // header + TextView txtHeader = new TextView(context); + txtHeader.setText("Unhandled Exception"); + + // error + stacktrace + TextView txtErrorMsg = new TextView(context); + txtErrorMsg.setText(msg); + txtErrorMsg.setHeight(1000); + txtErrorMsg.setMovementMethod(new ScrollingMovementMethod()); + + // copy button + Button copyToClipboard = new Button(context); + copyToClipboard.setText("Copy to clipboard"); + copyToClipboard.setOnClickListener(new OnClickListener() + { + @Override + public void onClick(View v) + { + + ClipboardManager clipboard = (ClipboardManager) activity.getSystemService(Context.CLIPBOARD_SERVICE); + ClipData clip = ClipData.newPlainText("nsError", msg); + clipboard.setPrimaryClip(clip); + } + }); + + layout.addView(txtHeader); + layout.addView(txtErrorMsg); + layout.addView(copyToClipboard); + } + + private static void createErrorFile(final Context context) + { + try + { + File errFile = new File(context.getFilesDir(), ERROR_FILE_NAME); + errFile.createNewFile(); + } + catch (IOException e) + { + Log.d("ErrorReport", e.getMessage()); + } + } +} diff --git a/widgets/src/main/java/com/tns/ErrorReportActivity.java b/widgets/src/main/java/com/tns/ErrorReportActivity.java new file mode 100644 index 000000000..8bb412cad --- /dev/null +++ b/widgets/src/main/java/com/tns/ErrorReportActivity.java @@ -0,0 +1,21 @@ +package com.tns; + +import android.app.Activity; +import android.os.Bundle; + +public class ErrorReportActivity extends Activity +{ + public void onCreate(Bundle savedInstanceState) + { + super.onCreate(savedInstanceState); + new ErrorReport(this).buildUI(); + } + + @Override + protected void onPause() + { + // the moment the error activity is not in the foreground we want to kill the process + super.onPause(); + ErrorReport.killProcess(this); + } +} diff --git a/widgets/src/main/java/com/tns/FragmentClass.java b/widgets/src/main/java/com/tns/FragmentClass.java new file mode 100644 index 000000000..a1009afe7 --- /dev/null +++ b/widgets/src/main/java/com/tns/FragmentClass.java @@ -0,0 +1,71 @@ +package com.tns; + +import android.animation.Animator; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +@JavaScriptImplementation(javaScriptFile = "app/tns_modules/ui/frme/frame.js") +public class FragmentClass extends android.app.Fragment implements com.tns.NativeScriptHashCodeProvider { + + public FragmentClass() + { + com.tns.Platform.initInstance(this); + } + + public void onHiddenChanged(boolean hidden) { + java.lang.Object[] params = new Object[1]; + params[0] = hidden; + com.tns.Platform.callJSMethod(this, "onHiddenChanged", void.class, params); + } + + public Animator onCreateAnimator(int transit, boolean enter, int nextAnim) { + java.lang.Object[] params = new Object[3]; + params[0] = transit; + params[1] = enter; + params[2] = nextAnim; + return (Animator)com.tns.Platform.callJSMethod(this, "onCreateAnimator", Animator.class, params); + + } + + public void onCreate(Bundle savedInstanceState) { + java.lang.Object[] params = new Object[1]; + params[0] = savedInstanceState; + com.tns.Platform.callJSMethod(this, "onCreate", void.class, params); + } + + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + java.lang.Object[] params = new Object[3]; + params[0] = inflater; + params[1] = container; + params[2] = savedInstanceState; + return (View)com.tns.Platform.callJSMethod(this, "onCreateView", View.class, params); + + } + + public void onSaveInstanceState(Bundle outState) { + java.lang.Object[] params = new Object[1]; + params[0] = outState; + com.tns.Platform.callJSMethod(this, "onSaveInstanceState", void.class, params); + } + + public void onDestroyView() { + java.lang.Object[] params = null; + com.tns.Platform.callJSMethod(this, "onDestroyView", void.class, params); + + } + + public void onDestroy() { + java.lang.Object[] params = null; + com.tns.Platform.callJSMethod(this, "onDestroy", void.class, params); + + } + public boolean equals__super(java.lang.Object other) { + return super.equals(other); + } + public int hashCode__super() { + return super.hashCode(); + } + +} diff --git a/widgets/src/main/java/com/tns/LogcatLogger.java b/widgets/src/main/java/com/tns/LogcatLogger.java new file mode 100644 index 000000000..531ff3852 --- /dev/null +++ b/widgets/src/main/java/com/tns/LogcatLogger.java @@ -0,0 +1,56 @@ +package com.tns; + +import android.content.Context; +import android.util.Log; + +public final class LogcatLogger implements Logger +{ + private final static String DEFAULT_LOG_TAG = "TNS.Java"; + + private boolean enabled; + + public LogcatLogger(boolean isEnabled, Context context) + { + this.enabled = isEnabled; + + if (!isEnabled) + { + this.initLogging(context); + } + } + + public final boolean isEnabled() + { + return enabled; + } + + public final void setEnabled(boolean isEnabled) + { + enabled = isEnabled; + } + + public final void write(String msg) + { + Log.d(DEFAULT_LOG_TAG, msg); + } + + public final void write(String tag, String msg) + { + Log.d(tag, msg); + } + + private void initLogging(Context context) + { + boolean isDebuggableApp = JsDebugger.isDebuggableApp(context); + + if (isDebuggableApp) + { + String verboseLoggingProp = Util.readSystemProperty("nativescript.verbose.logging"); + + if (Util.isPositive(verboseLoggingProp)) + { + setEnabled(true); + } + } + } +} diff --git a/widgets/src/main/java/com/tns/NativeScriptActivity.java b/widgets/src/main/java/com/tns/NativeScriptActivity.java new file mode 100644 index 000000000..270171399 --- /dev/null +++ b/widgets/src/main/java/com/tns/NativeScriptActivity.java @@ -0,0 +1,59 @@ +package com.tns; + +@JavaScriptImplementation(javaScriptFile = "app/tns_modules/ui/frme/frame.js") +public class NativeScriptActivity extends android.app.Activity implements com.tns.NativeScriptHashCodeProvider { + + public NativeScriptActivity() + { + com.tns.Platform.initInstance(this); + } + + protected void onCreate(android.os.Bundle savedInstanceState) { + java.lang.Object[] params = new Object[1]; + params[0] = savedInstanceState; + com.tns.Platform.callJSMethod(this, "onCreate", void.class, params); + } + + protected void onSaveInstanceState(android.os.Bundle outState) { + java.lang.Object[] params = new Object[1]; + params[0] = outState; + com.tns.Platform.callJSMethod(this, "onSaveInstanceState", void.class, params); + } + + protected void onStart() { + java.lang.Object[] params = null; + com.tns.Platform.callJSMethod(this, "onStart", void.class, params); + } + + protected void onStop() { + java.lang.Object[] params = null; + com.tns.Platform.callJSMethod(this, "onStop", void.class, params); + } + + protected void onDestroy() { + java.lang.Object[] params = null; + com.tns.Platform.callJSMethod(this, "onDestroy", void.class, params); + + } + + public void onBackPressed() { + java.lang.Object[] params = null; + com.tns.Platform.callJSMethod(this, "onBackPressed", void.class, params); + } + + protected void onActivityResult(int requestCode, int resultCode, android.content.Intent data) { + java.lang.Object[] params = new Object[3]; + params[0] = requestCode; + params[1] = resultCode; + params[2] = data; + com.tns.Platform.callJSMethod(this, "onActivityResult", void.class, params); + } + + public boolean equals__super(java.lang.Object other) { + return super.equals(other); + } + public int hashCode__super() { + return super.hashCode(); + } + +} diff --git a/widgets/src/main/java/com/tns/NativeScriptApplication.java b/widgets/src/main/java/com/tns/NativeScriptApplication.java new file mode 100644 index 000000000..dd3d11803 --- /dev/null +++ b/widgets/src/main/java/com/tns/NativeScriptApplication.java @@ -0,0 +1,52 @@ +package com.tns; + +import android.app.Application; + +@JavaScriptImplementation(javaScriptFile = "app/tns_modules/application/application.js") +public class NativeScriptApplication extends android.app.Application implements com.tns.NativeScriptHashCodeProvider { + + private static NativeScriptApplication thiz; + + public NativeScriptApplication() + { + thiz = this; + } + + protected void attachBaseContext(android.content.Context param_0) { + super.attachBaseContext(param_0); + + new RuntimeHelper(this).initRuntime(); + + Platform.initInstance(this); + } + + + public void onCreate() { + java.lang.Object[] params = null; + com.tns.Platform.callJSMethod(this, "onCreate", void.class, params); + } + + public void onLowMemory() { + java.lang.Object[] params = null; + com.tns.Platform.callJSMethod(this, "onLowMemory", void.class, params); + } + + public void onTrimMemory(int level) { + java.lang.Object[] params = new Object[1]; + params[0] = level; + com.tns.Platform.callJSMethod(this, "onTrimMemory", void.class, params); + } + + + public boolean equals__super(java.lang.Object other) { + return super.equals(other); + } + public int hashCode__super() { + return super.hashCode(); + } + + public static Application getInstance() { + return thiz; + } + +} diff --git a/widgets/src/main/java/com/tns/NativeScriptSyncService.java b/widgets/src/main/java/com/tns/NativeScriptSyncService.java new file mode 100644 index 000000000..1a51feee3 --- /dev/null +++ b/widgets/src/main/java/com/tns/NativeScriptSyncService.java @@ -0,0 +1,424 @@ +package com.tns; + +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.OutputStream; + +import android.content.Context; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager.NameNotFoundException; +import android.net.LocalServerSocket; +import android.net.LocalSocket; +import android.util.Log; + +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); + new Thread(commThread).start(); + } + } + catch (IOException e) + { + e.printStackTrace(); + } + } + } + + private class ListenerWorker implements Runnable + { + private final DataInputStream input; + private Closeable socket; + private OutputStream output; + + public ListenerWorker(LocalSocket socket) throws IOException + { + this.socket = socket; + input = new DataInputStream(socket.getInputStream()); + output = socket.getOutputStream(); + } + + public void run() + { + try + { + int length = input.readInt(); + input.readFully(new byte[length]); // ignore the payload + executePartialSync(context, syncDir); + executeRemovedSync(context, removedSyncDir); + + Platform.runScript(new File(NativeScriptSyncService.this.context.getFilesDir(), "internal/livesync.js")); + try + { + output.write(1); + } + catch (IOException e) + { + e.printStackTrace(); + } + 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) + { + if (logger.isEnabled()) + { + logger.write("Syncing total number of fiiles: " + files.length); + } + + 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); + } + } + } + else + { + if (logger.isEnabled()) + { + logger.write("Can't move files. Source is empty."); + } + } + } + + // 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()) + { + Log.e("TNS", "Application dir does not exists. Partial Sync failed. appDir: " + appPath); + return; + } + + if (logger.isEnabled()) + { + logger.write("Syncing sourceDir " + sourceDir.getAbsolutePath() + " with " + appDir.getAbsolutePath()); + } + + moveFiles(sourceDir, sourceDir.getAbsolutePath(), appDir.getAbsolutePath()); + } + + private void deleteRemovedFiles(File sourceDir, String sourceRootAbsolutePath, String targetRootAbsolutePath) + { + if (!sourceDir.exists()) + { + if (logger.isEnabled()) + { + logger.write("Directory does not exist: " + sourceDir.getAbsolutePath()); + } + } + + 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; + } +} diff --git a/widgets/src/main/java/com/tns/NativeScriptUncaughtExceptionHandler.java b/widgets/src/main/java/com/tns/NativeScriptUncaughtExceptionHandler.java new file mode 100644 index 000000000..645aabe2e --- /dev/null +++ b/widgets/src/main/java/com/tns/NativeScriptUncaughtExceptionHandler.java @@ -0,0 +1,55 @@ +package com.tns; + +import java.lang.Thread.UncaughtExceptionHandler; + +import android.content.Context; + +public class NativeScriptUncaughtExceptionHandler implements UncaughtExceptionHandler +{ + private final Context context; + + private final UncaughtExceptionHandler defaultHandler; + + private final Logger logger; + + public NativeScriptUncaughtExceptionHandler(Logger logger, Context context) + { + this.logger = logger; + this.context = context; + defaultHandler = Thread.getDefaultUncaughtExceptionHandler(); + } + + @Override + public void uncaughtException(Thread thread, Throwable ex) + { + String errorMessage = ErrorReport.getErrorMessage(ex); + + if (Platform.isInitialized()) + { + try + { + ex.printStackTrace(); + Platform.passUncaughtExceptionToJsNative(ex, errorMessage); + + if (JsDebugger.isJsDebugerActive()) + { + return; + } + } + catch (Throwable t) + { + t.printStackTrace(); + } + } + + if (logger.isEnabled()) + { + logger.write("Uncaught Exception Message=" + errorMessage); + } + + if (!ErrorReport.startActivity(context, errorMessage) && defaultHandler != null) + { + defaultHandler.uncaughtException(thread, ex); + } + } +} diff --git a/widgets/src/main/java/com/tns/RuntimeHelper.java b/widgets/src/main/java/com/tns/RuntimeHelper.java new file mode 100644 index 000000000..fb09ae477 --- /dev/null +++ b/widgets/src/main/java/com/tns/RuntimeHelper.java @@ -0,0 +1,108 @@ +package com.tns; + +import java.io.File; + +import android.app.Application; +import android.content.pm.PackageManager.NameNotFoundException; +import android.os.Handler; +import android.os.Looper; +import android.util.Log; + +public class RuntimeHelper +{ + private final Application app; + + public RuntimeHelper(Application app) + { + this.app = app; + } + + // hasErrorIntent tells you if there was an event (with an uncaught exception) raised from ErrorReport + public boolean hasErrorIntent() + { + boolean hasErrorIntent = false; + + try + { + //empty file just to check if there was a raised uncaught error by ErrorReport + File errFile = new File(app.getFilesDir(), ErrorReport.ERROR_FILE_NAME); + + if (errFile.exists()) + { + errFile.delete(); + hasErrorIntent = true; + } + } + catch (Exception e) + { + Log.d(logTag, e.getMessage()); + } + + return hasErrorIntent; + } + + public void initRuntime() + { + System.loadLibrary("NativeScript"); + + Logger logger = new LogcatLogger(true, app); + + boolean showErrorIntent = hasErrorIntent(); + if (!showErrorIntent) + { + Thread.UncaughtExceptionHandler exHandler = new NativeScriptUncaughtExceptionHandler(logger, app); + + Thread.setDefaultUncaughtExceptionHandler(exHandler); + + Async.Http.setApplicationContext(this.app); + + ExtractPolicy extractPolicy = new DefaultExtractPolicy(logger); + boolean skipAssetExtraction = Util.runPlugin(logger, app); + if (!skipAssetExtraction) + { + new AssetExtractor(null, logger).extractAssets(app, extractPolicy); + } + + if (NativeScriptSyncService.isSyncEnabled(this.app)) + { + NativeScriptSyncService syncService = new NativeScriptSyncService(logger, this.app); + + 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 = app.getPackageName(); + File rootDir = new File(app.getApplicationInfo().dataDir); + File appDir = app.getFilesDir(); + + ClassLoader classLoader = app.getClassLoader(); + File dexDir = new File(rootDir, "code_cache/secondary-dexes"); + String dexThumb = null; + try + { + dexThumb = Util.getDexThumb(app); + } + catch (NameNotFoundException e) + { + if (logger.isEnabled()) logger.write("Error while getting current proxy thumb"); + e.printStackTrace(); + } + ThreadScheduler workThreadScheduler = new WorkThreadScheduler(new Handler(Looper.getMainLooper())); + Platform.init(this.app, workThreadScheduler, logger, appName, null, rootDir, appDir, classLoader, dexDir, dexThumb); + Platform.runScript(new File(appDir, "internal/ts_helpers.js")); + Platform.run(); + } + } + + private final String logTag = "MyApp"; +} \ No newline at end of file diff --git a/widgets/src/main/java/com/tns/Util.java b/widgets/src/main/java/com/tns/Util.java new file mode 100644 index 000000000..56b32090e --- /dev/null +++ b/widgets/src/main/java/com/tns/Util.java @@ -0,0 +1,121 @@ +package com.tns; + +import java.io.*; + +import com.tns.internal.Plugin; + +import android.content.Context; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.content.pm.PackageManager.NameNotFoundException; +import android.os.Bundle; + +public final class Util +{ + private Util() + { + } + + public static String getDexThumb(Context context) throws NameNotFoundException + { + PackageInfo packageInfo = context.getPackageManager().getPackageInfo(context.getPackageName(), 0); + int code = packageInfo.versionCode; + long updateTime = packageInfo.lastUpdateTime; + 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; + String pluginClassName = "org.nativescript.livesync.LiveSyncPlugin"; + try + { + ApplicationInfo ai = context.getPackageManager().getApplicationInfo(context.getPackageName(), PackageManager.GET_META_DATA); + Bundle metadataBundle = ai.metaData; + if (metadataBundle != null) + { + pluginClassName = metadataBundle.getString("com.tns.internal.Plugin"); + } + } + catch (Exception e) + { + if (logger.isEnabled()) + e.printStackTrace(); + } + + try + { + Class liveSyncPluginClass = Class.forName(pluginClassName); + Plugin p = (Plugin) liveSyncPluginClass.newInstance(); + success = p.execute(context); + } + catch (Exception e) + { + if (logger.isEnabled()) + e.printStackTrace(); + } + 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/widgets/src/main/java/com/tns/internal/AppBuilderCallback.java b/widgets/src/main/java/com/tns/internal/AppBuilderCallback.java new file mode 100644 index 000000000..669eabd06 --- /dev/null +++ b/widgets/src/main/java/com/tns/internal/AppBuilderCallback.java @@ -0,0 +1,22 @@ +package com.tns.internal; + +import com.tns.ExtractPolicy; + +public interface AppBuilderCallback +{ + void onConfigurationChanged(android.content.Context context, android.content.res.Configuration newConfig); + + void onCreate(android.content.Context context); + + void onLowMemory(android.content.Context context); + + void onTerminate(android.content.Context context); + + void onTrimMemory(android.content.Context context, int level); + + Thread.UncaughtExceptionHandler getDefaultUncaughtExceptionHandler(); + + ExtractPolicy getExtractPolicy(); + + boolean shouldEnableDebugging(android.content.Context context); +} diff --git a/widgets/src/main/java/com/tns/internal/Plugin.java b/widgets/src/main/java/com/tns/internal/Plugin.java new file mode 100644 index 000000000..c3c1a8038 --- /dev/null +++ b/widgets/src/main/java/com/tns/internal/Plugin.java @@ -0,0 +1,6 @@ +package com.tns.internal; + +public interface Plugin +{ + boolean execute(android.content.Context context) throws Exception; +}