Move application bootstrap logic from widgets to project template.

This commit is contained in:
atanasovg
2016-03-03 14:35:41 +02:00
parent 86bec1b41b
commit efeb9ffd49
17 changed files with 0 additions and 1751 deletions

View File

@@ -51,18 +51,10 @@ android {
}
}
android.libraryVariants.all { variant ->
variant.outputs.each { output ->
output.packageLibrary.exclude('libs/android-runtime.jar')
output.packageLibrary.exclude('libs/android-binding-generator.jar')
}
}
dependencies {
compile fileTree(include: ['*.jar'], dir: 'libs')
testCompile 'junit:junit:4.12'
compile 'com.android.support:support-v4:+'
compile files('extlibs/android-binding-generator.jar')
compile files('extlibs/android-runtime.jar')
}
task cleanDistDir (type: Delete) {

View File

Binary file not shown.

View File

Binary file not shown.

View File

@@ -1,414 +0,0 @@
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<KeyValuePair> 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<Closeable> 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<KeyValuePair> headers = new ArrayList<KeyValuePair>();
public int statusCode;
public String responseAsString;
public Bitmap responseAsImage;
public Exception error;
public void getHeaders(HttpURLConnection connection)
{
Map<String, List<String>> 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<Closeable> 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<RequestOptions, Void, RequestResult>
{
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<Closeable> openedStreams = new Stack<Closeable>();
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<Closeable> streams) throws IOException
{
while (streams.size() > 0)
{
Closeable stream = streams.pop();
stream.close();
}
}
}
}
static class ImageDownloadTask extends AsyncTask<String, Void, Bitmap>
{
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);
}
}
}

View File

@@ -1,137 +0,0 @@
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();
}
}
}

View File

@@ -1,197 +0,0 @@
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());
}
}
}

View File

@@ -1,21 +0,0 @@
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);
}
}

View File

@@ -1,71 +0,0 @@
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();
}
}

View File

@@ -1,56 +0,0 @@
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);
}
}
}
}

View File

@@ -1,59 +0,0 @@
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();
}
}

View File

@@ -1,52 +0,0 @@
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;
}
}

View File

@@ -1,424 +0,0 @@
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;
}
}

View File

@@ -1,55 +0,0 @@
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);
}
}
}

View File

@@ -1,108 +0,0 @@
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(false, 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";
}

View File

@@ -1,121 +0,0 @@
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"));
}
}

View File

@@ -1,22 +0,0 @@
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);
}

View File

@@ -1,6 +0,0 @@
package com.tns.internal;
public interface Plugin
{
boolean execute(android.content.Context context) throws Exception;
}