& scale);
diff --git a/deploy/android_demo/app/src/main/java/com/baidu/paddle/lite/demo/ocr/AppCompatPreferenceActivity.java b/deploy/android_demo/app/src/main/java/com/baidu/paddle/lite/demo/ocr/AppCompatPreferenceActivity.java
new file mode 100644
index 00000000..397e4e39
--- /dev/null
+++ b/deploy/android_demo/app/src/main/java/com/baidu/paddle/lite/demo/ocr/AppCompatPreferenceActivity.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.baidu.paddle.lite.demo.ocr;
+
+import android.content.res.Configuration;
+import android.os.Bundle;
+import android.preference.PreferenceActivity;
+import android.support.annotation.LayoutRes;
+import android.support.annotation.Nullable;
+import android.support.v7.app.ActionBar;
+import android.support.v7.app.AppCompatDelegate;
+import android.support.v7.widget.Toolbar;
+import android.view.MenuInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+/**
+ * A {@link PreferenceActivity} which implements and proxies the necessary calls
+ * to be used with AppCompat.
+ *
+ * This technique can be used with an {@link android.app.Activity} class, not just
+ * {@link PreferenceActivity}.
+ */
+public abstract class AppCompatPreferenceActivity extends PreferenceActivity {
+ private AppCompatDelegate mDelegate;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ getDelegate().installViewFactory();
+ getDelegate().onCreate(savedInstanceState);
+ super.onCreate(savedInstanceState);
+ }
+
+ @Override
+ protected void onPostCreate(Bundle savedInstanceState) {
+ super.onPostCreate(savedInstanceState);
+ getDelegate().onPostCreate(savedInstanceState);
+ }
+
+ public ActionBar getSupportActionBar() {
+ return getDelegate().getSupportActionBar();
+ }
+
+ public void setSupportActionBar(@Nullable Toolbar toolbar) {
+ getDelegate().setSupportActionBar(toolbar);
+ }
+
+ @Override
+ public MenuInflater getMenuInflater() {
+ return getDelegate().getMenuInflater();
+ }
+
+ @Override
+ public void setContentView(@LayoutRes int layoutResID) {
+ getDelegate().setContentView(layoutResID);
+ }
+
+ @Override
+ public void setContentView(View view) {
+ getDelegate().setContentView(view);
+ }
+
+ @Override
+ public void setContentView(View view, ViewGroup.LayoutParams params) {
+ getDelegate().setContentView(view, params);
+ }
+
+ @Override
+ public void addContentView(View view, ViewGroup.LayoutParams params) {
+ getDelegate().addContentView(view, params);
+ }
+
+ @Override
+ protected void onPostResume() {
+ super.onPostResume();
+ getDelegate().onPostResume();
+ }
+
+ @Override
+ protected void onTitleChanged(CharSequence title, int color) {
+ super.onTitleChanged(title, color);
+ getDelegate().setTitle(title);
+ }
+
+ @Override
+ public void onConfigurationChanged(Configuration newConfig) {
+ super.onConfigurationChanged(newConfig);
+ getDelegate().onConfigurationChanged(newConfig);
+ }
+
+ @Override
+ protected void onStop() {
+ super.onStop();
+ getDelegate().onStop();
+ }
+
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
+ getDelegate().onDestroy();
+ }
+
+ public void invalidateOptionsMenu() {
+ getDelegate().invalidateOptionsMenu();
+ }
+
+ private AppCompatDelegate getDelegate() {
+ if (mDelegate == null) {
+ mDelegate = AppCompatDelegate.create(this, null);
+ }
+ return mDelegate;
+ }
+}
diff --git a/deploy/android_demo/app/src/main/java/com/baidu/paddle/lite/demo/ocr/MainActivity.java b/deploy/android_demo/app/src/main/java/com/baidu/paddle/lite/demo/ocr/MainActivity.java
new file mode 100644
index 00000000..b72d72df
--- /dev/null
+++ b/deploy/android_demo/app/src/main/java/com/baidu/paddle/lite/demo/ocr/MainActivity.java
@@ -0,0 +1,414 @@
+package com.baidu.paddle.lite.demo.ocr;
+
+import android.Manifest;
+import android.app.ProgressDialog;
+import android.content.ContentResolver;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.content.pm.PackageManager;
+import android.database.Cursor;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Message;
+import android.preference.PreferenceManager;
+import android.provider.MediaStore;
+import android.support.annotation.NonNull;
+import android.support.v4.app.ActivityCompat;
+import android.support.v4.content.ContextCompat;
+import android.support.v7.app.AppCompatActivity;
+import android.text.method.ScrollingMovementMethod;
+import android.util.Log;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+import android.widget.ImageView;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+
+public class MainActivity extends AppCompatActivity {
+ private static final String TAG = MainActivity.class.getSimpleName();
+ public static final int OPEN_GALLERY_REQUEST_CODE = 0;
+ public static final int TAKE_PHOTO_REQUEST_CODE = 1;
+
+ public static final int REQUEST_LOAD_MODEL = 0;
+ public static final int REQUEST_RUN_MODEL = 1;
+ public static final int RESPONSE_LOAD_MODEL_SUCCESSED = 0;
+ public static final int RESPONSE_LOAD_MODEL_FAILED = 1;
+ public static final int RESPONSE_RUN_MODEL_SUCCESSED = 2;
+ public static final int RESPONSE_RUN_MODEL_FAILED = 3;
+
+ protected ProgressDialog pbLoadModel = null;
+ protected ProgressDialog pbRunModel = null;
+
+ protected Handler receiver = null; // Receive messages from worker thread
+ protected Handler sender = null; // Send command to worker thread
+ protected HandlerThread worker = null; // Worker thread to load&run model
+
+ // UI components of object detection
+ protected TextView tvInputSetting;
+ protected ImageView ivInputImage;
+ protected TextView tvOutputResult;
+ protected TextView tvInferenceTime;
+
+ // Model settings of object detection
+ protected String modelPath = "";
+ protected String labelPath = "";
+ protected String imagePath = "";
+ protected int cpuThreadNum = 1;
+ protected String cpuPowerMode = "";
+ protected String inputColorFormat = "";
+ protected long[] inputShape = new long[]{};
+ protected float[] inputMean = new float[]{};
+ protected float[] inputStd = new float[]{};
+ protected float scoreThreshold = 0.1f;
+
+ protected Predictor predictor = new Predictor();
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_main);
+
+ // Clear all setting items to avoid app crashing due to the incorrect settings
+ SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
+ SharedPreferences.Editor editor = sharedPreferences.edit();
+ editor.clear();
+ editor.commit();
+
+ // Prepare the worker thread for mode loading and inference
+ receiver = new Handler() {
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case RESPONSE_LOAD_MODEL_SUCCESSED:
+ pbLoadModel.dismiss();
+ onLoadModelSuccessed();
+ break;
+ case RESPONSE_LOAD_MODEL_FAILED:
+ pbLoadModel.dismiss();
+ Toast.makeText(MainActivity.this, "Load model failed!", Toast.LENGTH_SHORT).show();
+ onLoadModelFailed();
+ break;
+ case RESPONSE_RUN_MODEL_SUCCESSED:
+ pbRunModel.dismiss();
+ onRunModelSuccessed();
+ break;
+ case RESPONSE_RUN_MODEL_FAILED:
+ pbRunModel.dismiss();
+ Toast.makeText(MainActivity.this, "Run model failed!", Toast.LENGTH_SHORT).show();
+ onRunModelFailed();
+ break;
+ default:
+ break;
+ }
+ }
+ };
+
+ worker = new HandlerThread("Predictor Worker");
+ worker.start();
+ sender = new Handler(worker.getLooper()) {
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case REQUEST_LOAD_MODEL:
+ // Load model and reload test image
+ if (onLoadModel()) {
+ receiver.sendEmptyMessage(RESPONSE_LOAD_MODEL_SUCCESSED);
+ } else {
+ receiver.sendEmptyMessage(RESPONSE_LOAD_MODEL_FAILED);
+ }
+ break;
+ case REQUEST_RUN_MODEL:
+ // Run model if model is loaded
+ if (onRunModel()) {
+ receiver.sendEmptyMessage(RESPONSE_RUN_MODEL_SUCCESSED);
+ } else {
+ receiver.sendEmptyMessage(RESPONSE_RUN_MODEL_FAILED);
+ }
+ break;
+ default:
+ break;
+ }
+ }
+ };
+
+ // Setup the UI components
+ tvInputSetting = findViewById(R.id.tv_input_setting);
+ ivInputImage = findViewById(R.id.iv_input_image);
+ tvInferenceTime = findViewById(R.id.tv_inference_time);
+ tvOutputResult = findViewById(R.id.tv_output_result);
+ tvInputSetting.setMovementMethod(ScrollingMovementMethod.getInstance());
+ tvOutputResult.setMovementMethod(ScrollingMovementMethod.getInstance());
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
+ boolean settingsChanged = false;
+ String model_path = sharedPreferences.getString(getString(R.string.MODEL_PATH_KEY),
+ getString(R.string.MODEL_PATH_DEFAULT));
+ String label_path = sharedPreferences.getString(getString(R.string.LABEL_PATH_KEY),
+ getString(R.string.LABEL_PATH_DEFAULT));
+ String image_path = sharedPreferences.getString(getString(R.string.IMAGE_PATH_KEY),
+ getString(R.string.IMAGE_PATH_DEFAULT));
+ settingsChanged |= !model_path.equalsIgnoreCase(modelPath);
+ settingsChanged |= !label_path.equalsIgnoreCase(labelPath);
+ settingsChanged |= !image_path.equalsIgnoreCase(imagePath);
+ int cpu_thread_num = Integer.parseInt(sharedPreferences.getString(getString(R.string.CPU_THREAD_NUM_KEY),
+ getString(R.string.CPU_THREAD_NUM_DEFAULT)));
+ settingsChanged |= cpu_thread_num != cpuThreadNum;
+ String cpu_power_mode =
+ sharedPreferences.getString(getString(R.string.CPU_POWER_MODE_KEY),
+ getString(R.string.CPU_POWER_MODE_DEFAULT));
+ settingsChanged |= !cpu_power_mode.equalsIgnoreCase(cpuPowerMode);
+ String input_color_format =
+ sharedPreferences.getString(getString(R.string.INPUT_COLOR_FORMAT_KEY),
+ getString(R.string.INPUT_COLOR_FORMAT_DEFAULT));
+ settingsChanged |= !input_color_format.equalsIgnoreCase(inputColorFormat);
+ long[] input_shape =
+ Utils.parseLongsFromString(sharedPreferences.getString(getString(R.string.INPUT_SHAPE_KEY),
+ getString(R.string.INPUT_SHAPE_DEFAULT)), ",");
+ float[] input_mean =
+ Utils.parseFloatsFromString(sharedPreferences.getString(getString(R.string.INPUT_MEAN_KEY),
+ getString(R.string.INPUT_MEAN_DEFAULT)), ",");
+ float[] input_std =
+ Utils.parseFloatsFromString(sharedPreferences.getString(getString(R.string.INPUT_STD_KEY)
+ , getString(R.string.INPUT_STD_DEFAULT)), ",");
+ settingsChanged |= input_shape.length != inputShape.length;
+ settingsChanged |= input_mean.length != inputMean.length;
+ settingsChanged |= input_std.length != inputStd.length;
+ if (!settingsChanged) {
+ for (int i = 0; i < input_shape.length; i++) {
+ settingsChanged |= input_shape[i] != inputShape[i];
+ }
+ for (int i = 0; i < input_mean.length; i++) {
+ settingsChanged |= input_mean[i] != inputMean[i];
+ }
+ for (int i = 0; i < input_std.length; i++) {
+ settingsChanged |= input_std[i] != inputStd[i];
+ }
+ }
+ float score_threshold =
+ Float.parseFloat(sharedPreferences.getString(getString(R.string.SCORE_THRESHOLD_KEY),
+ getString(R.string.SCORE_THRESHOLD_DEFAULT)));
+ settingsChanged |= scoreThreshold != score_threshold;
+ if (settingsChanged) {
+ modelPath = model_path;
+ labelPath = label_path;
+ imagePath = image_path;
+ cpuThreadNum = cpu_thread_num;
+ cpuPowerMode = cpu_power_mode;
+ inputColorFormat = input_color_format;
+ inputShape = input_shape;
+ inputMean = input_mean;
+ inputStd = input_std;
+ scoreThreshold = score_threshold;
+ // Update UI
+ tvInputSetting.setText("Model: " + modelPath.substring(modelPath.lastIndexOf("/") + 1) + "\n" + "CPU" +
+ " Thread Num: " + Integer.toString(cpuThreadNum) + "\n" + "CPU Power Mode: " + cpuPowerMode);
+ tvInputSetting.scrollTo(0, 0);
+ // Reload model if configure has been changed
+ loadModel();
+ }
+ }
+
+ public void loadModel() {
+ pbLoadModel = ProgressDialog.show(this, "", "Loading model...", false, false);
+ sender.sendEmptyMessage(REQUEST_LOAD_MODEL);
+ }
+
+ public void runModel() {
+ pbRunModel = ProgressDialog.show(this, "", "Running model...", false, false);
+ sender.sendEmptyMessage(REQUEST_RUN_MODEL);
+ }
+
+ public boolean onLoadModel() {
+ return predictor.init(MainActivity.this, modelPath, labelPath, cpuThreadNum,
+ cpuPowerMode,
+ inputColorFormat,
+ inputShape, inputMean,
+ inputStd, scoreThreshold);
+ }
+
+ public boolean onRunModel() {
+ return predictor.isLoaded() && predictor.runModel();
+ }
+
+ public void onLoadModelSuccessed() {
+ // Load test image from path and run model
+ try {
+ if (imagePath.isEmpty()) {
+ return;
+ }
+ Bitmap image = null;
+ // Read test image file from custom path if the first character of mode path is '/', otherwise read test
+ // image file from assets
+ if (!imagePath.substring(0, 1).equals("/")) {
+ InputStream imageStream = getAssets().open(imagePath);
+ image = BitmapFactory.decodeStream(imageStream);
+ } else {
+ if (!new File(imagePath).exists()) {
+ return;
+ }
+ image = BitmapFactory.decodeFile(imagePath);
+ }
+ if (image != null && predictor.isLoaded()) {
+ predictor.setInputImage(image);
+ runModel();
+ }
+ } catch (IOException e) {
+ Toast.makeText(MainActivity.this, "Load image failed!", Toast.LENGTH_SHORT).show();
+ e.printStackTrace();
+ }
+ }
+
+ public void onLoadModelFailed() {
+ }
+
+ public void onRunModelSuccessed() {
+ // Obtain results and update UI
+ tvInferenceTime.setText("Inference time: " + predictor.inferenceTime() + " ms");
+ Bitmap outputImage = predictor.outputImage();
+ if (outputImage != null) {
+ ivInputImage.setImageBitmap(outputImage);
+ }
+ tvOutputResult.setText(predictor.outputResult());
+ tvOutputResult.scrollTo(0, 0);
+ }
+
+ public void onRunModelFailed() {
+ }
+
+ public void onImageChanged(Bitmap image) {
+ // Rerun model if users pick test image from gallery or camera
+ if (image != null && predictor.isLoaded()) {
+ predictor.setInputImage(image);
+ runModel();
+ }
+ }
+
+ public void onSettingsClicked() {
+ startActivity(new Intent(MainActivity.this, SettingsActivity.class));
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ MenuInflater inflater = getMenuInflater();
+ inflater.inflate(R.menu.menu_action_options, menu);
+ return true;
+ }
+
+ public boolean onPrepareOptionsMenu(Menu menu) {
+ boolean isLoaded = predictor.isLoaded();
+ menu.findItem(R.id.open_gallery).setEnabled(isLoaded);
+ menu.findItem(R.id.take_photo).setEnabled(isLoaded);
+ return super.onPrepareOptionsMenu(menu);
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ switch (item.getItemId()) {
+ case android.R.id.home:
+ finish();
+ break;
+ case R.id.open_gallery:
+ if (requestAllPermissions()) {
+ openGallery();
+ }
+ break;
+ case R.id.take_photo:
+ if (requestAllPermissions()) {
+ takePhoto();
+ }
+ break;
+ case R.id.settings:
+ if (requestAllPermissions()) {
+ // Make sure we have SDCard r&w permissions to load model from SDCard
+ onSettingsClicked();
+ }
+ break;
+ }
+ return super.onOptionsItemSelected(item);
+ }
+
+ @Override
+ public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
+ @NonNull int[] grantResults) {
+ super.onRequestPermissionsResult(requestCode, permissions, grantResults);
+ if (grantResults[0] != PackageManager.PERMISSION_GRANTED || grantResults[1] != PackageManager.PERMISSION_GRANTED) {
+ Toast.makeText(this, "Permission Denied", Toast.LENGTH_SHORT).show();
+ }
+ }
+
+ private boolean requestAllPermissions() {
+ if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)
+ != PackageManager.PERMISSION_GRANTED || ContextCompat.checkSelfPermission(this,
+ Manifest.permission.CAMERA)
+ != PackageManager.PERMISSION_GRANTED) {
+ ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE,
+ Manifest.permission.CAMERA},
+ 0);
+ return false;
+ }
+ return true;
+ }
+
+ private void openGallery() {
+ Intent intent = new Intent(Intent.ACTION_PICK, null);
+ intent.setDataAndType(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, "image/*");
+ startActivityForResult(intent, OPEN_GALLERY_REQUEST_CODE);
+ }
+
+ private void takePhoto() {
+ Intent takePhotoIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
+ if (takePhotoIntent.resolveActivity(getPackageManager()) != null) {
+ startActivityForResult(takePhotoIntent, TAKE_PHOTO_REQUEST_CODE);
+ }
+ }
+
+ @Override
+ protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+ super.onActivityResult(requestCode, resultCode, data);
+ if (resultCode == RESULT_OK && data != null) {
+ switch (requestCode) {
+ case OPEN_GALLERY_REQUEST_CODE:
+ try {
+ ContentResolver resolver = getContentResolver();
+ Uri uri = data.getData();
+ Bitmap image = MediaStore.Images.Media.getBitmap(resolver, uri);
+ String[] proj = {MediaStore.Images.Media.DATA};
+ Cursor cursor = managedQuery(uri, proj, null, null, null);
+ cursor.moveToFirst();
+ onImageChanged(image);
+ } catch (IOException e) {
+ Log.e(TAG, e.toString());
+ }
+ break;
+ case TAKE_PHOTO_REQUEST_CODE:
+ Bundle extras = data.getExtras();
+ Bitmap image = (Bitmap) extras.get("data");
+ onImageChanged(image);
+ break;
+ default:
+ break;
+ }
+ }
+ }
+
+ @Override
+ protected void onDestroy() {
+ if (predictor != null) {
+ predictor.releaseModel();
+ }
+ worker.quit();
+ super.onDestroy();
+ }
+}
diff --git a/deploy/android_demo/app/src/main/java/com/baidu/paddle/lite/demo/ocr/OCRPredictorNative.java b/deploy/android_demo/app/src/main/java/com/baidu/paddle/lite/demo/ocr/OCRPredictorNative.java
new file mode 100644
index 00000000..103d5d37
--- /dev/null
+++ b/deploy/android_demo/app/src/main/java/com/baidu/paddle/lite/demo/ocr/OCRPredictorNative.java
@@ -0,0 +1,100 @@
+package com.baidu.paddle.lite.demo.ocr;
+
+import android.graphics.Bitmap;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+public class OCRPredictorNative {
+
+ private static final AtomicBoolean isSOLoaded = new AtomicBoolean();
+
+ public static void loadLibrary() throws RuntimeException {
+ if (!isSOLoaded.get() && isSOLoaded.compareAndSet(false, true)) {
+ try {
+ System.loadLibrary("Native");
+ } catch (Throwable e) {
+ RuntimeException exception = new RuntimeException(
+ "Load libNative.so failed, please check it exists in apk file.", e);
+ throw exception;
+ }
+ }
+ }
+
+ private Config config;
+
+ private long nativePointer = 0;
+
+ public OCRPredictorNative(Config config) {
+ this.config = config;
+ loadLibrary();
+ nativePointer = init(config.detModelFilename, config.recModelFilename,
+ config.cpuThreadNum, config.cpuPower);
+ Log.i("OCRPredictorNative", "load success " + nativePointer);
+
+ }
+
+ public void release(){
+ if (nativePointer != 0){
+ nativePointer = 0;
+ destory(nativePointer);
+ }
+ }
+
+ public ArrayList runImage(float[] inputData, int width, int height, int channels, Bitmap originalImage) {
+ Log.i("OCRPredictorNative", "begin to run image " + inputData.length + " " + width + " " + height);
+ float[] dims = new float[]{1, channels, height, width};
+ float[] rawResults = forward(nativePointer, inputData, dims, originalImage);
+ ArrayList results = postprocess(rawResults);
+ return results;
+ }
+
+ public static class Config {
+ public int cpuThreadNum;
+ public String cpuPower;
+ public String detModelFilename;
+ public String recModelFilename;
+
+ }
+
+ protected native long init(String detModelPath, String recModelPath, int threadNum, String cpuMode);
+
+ protected native float[] forward(long pointer, float[] buf, float[] ddims, Bitmap originalImage);
+
+ protected native void destory(long pointer);
+
+ private ArrayList postprocess(float[] raw) {
+ ArrayList results = new ArrayList();
+ int begin = 0;
+
+ while (begin < raw.length) {
+ int point_num = Math.round(raw[begin]);
+ int word_num = Math.round(raw[begin + 1]);
+ OcrResultModel model = parse(raw, begin + 2, point_num, word_num);
+ begin += 2 + 1 + point_num * 2 + word_num;
+ results.add(model);
+ }
+
+ return results;
+ }
+
+ private OcrResultModel parse(float[] raw, int begin, int pointNum, int wordNum) {
+ int current = begin;
+ OcrResultModel model = new OcrResultModel();
+ model.setConfidence(raw[current]);
+ current++;
+ for (int i = 0; i < pointNum; i++) {
+ model.addPoints(Math.round(raw[current + i * 2]), Math.round(raw[current + i * 2 + 1]));
+ }
+ current += (pointNum * 2);
+ for (int i = 0; i < wordNum; i++) {
+ int index = Math.round(raw[current + i]);
+ model.addWordIndex(index);
+ }
+ Log.i("OCRPredictorNative", "word finished " + wordNum);
+ return model;
+ }
+
+
+}
diff --git a/deploy/android_demo/app/src/main/java/com/baidu/paddle/lite/demo/ocr/OcrResultModel.java b/deploy/android_demo/app/src/main/java/com/baidu/paddle/lite/demo/ocr/OcrResultModel.java
new file mode 100644
index 00000000..9494574e
--- /dev/null
+++ b/deploy/android_demo/app/src/main/java/com/baidu/paddle/lite/demo/ocr/OcrResultModel.java
@@ -0,0 +1,52 @@
+package com.baidu.paddle.lite.demo.ocr;
+
+import android.graphics.Point;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class OcrResultModel {
+ private List points;
+ private List wordIndex;
+ private String label;
+ private float confidence;
+
+ public OcrResultModel() {
+ super();
+ points = new ArrayList<>();
+ wordIndex = new ArrayList<>();
+ }
+
+ public void addPoints(int x, int y) {
+ Point point = new Point(x, y);
+ points.add(point);
+ }
+
+ public void addWordIndex(int index) {
+ wordIndex.add(index);
+ }
+
+ public List getPoints() {
+ return points;
+ }
+
+ public List getWordIndex() {
+ return wordIndex;
+ }
+
+ public String getLabel() {
+ return label;
+ }
+
+ public void setLabel(String label) {
+ this.label = label;
+ }
+
+ public float getConfidence() {
+ return confidence;
+ }
+
+ public void setConfidence(float confidence) {
+ this.confidence = confidence;
+ }
+}
diff --git a/deploy/android_demo/app/src/main/java/com/baidu/paddle/lite/demo/ocr/Predictor.java b/deploy/android_demo/app/src/main/java/com/baidu/paddle/lite/demo/ocr/Predictor.java
new file mode 100644
index 00000000..d491481e
--- /dev/null
+++ b/deploy/android_demo/app/src/main/java/com/baidu/paddle/lite/demo/ocr/Predictor.java
@@ -0,0 +1,351 @@
+package com.baidu.paddle.lite.demo.ocr;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.Path;
+import android.graphics.Point;
+import android.util.Log;
+
+import java.io.File;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+import java.util.Vector;
+
+import static android.graphics.Color.*;
+
+public class Predictor {
+ private static final String TAG = Predictor.class.getSimpleName();
+ public boolean isLoaded = false;
+ public int warmupIterNum = 1;
+ public int inferIterNum = 1;
+ public int cpuThreadNum = 4;
+ public String cpuPowerMode = "LITE_POWER_HIGH";
+ public String modelPath = "";
+ public String modelName = "";
+ protected OCRPredictorNative paddlePredictor = null;
+ protected float inferenceTime = 0;
+ // Only for object detection
+ protected Vector wordLabels = new Vector();
+ protected String inputColorFormat = "BGR";
+ protected long[] inputShape = new long[]{1, 3, 960};
+ protected float[] inputMean = new float[]{0.485f, 0.456f, 0.406f};
+ protected float[] inputStd = new float[]{1.0f / 0.229f, 1.0f / 0.224f, 1.0f / 0.225f};
+ protected float scoreThreshold = 0.1f;
+ protected Bitmap inputImage = null;
+ protected Bitmap outputImage = null;
+ protected String outputResult = "";
+ protected float preprocessTime = 0;
+ protected float postprocessTime = 0;
+
+
+ public Predictor() {
+ }
+
+ public boolean init(Context appCtx, String modelPath, String labelPath, int cpuThreadNum, String cpuPowerMode,
+ String inputColorFormat,
+ long[] inputShape, float[] inputMean,
+ float[] inputStd, float scoreThreshold) {
+ if (inputShape.length != 3) {
+ Log.e(TAG, "Size of input shape should be: 3");
+ return false;
+ }
+ if (inputMean.length != inputShape[1]) {
+ Log.e(TAG, "Size of input mean should be: " + Long.toString(inputShape[1]));
+ return false;
+ }
+ if (inputStd.length != inputShape[1]) {
+ Log.e(TAG, "Size of input std should be: " + Long.toString(inputShape[1]));
+ return false;
+ }
+ if (inputShape[0] != 1) {
+ Log.e(TAG, "Only one batch is supported in the image classification demo, you can use any batch size in " +
+ "your Apps!");
+ return false;
+ }
+ if (inputShape[1] != 1 && inputShape[1] != 3) {
+ Log.e(TAG, "Only one/three channels are supported in the image classification demo, you can use any " +
+ "channel size in your Apps!");
+ return false;
+ }
+ if (!inputColorFormat.equalsIgnoreCase("BGR")) {
+ Log.e(TAG, "Only BGR color format is supported.");
+ return false;
+ }
+ isLoaded = loadModel(appCtx, modelPath, cpuThreadNum, cpuPowerMode);
+ if (!isLoaded) {
+ return false;
+ }
+ isLoaded = loadLabel(appCtx, labelPath);
+ if (!isLoaded) {
+ return false;
+ }
+ this.inputColorFormat = inputColorFormat;
+ this.inputShape = inputShape;
+ this.inputMean = inputMean;
+ this.inputStd = inputStd;
+ this.scoreThreshold = scoreThreshold;
+ return true;
+ }
+
+ protected boolean loadModel(Context appCtx, String modelPath, int cpuThreadNum, String cpuPowerMode) {
+ // Release model if exists
+ releaseModel();
+
+ // Load model
+ if (modelPath.isEmpty()) {
+ return false;
+ }
+ String realPath = modelPath;
+ if (!modelPath.substring(0, 1).equals("/")) {
+ // Read model files from custom path if the first character of mode path is '/'
+ // otherwise copy model to cache from assets
+ realPath = appCtx.getCacheDir() + "/" + modelPath;
+ Utils.copyDirectoryFromAssets(appCtx, modelPath, realPath);
+ }
+ if (realPath.isEmpty()) {
+ return false;
+ }
+
+ OCRPredictorNative.Config config = new OCRPredictorNative.Config();
+ config.cpuThreadNum = cpuThreadNum;
+ config.detModelFilename = realPath + File.separator + "ch_det_mv3_db_opt.nb";
+ config.recModelFilename = realPath + File.separator + "ch_rec_mv3_crnn_opt.nb";
+ Log.e("Predictor", "model path" + config.detModelFilename + " ; " + config.recModelFilename);
+ config.cpuPower = cpuPowerMode;
+ paddlePredictor = new OCRPredictorNative(config);
+
+ this.cpuThreadNum = cpuThreadNum;
+ this.cpuPowerMode = cpuPowerMode;
+ this.modelPath = realPath;
+ this.modelName = realPath.substring(realPath.lastIndexOf("/") + 1);
+ return true;
+ }
+
+ public void releaseModel() {
+ if (paddlePredictor != null){
+ paddlePredictor.release();
+ paddlePredictor = null;
+ }
+ isLoaded = false;
+ cpuThreadNum = 4;
+ cpuPowerMode = "LITE_POWER_HIGH";
+ modelPath = "";
+ modelName = "";
+ }
+
+ protected boolean loadLabel(Context appCtx, String labelPath) {
+ wordLabels.clear();
+ // Load word labels from file
+ try {
+ InputStream assetsInputStream = appCtx.getAssets().open(labelPath);
+ int available = assetsInputStream.available();
+ byte[] lines = new byte[available];
+ assetsInputStream.read(lines);
+ assetsInputStream.close();
+ String words = new String(lines);
+ String[] contents = words.split("\n");
+ for (String content : contents) {
+ wordLabels.add(content);
+ }
+ Log.i(TAG, "Word label size: " + wordLabels.size());
+ } catch (Exception e) {
+ Log.e(TAG, e.getMessage());
+ return false;
+ }
+ return true;
+ }
+
+
+ public boolean runModel() {
+ if (inputImage == null || !isLoaded()) {
+ return false;
+ }
+
+ // Pre-process image, and feed input tensor with pre-processed data
+
+ Bitmap scaleImage = Utils.resizeWithStep(inputImage, Long.valueOf(inputShape[2]).intValue(), 32);
+
+ Date start = new Date();
+ int channels = (int) inputShape[1];
+ int width = scaleImage.getWidth();
+ int height = scaleImage.getHeight();
+ float[] inputData = new float[channels * width * height];
+ if (channels == 3) {
+ int[] channelIdx = null;
+ if (inputColorFormat.equalsIgnoreCase("RGB")) {
+ channelIdx = new int[]{0, 1, 2};
+ } else if (inputColorFormat.equalsIgnoreCase("BGR")) {
+ channelIdx = new int[]{2, 1, 0};
+ } else {
+ Log.i(TAG, "Unknown color format " + inputColorFormat + ", only RGB and BGR color format is " +
+ "supported!");
+ return false;
+ }
+ int[] channelStride = new int[]{width * height, width * height * 2};
+ int p = scaleImage.getPixel(scaleImage.getWidth() - 1, scaleImage.getHeight() - 1);
+ for (int y = 0; y < height; y++) {
+ for (int x = 0; x < width; x++) {
+ int color = scaleImage.getPixel(x, y);
+ float[] rgb = new float[]{(float) red(color) / 255.0f, (float) green(color) / 255.0f,
+ (float) blue(color) / 255.0f};
+ inputData[y * width + x] = (rgb[channelIdx[0]] - inputMean[0]) / inputStd[0];
+ inputData[y * width + x + channelStride[0]] = (rgb[channelIdx[1]] - inputMean[1]) / inputStd[1];
+ inputData[y * width + x + channelStride[1]] = (rgb[channelIdx[2]] - inputMean[2]) / inputStd[2];
+
+ }
+ }
+ } else if (channels == 1) {
+ for (int y = 0; y < height; y++) {
+ for (int x = 0; x < width; x++) {
+ int color = inputImage.getPixel(x, y);
+ float gray = (float) (red(color) + green(color) + blue(color)) / 3.0f / 255.0f;
+ inputData[y * width + x] = (gray - inputMean[0]) / inputStd[0];
+ }
+ }
+ } else {
+ Log.i(TAG, "Unsupported channel size " + Integer.toString(channels) + ", only channel 1 and 3 is " +
+ "supported!");
+ return false;
+ }
+ float[] pixels = inputData;
+ Log.i(TAG, "pixels " + pixels[0] + " " + pixels[1] + " " + pixels[2] + " " + pixels[3]
+ + " " + pixels[pixels.length / 2] + " " + pixels[pixels.length / 2 + 1] + " " + pixels[pixels.length - 2] + " " + pixels[pixels.length - 1]);
+ Date end = new Date();
+ preprocessTime = (float) (end.getTime() - start.getTime());
+
+ // Warm up
+ for (int i = 0; i < warmupIterNum; i++) {
+ paddlePredictor.runImage(inputData, width, height, channels, inputImage);
+ }
+ warmupIterNum = 0; // 之后不要再warm了
+ // Run inference
+ start = new Date();
+ ArrayList results = paddlePredictor.runImage(inputData, width, height, channels, inputImage);
+ end = new Date();
+ inferenceTime = (end.getTime() - start.getTime()) / (float) inferIterNum;
+
+ results = postprocess(results);
+ Log.i(TAG, "[stat] Preprocess Time: " + preprocessTime
+ + " ; Inference Time: " + inferenceTime + " ;Box Size " + results.size());
+ drawResults(results);
+
+ return true;
+ }
+
+
+ public boolean isLoaded() {
+ return paddlePredictor != null && isLoaded;
+ }
+
+ public String modelPath() {
+ return modelPath;
+ }
+
+ public String modelName() {
+ return modelName;
+ }
+
+ public int cpuThreadNum() {
+ return cpuThreadNum;
+ }
+
+ public String cpuPowerMode() {
+ return cpuPowerMode;
+ }
+
+ public float inferenceTime() {
+ return inferenceTime;
+ }
+
+ public Bitmap inputImage() {
+ return inputImage;
+ }
+
+ public Bitmap outputImage() {
+ return outputImage;
+ }
+
+ public String outputResult() {
+ return outputResult;
+ }
+
+ public float preprocessTime() {
+ return preprocessTime;
+ }
+
+ public float postprocessTime() {
+ return postprocessTime;
+ }
+
+
+ public void setInputImage(Bitmap image) {
+ if (image == null) {
+ return;
+ }
+ // Scale image to the size of input tensor
+ Bitmap rgbaImage = image.copy(Bitmap.Config.ARGB_8888, true);
+ this.inputImage = rgbaImage;
+ }
+
+ private ArrayList postprocess(ArrayList results) {
+ for (OcrResultModel r : results) {
+ StringBuffer word = new StringBuffer();
+ for (int index : r.getWordIndex()) {
+ if (index >= 0 && index < wordLabels.size()) {
+ word.append(wordLabels.get(index));
+ } else {
+ Log.e(TAG, "Word index is not in label list:" + index);
+ word.append("×");
+ }
+ }
+ r.setLabel(word.toString());
+ }
+ return results;
+ }
+
+ private void drawResults(ArrayList results) {
+ StringBuffer outputResultSb = new StringBuffer("");
+ for (int i=0;i points = result.getPoints();
+ path.moveTo(points.get(0).x, points.get(0).y);
+ for (int i = points.size() - 1; i >= 0; i--) {
+ Point p = points.get(i);
+ path.lineTo(p.x, p.y);
+ }
+ canvas.drawPath(path, paint);
+ canvas.drawPath(path, paintFillAlpha);
+ }
+ }
+
+}
diff --git a/deploy/android_demo/app/src/main/java/com/baidu/paddle/lite/demo/ocr/SettingsActivity.java b/deploy/android_demo/app/src/main/java/com/baidu/paddle/lite/demo/ocr/SettingsActivity.java
new file mode 100644
index 00000000..28727e60
--- /dev/null
+++ b/deploy/android_demo/app/src/main/java/com/baidu/paddle/lite/demo/ocr/SettingsActivity.java
@@ -0,0 +1,200 @@
+package com.baidu.paddle.lite.demo.ocr;
+
+import android.content.SharedPreferences;
+import android.os.Bundle;
+import android.preference.CheckBoxPreference;
+import android.preference.EditTextPreference;
+import android.preference.ListPreference;
+import android.support.v7.app.ActionBar;
+
+import java.util.ArrayList;
+import java.util.List;
+
+
+public class SettingsActivity extends AppCompatPreferenceActivity implements SharedPreferences.OnSharedPreferenceChangeListener {
+ ListPreference lpChoosePreInstalledModel = null;
+ CheckBoxPreference cbEnableCustomSettings = null;
+ EditTextPreference etModelPath = null;
+ EditTextPreference etLabelPath = null;
+ EditTextPreference etImagePath = null;
+ ListPreference lpCPUThreadNum = null;
+ ListPreference lpCPUPowerMode = null;
+ ListPreference lpInputColorFormat = null;
+ EditTextPreference etInputShape = null;
+ EditTextPreference etInputMean = null;
+ EditTextPreference etInputStd = null;
+ EditTextPreference etScoreThreshold = null;
+
+ List preInstalledModelPaths = null;
+ List preInstalledLabelPaths = null;
+ List preInstalledImagePaths = null;
+ List preInstalledInputShapes = null;
+ List preInstalledCPUThreadNums = null;
+ List preInstalledCPUPowerModes = null;
+ List preInstalledInputColorFormats = null;
+ List preInstalledInputMeans = null;
+ List preInstalledInputStds = null;
+ List preInstalledScoreThresholds = null;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ addPreferencesFromResource(R.xml.settings);
+ ActionBar supportActionBar = getSupportActionBar();
+ if (supportActionBar != null) {
+ supportActionBar.setDisplayHomeAsUpEnabled(true);
+ }
+
+ // Initialized pre-installed models
+ preInstalledModelPaths = new ArrayList();
+ preInstalledLabelPaths = new ArrayList();
+ preInstalledImagePaths = new ArrayList();
+ preInstalledInputShapes = new ArrayList();
+ preInstalledCPUThreadNums = new ArrayList();
+ preInstalledCPUPowerModes = new ArrayList();
+ preInstalledInputColorFormats = new ArrayList();
+ preInstalledInputMeans = new ArrayList();
+ preInstalledInputStds = new ArrayList();
+ preInstalledScoreThresholds = new ArrayList();
+ // Add ssd_mobilenet_v1_pascalvoc_for_cpu
+ preInstalledModelPaths.add(getString(R.string.MODEL_PATH_DEFAULT));
+ preInstalledLabelPaths.add(getString(R.string.LABEL_PATH_DEFAULT));
+ preInstalledImagePaths.add(getString(R.string.IMAGE_PATH_DEFAULT));
+ preInstalledCPUThreadNums.add(getString(R.string.CPU_THREAD_NUM_DEFAULT));
+ preInstalledCPUPowerModes.add(getString(R.string.CPU_POWER_MODE_DEFAULT));
+ preInstalledInputColorFormats.add(getString(R.string.INPUT_COLOR_FORMAT_DEFAULT));
+ preInstalledInputShapes.add(getString(R.string.INPUT_SHAPE_DEFAULT));
+ preInstalledInputMeans.add(getString(R.string.INPUT_MEAN_DEFAULT));
+ preInstalledInputStds.add(getString(R.string.INPUT_STD_DEFAULT));
+ preInstalledScoreThresholds.add(getString(R.string.SCORE_THRESHOLD_DEFAULT));
+
+ // Setup UI components
+ lpChoosePreInstalledModel =
+ (ListPreference) findPreference(getString(R.string.CHOOSE_PRE_INSTALLED_MODEL_KEY));
+ String[] preInstalledModelNames = new String[preInstalledModelPaths.size()];
+ for (int i = 0; i < preInstalledModelPaths.size(); i++) {
+ preInstalledModelNames[i] =
+ preInstalledModelPaths.get(i).substring(preInstalledModelPaths.get(i).lastIndexOf("/") + 1);
+ }
+ lpChoosePreInstalledModel.setEntries(preInstalledModelNames);
+ lpChoosePreInstalledModel.setEntryValues(preInstalledModelPaths.toArray(new String[preInstalledModelPaths.size()]));
+ cbEnableCustomSettings =
+ (CheckBoxPreference) findPreference(getString(R.string.ENABLE_CUSTOM_SETTINGS_KEY));
+ etModelPath = (EditTextPreference) findPreference(getString(R.string.MODEL_PATH_KEY));
+ etModelPath.setTitle("Model Path (SDCard: " + Utils.getSDCardDirectory() + ")");
+ etLabelPath = (EditTextPreference) findPreference(getString(R.string.LABEL_PATH_KEY));
+ etImagePath = (EditTextPreference) findPreference(getString(R.string.IMAGE_PATH_KEY));
+ lpCPUThreadNum =
+ (ListPreference) findPreference(getString(R.string.CPU_THREAD_NUM_KEY));
+ lpCPUPowerMode =
+ (ListPreference) findPreference(getString(R.string.CPU_POWER_MODE_KEY));
+ lpInputColorFormat =
+ (ListPreference) findPreference(getString(R.string.INPUT_COLOR_FORMAT_KEY));
+ etInputShape = (EditTextPreference) findPreference(getString(R.string.INPUT_SHAPE_KEY));
+ etInputMean = (EditTextPreference) findPreference(getString(R.string.INPUT_MEAN_KEY));
+ etInputStd = (EditTextPreference) findPreference(getString(R.string.INPUT_STD_KEY));
+ etScoreThreshold = (EditTextPreference) findPreference(getString(R.string.SCORE_THRESHOLD_KEY));
+ }
+
+ private void reloadPreferenceAndUpdateUI() {
+ SharedPreferences sharedPreferences = getPreferenceScreen().getSharedPreferences();
+ boolean enableCustomSettings =
+ sharedPreferences.getBoolean(getString(R.string.ENABLE_CUSTOM_SETTINGS_KEY), false);
+ String modelPath = sharedPreferences.getString(getString(R.string.CHOOSE_PRE_INSTALLED_MODEL_KEY),
+ getString(R.string.MODEL_PATH_DEFAULT));
+ int modelIdx = lpChoosePreInstalledModel.findIndexOfValue(modelPath);
+ if (modelIdx >= 0 && modelIdx < preInstalledModelPaths.size()) {
+ if (!enableCustomSettings) {
+ SharedPreferences.Editor editor = sharedPreferences.edit();
+ editor.putString(getString(R.string.MODEL_PATH_KEY), preInstalledModelPaths.get(modelIdx));
+ editor.putString(getString(R.string.LABEL_PATH_KEY), preInstalledLabelPaths.get(modelIdx));
+ editor.putString(getString(R.string.IMAGE_PATH_KEY), preInstalledImagePaths.get(modelIdx));
+ editor.putString(getString(R.string.CPU_THREAD_NUM_KEY), preInstalledCPUThreadNums.get(modelIdx));
+ editor.putString(getString(R.string.CPU_POWER_MODE_KEY), preInstalledCPUPowerModes.get(modelIdx));
+ editor.putString(getString(R.string.INPUT_COLOR_FORMAT_KEY),
+ preInstalledInputColorFormats.get(modelIdx));
+ editor.putString(getString(R.string.INPUT_SHAPE_KEY), preInstalledInputShapes.get(modelIdx));
+ editor.putString(getString(R.string.INPUT_MEAN_KEY), preInstalledInputMeans.get(modelIdx));
+ editor.putString(getString(R.string.INPUT_STD_KEY), preInstalledInputStds.get(modelIdx));
+ editor.putString(getString(R.string.SCORE_THRESHOLD_KEY),
+ preInstalledScoreThresholds.get(modelIdx));
+ editor.commit();
+ }
+ lpChoosePreInstalledModel.setSummary(modelPath);
+ }
+ cbEnableCustomSettings.setChecked(enableCustomSettings);
+ etModelPath.setEnabled(enableCustomSettings);
+ etLabelPath.setEnabled(enableCustomSettings);
+ etImagePath.setEnabled(enableCustomSettings);
+ lpCPUThreadNum.setEnabled(enableCustomSettings);
+ lpCPUPowerMode.setEnabled(enableCustomSettings);
+ lpInputColorFormat.setEnabled(enableCustomSettings);
+ etInputShape.setEnabled(enableCustomSettings);
+ etInputMean.setEnabled(enableCustomSettings);
+ etInputStd.setEnabled(enableCustomSettings);
+ etScoreThreshold.setEnabled(enableCustomSettings);
+ modelPath = sharedPreferences.getString(getString(R.string.MODEL_PATH_KEY),
+ getString(R.string.MODEL_PATH_DEFAULT));
+ String labelPath = sharedPreferences.getString(getString(R.string.LABEL_PATH_KEY),
+ getString(R.string.LABEL_PATH_DEFAULT));
+ String imagePath = sharedPreferences.getString(getString(R.string.IMAGE_PATH_KEY),
+ getString(R.string.IMAGE_PATH_DEFAULT));
+ String cpuThreadNum = sharedPreferences.getString(getString(R.string.CPU_THREAD_NUM_KEY),
+ getString(R.string.CPU_THREAD_NUM_DEFAULT));
+ String cpuPowerMode = sharedPreferences.getString(getString(R.string.CPU_POWER_MODE_KEY),
+ getString(R.string.CPU_POWER_MODE_DEFAULT));
+ String inputColorFormat = sharedPreferences.getString(getString(R.string.INPUT_COLOR_FORMAT_KEY),
+ getString(R.string.INPUT_COLOR_FORMAT_DEFAULT));
+ String inputShape = sharedPreferences.getString(getString(R.string.INPUT_SHAPE_KEY),
+ getString(R.string.INPUT_SHAPE_DEFAULT));
+ String inputMean = sharedPreferences.getString(getString(R.string.INPUT_MEAN_KEY),
+ getString(R.string.INPUT_MEAN_DEFAULT));
+ String inputStd = sharedPreferences.getString(getString(R.string.INPUT_STD_KEY),
+ getString(R.string.INPUT_STD_DEFAULT));
+ String scoreThreshold = sharedPreferences.getString(getString(R.string.SCORE_THRESHOLD_KEY),
+ getString(R.string.SCORE_THRESHOLD_DEFAULT));
+ etModelPath.setSummary(modelPath);
+ etModelPath.setText(modelPath);
+ etLabelPath.setSummary(labelPath);
+ etLabelPath.setText(labelPath);
+ etImagePath.setSummary(imagePath);
+ etImagePath.setText(imagePath);
+ lpCPUThreadNum.setValue(cpuThreadNum);
+ lpCPUThreadNum.setSummary(cpuThreadNum);
+ lpCPUPowerMode.setValue(cpuPowerMode);
+ lpCPUPowerMode.setSummary(cpuPowerMode);
+ lpInputColorFormat.setValue(inputColorFormat);
+ lpInputColorFormat.setSummary(inputColorFormat);
+ etInputShape.setSummary(inputShape);
+ etInputShape.setText(inputShape);
+ etInputMean.setSummary(inputMean);
+ etInputMean.setText(inputMean);
+ etInputStd.setSummary(inputStd);
+ etInputStd.setText(inputStd);
+ etScoreThreshold.setText(scoreThreshold);
+ etScoreThreshold.setSummary(scoreThreshold);
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ getPreferenceScreen().getSharedPreferences().registerOnSharedPreferenceChangeListener(this);
+ reloadPreferenceAndUpdateUI();
+ }
+
+ @Override
+ protected void onPause() {
+ super.onPause();
+ getPreferenceScreen().getSharedPreferences().unregisterOnSharedPreferenceChangeListener(this);
+ }
+
+ @Override
+ public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
+ if (key.equals(getString(R.string.CHOOSE_PRE_INSTALLED_MODEL_KEY))) {
+ SharedPreferences.Editor editor = sharedPreferences.edit();
+ editor.putBoolean(getString(R.string.ENABLE_CUSTOM_SETTINGS_KEY), false);
+ editor.commit();
+ }
+ reloadPreferenceAndUpdateUI();
+ }
+}
diff --git a/deploy/android_demo/app/src/main/java/com/baidu/paddle/lite/demo/ocr/Utils.java b/deploy/android_demo/app/src/main/java/com/baidu/paddle/lite/demo/ocr/Utils.java
new file mode 100644
index 00000000..90549433
--- /dev/null
+++ b/deploy/android_demo/app/src/main/java/com/baidu/paddle/lite/demo/ocr/Utils.java
@@ -0,0 +1,113 @@
+package com.baidu.paddle.lite.demo.ocr;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.os.Environment;
+
+import java.io.*;
+
+public class Utils {
+ private static final String TAG = Utils.class.getSimpleName();
+
+ public static void copyFileFromAssets(Context appCtx, String srcPath, String dstPath) {
+ if (srcPath.isEmpty() || dstPath.isEmpty()) {
+ return;
+ }
+ InputStream is = null;
+ OutputStream os = null;
+ try {
+ is = new BufferedInputStream(appCtx.getAssets().open(srcPath));
+ os = new BufferedOutputStream(new FileOutputStream(new File(dstPath)));
+ byte[] buffer = new byte[1024];
+ int length = 0;
+ while ((length = is.read(buffer)) != -1) {
+ os.write(buffer, 0, length);
+ }
+ } catch (FileNotFoundException e) {
+ e.printStackTrace();
+ } catch (IOException e) {
+ e.printStackTrace();
+ } finally {
+ try {
+ os.close();
+ is.close();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+
+ public static void copyDirectoryFromAssets(Context appCtx, String srcDir, String dstDir) {
+ if (srcDir.isEmpty() || dstDir.isEmpty()) {
+ return;
+ }
+ try {
+ if (!new File(dstDir).exists()) {
+ new File(dstDir).mkdirs();
+ }
+ for (String fileName : appCtx.getAssets().list(srcDir)) {
+ String srcSubPath = srcDir + File.separator + fileName;
+ String dstSubPath = dstDir + File.separator + fileName;
+ if (new File(srcSubPath).isDirectory()) {
+ copyDirectoryFromAssets(appCtx, srcSubPath, dstSubPath);
+ } else {
+ copyFileFromAssets(appCtx, srcSubPath, dstSubPath);
+ }
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+
+ public static float[] parseFloatsFromString(String string, String delimiter) {
+ String[] pieces = string.trim().toLowerCase().split(delimiter);
+ float[] floats = new float[pieces.length];
+ for (int i = 0; i < pieces.length; i++) {
+ floats[i] = Float.parseFloat(pieces[i].trim());
+ }
+ return floats;
+ }
+
+ public static long[] parseLongsFromString(String string, String delimiter) {
+ String[] pieces = string.trim().toLowerCase().split(delimiter);
+ long[] longs = new long[pieces.length];
+ for (int i = 0; i < pieces.length; i++) {
+ longs[i] = Long.parseLong(pieces[i].trim());
+ }
+ return longs;
+ }
+
+ public static String getSDCardDirectory() {
+ return Environment.getExternalStorageDirectory().getAbsolutePath();
+ }
+
+ public static boolean isSupportedNPU() {
+ return false;
+ // String hardware = android.os.Build.HARDWARE;
+ // return hardware.equalsIgnoreCase("kirin810") || hardware.equalsIgnoreCase("kirin990");
+ }
+
+ public static Bitmap resizeWithStep(Bitmap bitmap, int maxLength, int step) {
+ int width = bitmap.getWidth();
+ int height = bitmap.getHeight();
+ int maxWH = Math.max(width, height);
+ float ratio = 1;
+ int newWidth = width;
+ int newHeight = height;
+ if (maxWH > maxLength) {
+ ratio = maxLength * 1.0f / maxWH;
+ newWidth = (int) Math.floor(ratio * width);
+ newHeight = (int) Math.floor(ratio * height);
+ }
+
+ newWidth = newWidth - newWidth % step;
+ if (newWidth == 0) {
+ newWidth = step;
+ }
+ newHeight = newHeight - newHeight % step;
+ if (newHeight == 0) {
+ newHeight = step;
+ }
+ return Bitmap.createScaledBitmap(bitmap, newWidth, newHeight, true);
+ }
+}
diff --git a/deploy/android_demo/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/deploy/android_demo/app/src/main/res/drawable-v24/ic_launcher_foreground.xml
new file mode 100644
index 00000000..1f6bb290
--- /dev/null
+++ b/deploy/android_demo/app/src/main/res/drawable-v24/ic_launcher_foreground.xml
@@ -0,0 +1,34 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/deploy/android_demo/app/src/main/res/drawable/ic_launcher_background.xml b/deploy/android_demo/app/src/main/res/drawable/ic_launcher_background.xml
new file mode 100644
index 00000000..0d025f9b
--- /dev/null
+++ b/deploy/android_demo/app/src/main/res/drawable/ic_launcher_background.xml
@@ -0,0 +1,170 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/deploy/android_demo/app/src/main/res/layout/activity_main.xml b/deploy/android_demo/app/src/main/res/layout/activity_main.xml
new file mode 100644
index 00000000..98b9bc1a
--- /dev/null
+++ b/deploy/android_demo/app/src/main/res/layout/activity_main.xml
@@ -0,0 +1,99 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/deploy/android_demo/app/src/main/res/menu/menu_action_options.xml b/deploy/android_demo/app/src/main/res/menu/menu_action_options.xml
new file mode 100644
index 00000000..fe74758a
--- /dev/null
+++ b/deploy/android_demo/app/src/main/res/menu/menu_action_options.xml
@@ -0,0 +1,21 @@
+
diff --git a/deploy/android_demo/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/deploy/android_demo/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
new file mode 100644
index 00000000..eca70cfe
--- /dev/null
+++ b/deploy/android_demo/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/deploy/android_demo/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/deploy/android_demo/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
new file mode 100644
index 00000000..eca70cfe
--- /dev/null
+++ b/deploy/android_demo/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/deploy/android_demo/app/src/main/res/mipmap-hdpi/ic_launcher.png b/deploy/android_demo/app/src/main/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 00000000..898f3ed5
Binary files /dev/null and b/deploy/android_demo/app/src/main/res/mipmap-hdpi/ic_launcher.png differ
diff --git a/deploy/android_demo/app/src/main/res/mipmap-hdpi/ic_launcher_round.png b/deploy/android_demo/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
new file mode 100644
index 00000000..dffca360
Binary files /dev/null and b/deploy/android_demo/app/src/main/res/mipmap-hdpi/ic_launcher_round.png differ
diff --git a/deploy/android_demo/app/src/main/res/mipmap-mdpi/ic_launcher.png b/deploy/android_demo/app/src/main/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 00000000..64ba76f7
Binary files /dev/null and b/deploy/android_demo/app/src/main/res/mipmap-mdpi/ic_launcher.png differ
diff --git a/deploy/android_demo/app/src/main/res/mipmap-mdpi/ic_launcher_round.png b/deploy/android_demo/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
new file mode 100644
index 00000000..dae5e082
Binary files /dev/null and b/deploy/android_demo/app/src/main/res/mipmap-mdpi/ic_launcher_round.png differ
diff --git a/deploy/android_demo/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/deploy/android_demo/app/src/main/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 00000000..e5ed4659
Binary files /dev/null and b/deploy/android_demo/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ
diff --git a/deploy/android_demo/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/deploy/android_demo/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
new file mode 100644
index 00000000..14ed0af3
Binary files /dev/null and b/deploy/android_demo/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png differ
diff --git a/deploy/android_demo/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/deploy/android_demo/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 00000000..b0907cac
Binary files /dev/null and b/deploy/android_demo/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ
diff --git a/deploy/android_demo/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/deploy/android_demo/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
new file mode 100644
index 00000000..d8ae0315
Binary files /dev/null and b/deploy/android_demo/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png differ
diff --git a/deploy/android_demo/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/deploy/android_demo/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100644
index 00000000..2c18de9e
Binary files /dev/null and b/deploy/android_demo/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ
diff --git a/deploy/android_demo/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/deploy/android_demo/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
new file mode 100644
index 00000000..beed3cdd
Binary files /dev/null and b/deploy/android_demo/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png differ
diff --git a/deploy/android_demo/app/src/main/res/values/arrays.xml b/deploy/android_demo/app/src/main/res/values/arrays.xml
new file mode 100644
index 00000000..8e08ad57
--- /dev/null
+++ b/deploy/android_demo/app/src/main/res/values/arrays.xml
@@ -0,0 +1,39 @@
+
+
+
+ - 1 threads
+ - 2 threads
+ - 4 threads
+ - 8 threads
+
+
+ - 1
+ - 2
+ - 4
+ - 8
+
+
+ - HIGH(only big cores)
+ - LOW(only LITTLE cores)
+ - FULL(all cores)
+ - NO_BIND(depends on system)
+ - RAND_HIGH
+ - RAND_LOW
+
+
+ - LITE_POWER_HIGH
+ - LITE_POWER_LOW
+ - LITE_POWER_FULL
+ - LITE_POWER_NO_BIND
+ - LITE_POWER_RAND_HIGH
+ - LITE_POWER_RAND_LOW
+
+
+ - BGR color format
+ - RGB color format
+
+
+ - BGR
+ - RGB
+
+
\ No newline at end of file
diff --git a/deploy/android_demo/app/src/main/res/values/colors.xml b/deploy/android_demo/app/src/main/res/values/colors.xml
new file mode 100644
index 00000000..69b22338
--- /dev/null
+++ b/deploy/android_demo/app/src/main/res/values/colors.xml
@@ -0,0 +1,6 @@
+
+
+ #008577
+ #00574B
+ #D81B60
+
diff --git a/deploy/android_demo/app/src/main/res/values/strings.xml b/deploy/android_demo/app/src/main/res/values/strings.xml
new file mode 100644
index 00000000..f092ae40
--- /dev/null
+++ b/deploy/android_demo/app/src/main/res/values/strings.xml
@@ -0,0 +1,26 @@
+
+ OCR Chinese
+ CHOOSE_PRE_INSTALLED_MODEL_KEY
+ ENABLE_CUSTOM_SETTINGS_KEY
+ MODEL_PATH_KEY
+ LABEL_PATH_KEY
+ IMAGE_PATH_KEY
+ CPU_THREAD_NUM_KEY
+ CPU_POWER_MODE_KEY
+ INPUT_COLOR_FORMAT_KEY
+ INPUT_SHAPE_KEY
+ INPUT_MEAN_KEY
+ INPUT_STD_KEY
+ SCORE_THRESHOLD_KEY
+ models/ocr_v1_for_cpu
+ labels/ppocr_keys_v1.txt
+ images/5.jpg
+ 4
+ LITE_POWER_HIGH
+ BGR
+ 1,3,960
+ 0.485, 0.456, 0.406
+ 0.229,0.224,0.225
+ 0.1
+
+
diff --git a/deploy/android_demo/app/src/main/res/values/styles.xml b/deploy/android_demo/app/src/main/res/values/styles.xml
new file mode 100644
index 00000000..85326201
--- /dev/null
+++ b/deploy/android_demo/app/src/main/res/values/styles.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/deploy/android_demo/app/src/main/res/xml/settings.xml b/deploy/android_demo/app/src/main/res/xml/settings.xml
new file mode 100644
index 00000000..440f3e47
--- /dev/null
+++ b/deploy/android_demo/app/src/main/res/xml/settings.xml
@@ -0,0 +1,75 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/deploy/android_demo/app/src/test/java/com/baidu/paddle/lite/demo/ocr/ExampleUnitTest.java b/deploy/android_demo/app/src/test/java/com/baidu/paddle/lite/demo/ocr/ExampleUnitTest.java
new file mode 100644
index 00000000..d523a9a7
--- /dev/null
+++ b/deploy/android_demo/app/src/test/java/com/baidu/paddle/lite/demo/ocr/ExampleUnitTest.java
@@ -0,0 +1,17 @@
+package com.baidu.paddle.lite.demo.ocr;
+
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+/**
+ * Example local unit test, which will execute on the development machine (host).
+ *
+ * @see Testing documentation
+ */
+public class ExampleUnitTest {
+ @Test
+ public void addition_isCorrect() {
+ assertEquals(4, 2 + 2);
+ }
+}
\ No newline at end of file
diff --git a/deploy/android_demo/build.gradle b/deploy/android_demo/build.gradle
new file mode 100644
index 00000000..fafc1b97
--- /dev/null
+++ b/deploy/android_demo/build.gradle
@@ -0,0 +1,27 @@
+// Top-level build file where you can add configuration options common to all sub-projects/modules.
+
+buildscript {
+ repositories {
+ google()
+ jcenter()
+
+ }
+ dependencies {
+ classpath 'com.android.tools.build:gradle:3.4.0'
+
+ // NOTE: Do not place your application dependencies here; they belong
+ // in the individual module build.gradle files
+ }
+}
+
+allprojects {
+ repositories {
+ google()
+ jcenter()
+
+ }
+}
+
+task clean(type: Delete) {
+ delete rootProject.buildDir
+}
diff --git a/deploy/android_demo/gradle.properties b/deploy/android_demo/gradle.properties
new file mode 100644
index 00000000..82618cec
--- /dev/null
+++ b/deploy/android_demo/gradle.properties
@@ -0,0 +1,15 @@
+# Project-wide Gradle settings.
+# IDE (e.g. Android Studio) users:
+# Gradle settings configured through the IDE *will override*
+# any settings specified in this file.
+# For more details on how to configure your build environment visit
+# http://www.gradle.org/docs/current/userguide/build_environment.html
+# Specifies the JVM arguments used for the daemon process.
+# The setting is particularly useful for tweaking memory settings.
+org.gradle.jvmargs=-Xmx1536m
+# When configured, Gradle will run in incubating parallel mode.
+# This option should only be used with decoupled projects. More details, visit
+# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
+# org.gradle.parallel=true
+
+
diff --git a/deploy/android_demo/gradle/wrapper/gradle-wrapper.jar b/deploy/android_demo/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 00000000..f6b961fd
Binary files /dev/null and b/deploy/android_demo/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/deploy/android_demo/gradle/wrapper/gradle-wrapper.properties b/deploy/android_demo/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 00000000..578b5482
--- /dev/null
+++ b/deploy/android_demo/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+#Thu Aug 22 15:05:37 CST 2019
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-5.1.1-all.zip
diff --git a/deploy/android_demo/gradlew b/deploy/android_demo/gradlew
new file mode 100644
index 00000000..cccdd3d5
--- /dev/null
+++ b/deploy/android_demo/gradlew
@@ -0,0 +1,172 @@
+#!/usr/bin/env sh
+
+##############################################################################
+##
+## Gradle start up script for UN*X
+##
+##############################################################################
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+ ls=`ls -ld "$PRG"`
+ link=`expr "$ls" : '.*-> \(.*\)$'`
+ if expr "$link" : '/.*' > /dev/null; then
+ PRG="$link"
+ else
+ PRG=`dirname "$PRG"`"/$link"
+ fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >/dev/null
+APP_HOME="`pwd -P`"
+cd "$SAVED" >/dev/null
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS=""
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn () {
+ echo "$*"
+}
+
+die () {
+ echo
+ echo "$*"
+ echo
+ exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "`uname`" in
+ CYGWIN* )
+ cygwin=true
+ ;;
+ Darwin* )
+ darwin=true
+ ;;
+ MINGW* )
+ msys=true
+ ;;
+ NONSTOP* )
+ nonstop=true
+ ;;
+esac
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD="java"
+ which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
+ MAX_FD_LIMIT=`ulimit -H -n`
+ if [ $? -eq 0 ] ; then
+ if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+ MAX_FD="$MAX_FD_LIMIT"
+ fi
+ ulimit -n $MAX_FD
+ if [ $? -ne 0 ] ; then
+ warn "Could not set maximum file descriptor limit: $MAX_FD"
+ fi
+ else
+ warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+ fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+ GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin ; then
+ APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+ CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+ JAVACMD=`cygpath --unix "$JAVACMD"`
+
+ # We build the pattern for arguments to be converted via cygpath
+ ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+ SEP=""
+ for dir in $ROOTDIRSRAW ; do
+ ROOTDIRS="$ROOTDIRS$SEP$dir"
+ SEP="|"
+ done
+ OURCYGPATTERN="(^($ROOTDIRS))"
+ # Add a user-defined pattern to the cygpath arguments
+ if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+ OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+ fi
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ i=0
+ for arg in "$@" ; do
+ CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+ CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
+
+ if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
+ eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+ else
+ eval `echo args$i`="\"$arg\""
+ fi
+ i=$((i+1))
+ done
+ case $i in
+ (0) set -- ;;
+ (1) set -- "$args0" ;;
+ (2) set -- "$args0" "$args1" ;;
+ (3) set -- "$args0" "$args1" "$args2" ;;
+ (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+ (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+ (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+ (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+ (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+ (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+ esac
+fi
+
+# Escape application args
+save () {
+ for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
+ echo " "
+}
+APP_ARGS=$(save "$@")
+
+# Collect all arguments for the java command, following the shell quoting and substitution rules
+eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
+
+# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
+if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
+ cd "$(dirname "$0")"
+fi
+
+exec "$JAVACMD" "$@"
diff --git a/deploy/android_demo/gradlew.bat b/deploy/android_demo/gradlew.bat
new file mode 100644
index 00000000..f9553162
--- /dev/null
+++ b/deploy/android_demo/gradlew.bat
@@ -0,0 +1,84 @@
+@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
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@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=
+
+@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 Windows variants
+
+if not "%OS%" == "Windows_NT" goto win9xME_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=%*
+
+: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/deploy/android_demo/settings.gradle b/deploy/android_demo/settings.gradle
new file mode 100644
index 00000000..e7b4def4
--- /dev/null
+++ b/deploy/android_demo/settings.gradle
@@ -0,0 +1 @@
+include ':app'