فهرست منبع

1.完成Apex Drivers Order Edit。

Pen Li 7 سال پیش
والد
کامیت
801cfb4593
22فایلهای تغییر یافته به همراه2391 افزوده شده و 22 حذف شده
  1. 16 3
      ApexDrivers/app/src/main/AndroidManifest.xml
  2. 26 0
      ApexDrivers/app/src/main/java/com/usai/redant/apexdrivers/ApexDriverApplication.java
  3. 50 0
      ApexDrivers/app/src/main/java/com/usai/redant/apexdrivers/Detail/DetailActivity.java
  4. 66 0
      ApexDrivers/app/src/main/java/com/usai/redant/apexdrivers/Home/HomeFragment.java
  5. 94 1
      ApexDrivers/app/src/main/java/com/usai/redant/apexdrivers/MainActivity.java
  6. 88 0
      ApexDrivers/app/src/main/java/com/usai/redant/apexdrivers/Network/Network.java
  7. 15 0
      ApexDrivers/app/src/main/java/com/usai/redant/apexdrivers/Update/Model/UpdateBaseModel.java
  8. 7 0
      ApexDrivers/app/src/main/java/com/usai/redant/apexdrivers/Update/Model/UpdateInputModel.java
  9. 6 0
      ApexDrivers/app/src/main/java/com/usai/redant/apexdrivers/Update/Model/UpdateMultInputModel.java
  10. 18 0
      ApexDrivers/app/src/main/java/com/usai/redant/apexdrivers/Update/Model/UpdatePhotoModel.java
  11. 97 0
      ApexDrivers/app/src/main/java/com/usai/redant/apexdrivers/Update/PhotoPreviewActivity.java
  12. 464 4
      ApexDrivers/app/src/main/java/com/usai/redant/apexdrivers/Update/UpdateActivity.java
  13. 131 13
      ApexDrivers/app/src/main/java/com/usai/redant/apexdrivers/Update/UpdateAdapter.java
  14. 202 0
      ApexDrivers/app/src/main/java/com/usai/redant/apexdrivers/Update/XuanImageView/RotationGestureDetector.java
  15. 1033 0
      ApexDrivers/app/src/main/java/com/usai/redant/apexdrivers/Update/XuanImageView/XuanImageView.java
  16. 13 0
      ApexDrivers/app/src/main/java/com/usai/redant/apexdrivers/Update/XuanImageView/XuanImageViewSettings.java
  17. 15 0
      ApexDrivers/app/src/main/res/layout/activity_photo_preview.xml
  18. 1 1
      ApexDrivers/app/src/main/res/layout/capture.xml
  19. 1 0
      ApexDrivers/app/src/main/res/layout/update_photo_cell.xml
  20. 14 0
      ApexDrivers/app/src/main/res/menu/photo_preview_menu.xml
  21. 14 0
      ApexDrivers/app/src/main/res/menu/update_menu.xml
  22. 20 0
      ApexDrivers/app/src/main/res/values/attrs_xuanimageview.xml

+ 16 - 3
ApexDrivers/app/src/main/AndroidManifest.xml

@@ -2,9 +2,13 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
           package="com.usai.redant.apexdrivers">
 
-    <uses-permission android:name="android.permission.CAMERA" />
-    <uses-permission android:name="android.permission.WAKE_LOCK" />
-    <uses-permission android:name="android.permission.VIBRATE" />
+    <uses-permission android:name="android.permission.CAMERA"/>
+    <uses-permission android:name="android.permission.WAKE_LOCK"/>
+    <uses-permission android:name="android.permission.VIBRATE"/>
+    <uses-permission android:name="android.permission.FLASHLIGHT"/>
+    <!-- 调用硬件相机权限 -->
+    <uses-feature android:name="android.hardware.camera"/>
+    <uses-feature android:name="android.hardware.camera.autofocus"/>
 
     <application
         android:name=".ApexDriverApplication"
@@ -27,6 +31,15 @@
         </activity>
         <activity android:name=".Update.UpdateActivity">
         </activity>
+        <activity android:name=".camera.PreferencesActivity">
+        </activity>
+        <activity android:name=".CodeScanner.CaptureActivity">
+        </activity>
+        <activity android:name=".Update.PhotoPreviewActivity">
+        </activity>
+        <activity android:name=".Login.RetrievePasswordActivity">
+        </activity>
+
     </application>
 
 </manifest>

+ 26 - 0
ApexDrivers/app/src/main/java/com/usai/redant/apexdrivers/ApexDriverApplication.java

@@ -65,6 +65,32 @@ public class ApexDriverApplication extends Application {
         return p;
     }
 
+    public String encryptPassword() {
+         if (password != null) {
+             return AESUtil.encrypt(secretKey,password);
+         }
+         return null;
+    }
+
+    public String encryptUser() {
+        if (user != null) {
+            return AESUtil.encrypt(secretKey,user);
+        }
+        return null;
+    }
+
+    public void logout() {
+        user = null;
+        password = null;
+
+        SharedPreferences pref = ApexDriverApplication.sharedApplication().getSharedPreferences(ApexDriverApplication.preferencesKey, 0);
+        SharedPreferences.Editor editor = pref.edit();
+        editor.remove("user");
+        editor.remove("password");
+        editor.commit();
+
+    }
+
     public static ApexDriverApplication sharedApplication() {
         return mApp;
     }

+ 50 - 0
ApexDrivers/app/src/main/java/com/usai/redant/apexdrivers/Detail/DetailActivity.java

@@ -1,5 +1,6 @@
 package com.usai.redant.apexdrivers.Detail;
 
+import android.app.ProgressDialog;
 import android.content.Context;
 import android.content.Intent;
 import android.graphics.Color;
@@ -9,6 +10,7 @@ import android.os.Handler;
 import android.os.Message;
 import android.support.annotation.NonNull;
 import android.support.annotation.Nullable;
+import android.support.v7.app.AlertDialog;
 import android.support.v7.app.AppCompatActivity;
 import android.os.Bundle;
 import android.util.Log;
@@ -114,8 +116,38 @@ public class DetailActivity extends AppCompatActivity implements DetailAdapter.D
     /**
      * Data
      * */
+
+    private ProgressDialog mProgressDialog;
+    private void showProgressDialog() {
+        if (mProgressDialog == null) {
+            mProgressDialog = new ProgressDialog(this);
+            mProgressDialog.setMessage("loading...");
+            mProgressDialog.setCancelable(false);
+            mProgressDialog.show();
+        }
+    }
+
+    private void dismissProgressDialog() {
+        if (mProgressDialog != null && mProgressDialog.isShowing()) {
+            mProgressDialog.dismiss();
+        }
+        mProgressDialog = null;
+    }
+
+    private void showWarningMsg(String msg) {
+        if (msg == null || msg.length() == 0) {
+            return;
+        }
+
+        new AlertDialog.Builder(mCtx).setTitle("Warning")
+                .setMessage(msg)
+                .setPositiveButton("Ok",null)
+                .show();
+    }
+
     private void loadData() {
 
+        showProgressDialog();
         new Thread(new Runnable() {
             @Override
             public void run() {
@@ -133,6 +165,7 @@ public class DetailActivity extends AppCompatActivity implements DetailAdapter.D
 
     private void report(final String url, final Bundle params) {
 
+        showProgressDialog();
         new Thread(new Runnable() {
             @Override
             public void run() {
@@ -206,6 +239,7 @@ public class DetailActivity extends AppCompatActivity implements DetailAdapter.D
             super.handleMessage(msg);
 
             DetailActivity activity = mWeakDetail.get();
+            activity.dismissProgressDialog();
             switch (msg.what) {
                 case DetailActionReloadData: {
 
@@ -240,14 +274,23 @@ public class DetailActivity extends AppCompatActivity implements DetailAdapter.D
 
                             } else {
                                 // error
+                                String errMsg = json.optString("err_msg");
+                                if (errMsg == null || errMsg.length() == 0) {
+                                    errMsg = "Sorry,there is something wrong";
+                                }
+                                activity.showWarningMsg(errMsg);
                             }
 
                         } catch (JSONException e) {
                             e.printStackTrace();
                             // error
+                            String errMsg = "Sorry,there is something wrong";
+                            activity.showWarningMsg(errMsg);
                         }
                     } else {
                         // error
+                        String errMsg = "Sorry,there is something wrong";
+                        activity.showWarningMsg(errMsg);
                     }
                     /**
                      * 手动调用打开/关闭 Group,否则默认关闭
@@ -272,9 +315,16 @@ public class DetailActivity extends AppCompatActivity implements DetailAdapter.D
                             activity.startActivity(intent);
                         } else {
                             // error;
+                            String errMsg = json.optString("err_msg");
+                            if (errMsg == null || errMsg.length() == 0) {
+                                errMsg = "Sorry,there is something wrong";
+                            }
+                            activity.showWarningMsg(errMsg);
                         }
                     } else {
                         // error
+                        String errMsg = "Sorry,there is something wrong";
+                        activity.showWarningMsg(errMsg);
                     }
 
                 }

+ 66 - 0
ApexDrivers/app/src/main/java/com/usai/redant/apexdrivers/Home/HomeFragment.java

@@ -1,5 +1,6 @@
 package com.usai.redant.apexdrivers.Home;
 
+import android.app.ProgressDialog;
 import android.content.Context;
 import android.content.Intent;
 import android.os.Bundle;
@@ -8,6 +9,7 @@ import android.os.Message;
 import android.support.annotation.NonNull;
 import android.support.annotation.Nullable;
 import android.support.v4.app.Fragment;
+import android.support.v7.app.AlertDialog;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
@@ -100,8 +102,37 @@ public class HomeFragment extends Fragment {
         super.onSaveInstanceState(outState);
     }
 
+    private ProgressDialog mProgressDialog;
+    private void showProgressDialog() {
+        if (mProgressDialog == null) {
+            mProgressDialog = new ProgressDialog(mCtx);
+            mProgressDialog.setMessage("loading...");
+            mProgressDialog.setCancelable(false);
+            mProgressDialog.show();
+        }
+    }
+
+    private void dismissProgressDialog() {
+        if (mProgressDialog != null && mProgressDialog.isShowing()) {
+            mProgressDialog.dismiss();
+        }
+        mProgressDialog = null;
+    }
+
+    private void showWarningMsg(String msg) {
+        if (msg == null || msg.length() == 0) {
+            return;
+        }
+
+        new AlertDialog.Builder(mCtx).setTitle("Warning")
+                .setMessage(msg)
+                .setPositiveButton("Ok",null)
+                .show();
+    }
+
     private void loadData() {
 
+        showProgressDialog();
         new Thread(new Runnable() {
             @Override
             public void run() {
@@ -139,11 +170,40 @@ public class HomeFragment extends Fragment {
 
                         } else {
 
+                            // error
+                            Message msg = new Message();
+                            msg.what = HomeHandler.HomeActionError;
+
+                            String errMsg = json.optString("err_msg");
+                            if (errMsg == null || errMsg.length() == 0) {
+                                errMsg = "Sorry,there is something wrong";
+                            }
+                            msg.obj = errMsg;
+
+                            mHandler.sendMessage(msg);
+
                         }
 
                     } catch (JSONException e) {
                         e.printStackTrace();
+                        // error
+                        Message msg = new Message();
+                        msg.what = HomeHandler.HomeActionError;
+
+                        String errMsg = "Sorry,there is something wrong";
+                        msg.obj = errMsg;
+
+                        mHandler.sendMessage(msg);
                     }
+                } else {
+                    // error;
+                    Message msg = new Message();
+                    msg.what = HomeHandler.HomeActionError;
+
+                    String errMsg = "Sorry,there is something wrong";
+                    msg.obj = errMsg;
+
+                    mHandler.sendMessage(msg);
                 }
 
             }
@@ -155,6 +215,7 @@ public class HomeFragment extends Fragment {
     private static final class HomeHandler extends Handler {
 
         static final int HomeActionReloadData = 0;
+        static final int HomeActionError = 1;
 
         WeakReference<HomeFragment> mWeakHome;
 
@@ -167,6 +228,7 @@ public class HomeFragment extends Fragment {
             super.handleMessage(msg);
 
             HomeFragment fragment = mWeakHome.get();
+            fragment.dismissProgressDialog();
             switch (msg.what) {
                 case HomeActionReloadData: {
 
@@ -185,6 +247,10 @@ public class HomeFragment extends Fragment {
                     fragment.mAdapter.notifyDataSetChanged();
                 }
                 break;
+                case HomeActionError: {
+                    String errMsg = (String) msg.obj;
+                    fragment.showWarningMsg(errMsg);
+                }
                 default:{
 
                 }

+ 94 - 1
ApexDrivers/app/src/main/java/com/usai/redant/apexdrivers/MainActivity.java

@@ -1,13 +1,17 @@
 package com.usai.redant.apexdrivers;
 
+import android.app.ProgressDialog;
 import android.os.Bundle;
 import android.support.v4.app.FragmentManager;
 import android.support.v4.app.FragmentTransaction;
 import android.support.v7.app.AppCompatActivity;
+import android.view.Menu;
+import android.view.MenuItem;
 import android.widget.RelativeLayout;
 
 import com.usai.redant.apexdrivers.Home.HomeFragment;
 import com.usai.redant.apexdrivers.Login.LoginFragment;
+import com.usai.redant.apexdrivers.Network.Network;
 
 
 public class MainActivity extends AppCompatActivity implements LoginFragment.LoginCallBack {
@@ -41,8 +45,9 @@ public class MainActivity extends AppCompatActivity implements LoginFragment.Log
             loginFragment.setCallBack(this);
             transaction.add(R.id.root_container, loginFragment);
         }
-
         transaction.commit();
+
+        invalidateOptionsMenu();
     }
 
     @Override
@@ -56,5 +61,93 @@ public class MainActivity extends AppCompatActivity implements LoginFragment.Log
         transaction.replace(R.id.root_container,homeFragment);
 
         transaction.commit();
+
+        invalidateOptionsMenu();
+    }
+
+    @Override
+    public boolean onPrepareOptionsMenu(Menu menu) {
+        super.onPrepareOptionsMenu(menu);
+
+        menu.clear();
+
+        if (ApexDriverApplication.sharedApplication().isLogin()) {
+
+            menu.add(0, 0, 0, "Upload List");
+            menu.add(0, 1, 0, "Logout");
+
+        } else {
+
+        }
+
+        return true;
+    }
+
+    @Override
+    public boolean onOptionsItemSelected(MenuItem item) {
+
+        switch (item.getItemId()) {
+            case 0: {
+
+            }
+            break;
+            case 1: {
+                logout();
+            }
+            break;
+        }
+
+        return true;
+    }
+
+    private ProgressDialog mProgressDialog;
+    private void showProgressDialog() {
+        if (mProgressDialog == null) {
+            mProgressDialog = new ProgressDialog(this);
+            mProgressDialog.setMessage("loading...");
+            mProgressDialog.setCancelable(false);
+            mProgressDialog.show();
+        }
+    }
+
+    private void dismissProgressDialog() {
+        if (mProgressDialog != null && mProgressDialog.isShowing()) {
+            mProgressDialog.dismiss();
+        }
+        mProgressDialog = null;
+    }
+
+    private void logout() {
+
+        showProgressDialog();
+        new Thread(new Runnable() {
+            @Override
+            public void run() {
+                Network.logout();
+
+                runOnUiThread(new Runnable() {
+                    @Override
+                    public void run() {
+                        dismissProgressDialog();
+                        ApexDriverApplication.sharedApplication().logout();
+                        showLogin();
+                    }
+                });
+            }
+        }).start();
+    }
+
+    private void showLogin() {
+
+        FragmentManager manager = getSupportFragmentManager();
+        FragmentTransaction transaction = manager.beginTransaction();
+
+        LoginFragment loginFragment = new LoginFragment();
+        loginFragment.setCallBack(this);
+        transaction.replace(R.id.root_container, loginFragment);
+
+        transaction.commit();
+
+        invalidateOptionsMenu();
     }
 }

+ 88 - 0
ApexDrivers/app/src/main/java/com/usai/redant/apexdrivers/Network/Network.java

@@ -15,6 +15,8 @@ import java.io.BufferedReader;
 import java.io.InputStream;
 import java.io.InputStreamReader;
 
+import static java.lang.Thread.sleep;
+
 public class Network extends com.usai.redant.rautils.Utils.Network {
 
     public static String URL_HOST = "";
@@ -87,6 +89,11 @@ public class Network extends com.usai.redant.rautils.Utils.Network {
     public static JSONObject requestOrderList(Context context) {
 
         if (FAKE_DATA) {
+            try {
+                sleep(3000);
+            } catch (Exception e) {
+                e.printStackTrace();
+            }
             return loadFakeData(context, R.raw.fake_order_list);
         }
 
@@ -108,6 +115,11 @@ public class Network extends com.usai.redant.rautils.Utils.Network {
     public static JSONObject requestOrderDetail(Context context,String orderID) {
 
         if (FAKE_DATA) {
+            try {
+                sleep(3000);
+            } catch (Exception e) {
+                e.printStackTrace();
+            }
             return loadFakeData(context, R.raw.fake_order_detail);
         }
 
@@ -135,6 +147,11 @@ public class Network extends com.usai.redant.rautils.Utils.Network {
     public static JSONObject requestOrderUpdateInfo(Context context,String orderID, int actionID) {
 
         if (FAKE_DATA) {
+            try {
+                sleep(3000);
+            } catch (Exception e) {
+                e.printStackTrace();
+            }
             return loadFakeData(context, R.raw.fake_order_edit);
         }
 
@@ -162,6 +179,13 @@ public class Network extends com.usai.redant.rautils.Utils.Network {
     public static JSONObject report(String url, Bundle params) {
 
         if (FAKE_DATA) {
+
+            try {
+                sleep(3000);
+            } catch (Exception e) {
+                e.printStackTrace();
+            }
+
             try {
                 return new JSONObject("{\n" +
                         "  \"result\": 2\n" +
@@ -201,6 +225,59 @@ public class Network extends com.usai.redant.rautils.Utils.Network {
         }
     }
 
+    public static JSONObject submitUpdateParams(Bundle params) {
+
+        if (FAKE_DATA) {
+
+            try {
+                sleep(3000);
+            } catch (Exception e) {
+                e.printStackTrace();
+            }
+
+            return fakeError();
+        }
+
+        if (params == null) {
+            params = new Bundle();
+        }
+
+        String jsonStr = getJson(URL_HOST,params);
+        if (jsonStr == null || jsonStr.isEmpty()) {
+            return null;
+        }
+
+        try {
+            JSONObject json = new JSONObject(jsonStr);
+            return json;
+        } catch (JSONException e) {
+            e.printStackTrace();
+            return null;
+        }
+    }
+
+    public static JSONObject uploadFile(String filePath,Bundle params) {
+
+        return com.usai.redant.rautils.Utils.Network.uploadFileJSON(filePath,URL_HOST,params,null);
+    }
+
+    public static void logout() {
+
+        if (FAKE_DATA) {
+            try {
+                sleep(3000);
+            } catch (Exception e) {
+                e.printStackTrace();
+            }
+
+            return ;
+        }
+
+        Bundle params = new Bundle();
+
+        getJson(URL_HOST,params);
+    }
+
     private static JSONObject loadFakeData(Context context, int rawId) {
 
         try
@@ -225,4 +302,15 @@ public class Network extends com.usai.redant.rautils.Utils.Network {
         return null;
     }
 
+    private static JSONObject fakeError() {
+        try {
+            return new JSONObject("{\n" +
+                    "  \"result\": 0\n" +
+                    "}");
+        } catch (JSONException e) {
+            e.printStackTrace();
+            return null;
+        }
+    }
+
 }

+ 15 - 0
ApexDrivers/app/src/main/java/com/usai/redant/apexdrivers/Update/Model/UpdateBaseModel.java

@@ -1,7 +1,13 @@
 package com.usai.redant.apexdrivers.Update.Model;
 
+import java.lang.ref.WeakReference;
+
 public class UpdateBaseModel {
 
+    public interface UpdateModelDelegate {
+        void refresh();
+    }
+
     public final static int UpdateTypeLabel = 0;
     public final static int UpdateTypeInput = 1;
     public final static int UpdateTypeMultInput= 2;
@@ -11,4 +17,13 @@ public class UpdateBaseModel {
     public String key;
     public String title;
 
+    public WeakReference<UpdateModelDelegate> weakDelegate;
+    public void setDelegate(UpdateModelDelegate delegate) {
+        if (delegate == null) {
+            weakDelegate = null;
+        } else {
+            weakDelegate = new WeakReference<>(delegate);
+        }
+    }
+
 }

+ 7 - 0
ApexDrivers/app/src/main/java/com/usai/redant/apexdrivers/Update/Model/UpdateInputModel.java

@@ -5,4 +5,11 @@ public class UpdateInputModel extends UpdateBaseModel {
     public boolean scannable;
     public String value;
 
+    public void setScannerInputValue(String value) {
+        this.value = value;
+        if (weakDelegate != null) {
+            weakDelegate.get().refresh();
+        }
+    }
+
 }

+ 6 - 0
ApexDrivers/app/src/main/java/com/usai/redant/apexdrivers/Update/Model/UpdateMultInputModel.java

@@ -4,4 +4,10 @@ public class UpdateMultInputModel extends UpdateBaseModel {
 
     public String value;
 
+    public void setMultInputValue(String value) {
+        this.value = value;
+        if (weakDelegate != null) {
+            weakDelegate.get().refresh();
+        }
+    }
 }

+ 18 - 0
ApexDrivers/app/src/main/java/com/usai/redant/apexdrivers/Update/Model/UpdatePhotoModel.java

@@ -1,13 +1,31 @@
 package com.usai.redant.apexdrivers.Update.Model;
 
 import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
 
 public class UpdatePhotoModel extends UpdateBaseModel {
 
     public Bitmap photo;
+    public String photoPath;
 
     public String getPhotoName() {
         return key + ".jpg";
     }
 
+    public void setPhoto(Bitmap photo) {
+        this.photo = photo;
+        if (weakDelegate != null) {
+            weakDelegate.get().refresh();
+        }
+    }
+
+    public void setPhotoPath(String photoPath) {
+        this.photoPath = photoPath;
+        if (photoPath == null) {
+            setPhoto(null);
+        } else {
+            setPhoto(BitmapFactory.decodeFile(photoPath));
+        }
+    }
+
 }

+ 97 - 0
ApexDrivers/app/src/main/java/com/usai/redant/apexdrivers/Update/PhotoPreviewActivity.java

@@ -0,0 +1,97 @@
+package com.usai.redant.apexdrivers.Update;
+
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.support.v7.app.AppCompatActivity;
+import android.os.Bundle;
+import android.view.Menu;
+import android.view.MenuItem;
+
+import com.usai.redant.apexdrivers.R;
+import com.usai.redant.apexdrivers.Update.XuanImageView.XuanImageView;
+import com.usai.redant.apexdrivers.Update.XuanImageView.XuanImageViewSettings;
+import com.usai.redant.rautils.Utils.ImageUtil;
+
+import java.io.File;
+
+public class PhotoPreviewActivity extends AppCompatActivity {
+
+    public final static String PreviewActionDeleteKey = "delete";
+
+    private final static String PhotoPathKey = "PhotoPath";
+    public static Intent build(Context context,String photoPath) {
+        if (context == null) {
+            return null;
+        }
+        Intent intent = new Intent(context,PhotoPreviewActivity.class);
+        if (photoPath != null) {
+            intent.putExtra(PhotoPathKey,photoPath);
+        }
+        return intent;
+    }
+
+    private XuanImageView previewImageView;
+    private String photoPath;
+    private boolean delete;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_photo_preview);
+
+        android.support.v7.app.ActionBar actionBar = getSupportActionBar();
+        if(actionBar != null){
+            actionBar.setHomeButtonEnabled(true);
+            actionBar.setDisplayHomeAsUpEnabled(true);
+        }
+
+        previewImageView = findViewById(R.id.preview_image_view);
+        previewImageView.setAutoRotateCategory(XuanImageViewSettings.AUTO_ROTATE_CATEGORY_MAGNETISM);
+
+        photoPath = getIntent().getStringExtra(PhotoPathKey);
+        if (photoPath != null) {
+            Bitmap bitmap = BitmapFactory.decodeFile(photoPath);
+            previewImageView.setImageBitmap(bitmap);
+        }
+        delete = false;
+    }
+
+    @Override
+    public boolean onCreateOptionsMenu(Menu menu) {
+        getMenuInflater().inflate(R.menu.photo_preview_menu,menu);
+        return true;
+    }
+
+    @Override
+    public boolean onOptionsItemSelected(MenuItem item) {
+        if (item.getItemId() == R.id.delete_btn) {
+            if (photoPath != null) {
+                File file = new File(photoPath);
+                if (file.exists()) {
+                    file.delete();
+                    ImageUtil.updateGallery(this,photoPath);
+                }
+                delete = true;
+                finish();
+            }
+
+        }
+        if (item.getItemId() == android.R.id.home) {
+            finish();
+        }
+
+        return true;
+    }
+
+    @Override
+    public void finish() {
+
+        Intent intent = new Intent();
+        intent.putExtra(PreviewActionDeleteKey,delete);
+        setResult(RESULT_OK,intent);
+
+        super.finish();
+    }
+}

+ 464 - 4
ApexDrivers/app/src/main/java/com/usai/redant/apexdrivers/Update/UpdateActivity.java

@@ -1,27 +1,51 @@
 package com.usai.redant.apexdrivers.Update;
 
 import android.app.Activity;
+import android.app.AlertDialog;
+import android.app.ProgressDialog;
+import android.content.ContentValues;
 import android.content.Context;
+import android.content.DialogInterface;
 import android.content.Intent;
+import android.database.Cursor;
+import android.graphics.BitmapFactory;
+import android.net.Uri;
+import android.os.Environment;
 import android.os.Handler;
 import android.os.Message;
+import android.provider.MediaStore;
 import android.support.v7.app.AppCompatActivity;
 import android.os.Bundle;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.Menu;
 import android.view.MenuItem;
 import android.widget.ExpandableListView;
 
+import com.usai.redant.apexdrivers.ApexDriverApplication;
 import com.usai.redant.apexdrivers.CodeScanner.CaptureActivity;
+import com.usai.redant.apexdrivers.MainActivity;
 import com.usai.redant.apexdrivers.R;
+import com.usai.redant.apexdrivers.Update.Model.UpdateBaseModel;
 import com.usai.redant.apexdrivers.Update.Model.UpdateInputModel;
+import com.usai.redant.apexdrivers.Update.Model.UpdateMultInputModel;
 import com.usai.redant.apexdrivers.Update.Model.UpdatePhotoModel;
+import com.usai.redant.rautils.Utils.ImageUtil;
 import com.usai.redant.rautils.Utils.Network;
+import com.usai.redant.rautils.Utils.RAUtil;
 
 import org.json.JSONArray;
 import org.json.JSONException;
 import org.json.JSONObject;
 
+import java.io.File;
+import java.io.IOException;
 import java.lang.ref.WeakReference;
+import java.text.SimpleDateFormat;
 import java.util.ArrayList;
+import java.util.Date;
+
+import static com.usai.redant.rautils.Utils.Network.RESULT_TRUE;
 
 public class UpdateActivity extends AppCompatActivity implements UpdateAdapter.UpdateAdapterDelegate {
 
@@ -29,6 +53,11 @@ public class UpdateActivity extends AppCompatActivity implements UpdateAdapter.U
     private final static String ActionIDKey = "ActionID";
     private final static String ActionTitleKey = "ActionTitle";
 
+    private final static int REQUEST_SCANNER_CODE = 0;
+    private final static int REQUEST_CAMERA_CODE = 1;
+    private final static int REQUEST_PREVIEW_CODE = 2;
+
+
     public static Intent build(Context ctx, String orderID, int actionID,String actionTitle) {
 
         Intent intent = new Intent(ctx, UpdateActivity.class);
@@ -55,6 +84,14 @@ public class UpdateActivity extends AppCompatActivity implements UpdateAdapter.U
 
     private ExpandableListView mListView;
 
+    private UpdateInputModel mInputModel;
+    private UpdatePhotoModel mPhotoModel;
+
+    private File photoFile = null;
+    public String LastFileName = "";
+
+    private ProgressDialog mProgressDialog;
+
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
@@ -83,18 +120,120 @@ public class UpdateActivity extends AppCompatActivity implements UpdateAdapter.U
         loadData();
     }
 
+    @Override
+    protected void onSaveInstanceState(Bundle outState) {
+        super.onSaveInstanceState(outState);
+
+    }
+
     @Override
     public boolean onOptionsItemSelected(MenuItem item) {
         switch (item.getItemId()) {
             case android.R.id.home:
                 this.finish(); // back button
                 return true;
+            case R.id.update_btn: {
+                update();
+                return true;
+            }
         }
         return super.onOptionsItemSelected(item);
     }
 
+    @Override
+    public boolean onCreateOptionsMenu(Menu menu) {
+
+        getMenuInflater().inflate(R.menu.update_menu,menu);
+
+        return true;
+    }
+
+    @Override
+    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+        super.onActivityResult(requestCode, resultCode, data);
+
+        switch (requestCode) {
+            case REQUEST_SCANNER_CODE: {
+
+                if (resultCode == Activity.RESULT_OK) {
+                    Bundle bundle = data.getExtras();
+                    String pid = bundle.getString("pid");
+                    if (mInputModel != null) {
+                        mInputModel.setScannerInputValue(pid);
+                    }
+
+                }
+                mInputModel = null;
+            }
+            break;
+            case REQUEST_CAMERA_CODE: {
+
+                if (resultCode == Activity.RESULT_OK) {
+
+//                    File scaleFile = compressImageFile(photoFile);
+//                    if (scaleFile != null) {
+//                        photoFile = scaleFile;
+//                    }
+
+                    ImageUtil.updateGallery(mCtx,photoFile.toString());
+                    if (mPhotoModel != null) {
+                        mPhotoModel.setPhotoPath(photoFile.getAbsolutePath());
+                    }
+
+                }
+                mPhotoModel = null;
+                photoFile = null;
+            }
+            break;
+            case REQUEST_PREVIEW_CODE: {
+                if (resultCode == Activity.RESULT_OK) {
+
+                    boolean delete = data.getBooleanExtra(PhotoPreviewActivity.PreviewActionDeleteKey,false);
+                    if (delete) {
+                        mPhotoModel.setPhotoPath(null);
+                    }
+                }
+                mPhotoModel = null;
+            }
+            break;
+        }
+    }
+
+    /**
+     * data
+     * */
+
+    private void showProgressDialog() {
+        if (mProgressDialog == null) {
+            mProgressDialog = new ProgressDialog(this);
+            mProgressDialog.setMessage("loading...");
+            mProgressDialog.setCancelable(false);
+            mProgressDialog.show();
+        }
+    }
+
+    private void dismissProgressDialog() {
+        if (mProgressDialog != null && mProgressDialog.isShowing()) {
+            mProgressDialog.dismiss();
+        }
+        mProgressDialog = null;
+    }
+
+    private void showWarningMsg(String msg) {
+        if (msg == null || msg.length() == 0) {
+            return;
+        }
+
+        new android.support.v7.app.AlertDialog.Builder(mCtx).setTitle("Warning")
+                .setMessage(msg)
+                .setPositiveButton("Ok",null)
+                .show();
+    }
+
     private void loadData() {
 
+        showProgressDialog();
+
         new Thread(new Runnable() {
             @Override
             public void run() {
@@ -111,6 +250,210 @@ public class UpdateActivity extends AppCompatActivity implements UpdateAdapter.U
 
     }
 
+    private void update() {
+
+        showProgressDialog();
+
+        Bundle params = new Bundle();
+        final ArrayList<UpdatePhotoModel> photoArr = prepareParams(params);
+
+        final Bundle finalParams = params;
+        new Thread(new Runnable() {
+            @Override
+            public void run() {
+
+                final JSONObject json = com.usai.redant.apexdrivers.Network.Network.submitUpdateParams(finalParams);
+
+                runOnUiThread(new Runnable() {
+                    @Override
+                    public void run() {
+
+                        if (json != null) {
+                            final int result = json.optInt("result",0);
+                            if (result == RESULT_TRUE) {
+
+                                syncUploadPhotos(photoArr,json);
+
+                            } else {
+                                dismissProgressDialog();
+                                // error
+                                String errMsg = json.optString("err_msg");
+                                if (errMsg == null || errMsg.length() == 0) {
+                                    errMsg = "Sorry,there is something wrong";
+                                }
+                                showWarningMsg(errMsg);
+                            }
+                        } else {
+                            dismissProgressDialog();
+                            // error
+                            String errMsg = "Sorry,there is something wrong";
+                            showWarningMsg(errMsg);
+                        }
+                    }
+                });
+            }
+        }).start();
+    }
+
+    private void syncUploadPhotos(ArrayList<UpdatePhotoModel> photos, final JSONObject json) {
+
+        if (photos == null || (photos != null && photos.size() == 0) || json == null) {
+            return;
+        }
+
+        final ArrayList<UpdatePhotoModel> photoArr = photos;
+
+        new Thread(new Runnable() {
+            @Override
+            public void run() {
+                int retryCount = 0;
+                ArrayList<UpdatePhotoModel> failedArr = new ArrayList<>();
+                for (int i = 0; i < photoArr.size(); i++) {
+
+                    UpdatePhotoModel model = photoArr.get(i);
+                    String serial = json.optString(model.key);
+                    if (serial != null && serial.length() > 0) {
+
+                        Bundle fileParams = new Bundle();
+                        fileParams.putString("serial",serial);
+
+                        JSONObject jsonObj = com.usai.redant.apexdrivers.Network.Network.uploadFile(model.photoPath,fileParams);
+                        if (jsonObj != null) {
+                            int result = jsonObj.optInt("result",0);
+                            if (result != RESULT_TRUE) {
+                                i--;
+                                retryCount++;
+                                if (retryCount >= 3) {
+                                    failedArr.add(model);
+                                    break;
+                                }
+                            } else {
+                                retryCount = 0;
+                            }
+                        } else {
+                            // error
+                            i--;
+                            retryCount++;
+                            if (retryCount >= 3) {
+                                failedArr.add(model);
+                                break;
+                            }
+                        }
+                    }
+                }
+
+                final ArrayList<UpdatePhotoModel> uploadFaildArr = failedArr;
+                runOnUiThread(new Runnable() {
+                    @Override
+                    public void run() {
+                        dismissProgressDialog();
+                        if (uploadFaildArr.size() > 0) {
+
+                            new AlertDialog.Builder(mCtx)
+                                    .setTitle("Warning")
+                                    .setMessage("upload the photos failed,would you like to retry or do it background?")
+                                    .setPositiveButton("Background", new DialogInterface.OnClickListener() {
+                                        @Override
+                                        public void onClick(DialogInterface dialog, int which) {
+
+                                            backgroundUpload(uploadFaildArr,json);
+                                            goHome();
+                                        }
+                                    })
+                                    .setNegativeButton("Retry", new DialogInterface.OnClickListener() {
+                                        @Override
+                                        public void onClick(DialogInterface dialog, int which) {
+                                            syncUploadPhotos(uploadFaildArr,json);
+                                        }
+                                    })
+                                    .show();
+
+                        } else {
+
+                            goHome();
+                        }
+
+                    }
+                });
+            }
+        }).start();
+
+    }
+
+    private void goHome() {
+        Intent intent = new Intent(self, MainActivity.class);
+        self.startActivity(intent);
+    }
+
+    private void backgroundUpload(ArrayList<UpdatePhotoModel> photos, final JSONObject json) {
+
+    }
+
+    private ArrayList<UpdatePhotoModel> prepareParams(Bundle params) {
+
+        if (params == null) {
+            return null;
+        }
+        ArrayList<UpdatePhotoModel> photoArr = new ArrayList<>();
+
+        if (mOrderID != null) {
+            params.putString("orderID",mOrderID);
+        }
+        params.putInt("actionID",mActionID);
+
+        String user = ApexDriverApplication.sharedApplication().encryptUser();
+        String password = ApexDriverApplication.sharedApplication().encryptPassword();
+        if (user != null) {
+            params.putString("user",user);
+        }
+        if (password != null) {
+            params.putString("password",password);
+        }
+        params.putString("platform","android");
+//        params.putString("location",lat + "," + lon);
+
+        for (int i = 0; i < mSectionArray.size(); i++) {
+            UpdateSectionModel sectionModel = mSectionArray.get(i);
+            for (int j = 0; j < sectionModel.itemCount(); j++) {
+                UpdateBaseModel baseModel = sectionModel.itemModelForIndex(j);
+                switch (baseModel.type) {
+                    case UpdateBaseModel.UpdateTypeLabel: {
+
+                    }
+                    break;
+                    case UpdateBaseModel.UpdateTypeInput: {
+
+                        UpdateInputModel inputModel = (UpdateInputModel)baseModel;
+                        if (inputModel.key != null && inputModel.value != null && inputModel.value.length() > 0) {
+                            params.putString(inputModel.key,inputModel.value);
+                        }
+                    }
+                    break;
+                    case UpdateBaseModel.UpdateTypeMultInput: {
+
+                        UpdateMultInputModel multInputModel = (UpdateMultInputModel)baseModel;
+                        if (multInputModel.key != null && multInputModel.value != null && multInputModel.value.length() > 0) {
+                            params.putString(multInputModel.key,multInputModel.value);
+                        }
+                    }
+                    break;
+                    case UpdateBaseModel.UpdateTypePhoto: {
+
+                        UpdatePhotoModel photoModel = (UpdatePhotoModel)baseModel;
+                        if (photoModel.photoPath != null && photoModel.key != null) {
+                            params.putString(photoModel.key,photoModel.getPhotoName());
+
+                            photoArr.add(photoModel);
+                        }
+                    }
+                    break;
+                }
+            }
+        }
+
+        return photoArr;
+    }
+
     /**
      * Adapter Delegate
      * */
@@ -118,16 +461,122 @@ public class UpdateActivity extends AppCompatActivity implements UpdateAdapter.U
     @Override
     public void scannerButtonDidClick(UpdateInputModel model) {
 
-        Intent intent = new Intent();
+        if (model == null) {
+            return;
+        }
+        mInputModel = model;
 
+        Intent intent = new Intent();
         intent.setClass(mCtx, CaptureActivity.class);
-        startActivityForResult(intent, 0);
-
+        startActivityForResult(intent, REQUEST_SCANNER_CODE);
     }
 
     @Override
     public void photoButtonDidClick(UpdatePhotoModel model) {
 
+        if (model == null) {
+            return;
+        }
+
+        mPhotoModel = model;
+        if (model.photoPath == null) {
+
+            startCamera();
+        } else {
+
+            Intent intent = PhotoPreviewActivity.build(self,model.photoPath);
+            startActivityForResult(intent,REQUEST_PREVIEW_CODE);
+        }
+    }
+
+    /**
+     * Camera
+     * */
+    private void startCamera()
+    {
+
+        Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
+        // Ensure that there's a camera activity to handle the intent
+
+        if (takePictureIntent.resolveActivity(getPackageManager()) != null)
+        {
+            if (photoFile != null)
+                photoFile = null;
+            try
+            {
+                photoFile = createImageFile();
+            }
+            catch (IOException ex)
+            {
+                Log.d("Update Order", "StartCamera: " + ex);
+            }
+            // Continue only if the File was successfully created
+            if (photoFile != null)
+            {
+                takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, getImageContentUri(this,photoFile));
+                startActivityForResult(takePictureIntent, REQUEST_CAMERA_CODE);
+            }
+        }
+    }
+
+    private File createImageFile() throws IOException
+    {
+        // Create an image file name
+        String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmSS").format(new Date());
+        String imageFileName = "JPEG_" + timeStamp + "_";
+        File storageDir = new File(Environment.getExternalStorageDirectory().getPath() + "/redant/drivers/temp/");
+
+        File dir1 = new File(Environment.getExternalStorageDirectory().getPath() + "/redant");
+        File dir2 = new File(Environment.getExternalStorageDirectory().getPath() + "/redant/drivers");
+        File dir3 = new File(Environment.getExternalStorageDirectory().getPath() + "/redant/drivers/temp");
+
+
+        if (!dir1.exists())
+        {
+            boolean b = dir1.mkdir();
+        }
+        if (!dir2.exists())
+        {
+            boolean b = dir2.mkdir();
+        }
+        if (!dir3.exists())
+        {
+            boolean b = dir3.mkdir();
+        }
+
+        LastFileName = storageDir + imageFileName + ".jpg";
+
+        File image = File.createTempFile(imageFileName, /* prefix */
+                ".jpg", /* suffix */
+                storageDir /* directory */
+        );
+
+        return image;
+    }
+
+    private Uri getImageContentUri(Context context, File imageFile) {
+        String filePath = imageFile.getAbsolutePath();
+        Cursor cursor = context.getContentResolver().query(
+                MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
+                new String[] { MediaStore.Images.Media._ID },
+                MediaStore.Images.Media.DATA + "=? ",
+                new String[] { filePath }, null);
+
+        if (cursor != null && cursor.moveToFirst()) {
+            int id = cursor.getInt(cursor
+                    .getColumnIndex(MediaStore.MediaColumns._ID));
+            Uri baseUri = Uri.parse("content://media/external/images/media");
+            return Uri.withAppendedPath(baseUri, "" + id);
+        } else {
+            if (imageFile.exists()) {
+                ContentValues values = new ContentValues();
+                values.put(MediaStore.Images.Media.DATA, filePath);
+                return context.getContentResolver().insert(
+                        MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);
+            } else {
+                return null;
+            }
+        }
     }
 
     /**
@@ -150,6 +599,8 @@ public class UpdateActivity extends AppCompatActivity implements UpdateAdapter.U
             super.handleMessage(msg);
 
             UpdateActivity activity = mWeakActivity.get();
+            activity.dismissProgressDialog();
+
             switch (msg.what) {
                 case UpdateActionReloadData: {
 
@@ -159,7 +610,7 @@ public class UpdateActivity extends AppCompatActivity implements UpdateAdapter.U
                         try {
 
                             int restul = json.getInt("result");
-                            if (restul == Network.RESULT_TRUE) {
+                            if (restul == RESULT_TRUE) {
                                 activity.mJson = json;
                                 JSONArray sectionArr = json.optJSONArray("sections");
                                 if (sectionArr != null) {
@@ -179,14 +630,23 @@ public class UpdateActivity extends AppCompatActivity implements UpdateAdapter.U
 
                             } else {
                                 // error
+                                String errMsg = json.optString("err_msg");
+                                if (errMsg == null || errMsg.length() == 0) {
+                                    errMsg = "Sorry,there is something wrong";
+                                }
+                                activity.showWarningMsg(errMsg);
                             }
 
                         } catch (JSONException e) {
                             e.printStackTrace();
                             // error
+                            String errMsg = "Sorry,there is something wrong";
+                            activity.showWarningMsg(errMsg);
                         }
                     } else {
                         // error
+                        String errMsg = "Sorry,there is something wrong";
+                        activity.showWarningMsg(errMsg);
                     }
                     /**
                      * 手动调用打开/关闭 Group,否则默认关闭

+ 131 - 13
ApexDrivers/app/src/main/java/com/usai/redant/apexdrivers/Update/UpdateAdapter.java

@@ -1,6 +1,8 @@
 package com.usai.redant.apexdrivers.Update;
 
 import android.content.Context;
+import android.text.Editable;
+import android.text.TextWatcher;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
@@ -235,12 +237,13 @@ public class UpdateAdapter extends BaseExpandableListAdapter {
         }
     }
 
-    private class ScannerInputHolder {
+    private class ScannerInputHolder implements UpdateBaseModel.UpdateModelDelegate {
 
         TextView titleTv;
         EditText valueEt;
         ImageButton scannerBtn;
         WeakReference<UpdateInputModel> weakInput;
+        UpdateTextWatcher mTextWatcher = new UpdateTextWatcher();
 
         ScannerInputHolder(View view) {
 
@@ -257,41 +260,97 @@ public class UpdateAdapter extends BaseExpandableListAdapter {
                 }
             });
 
+            valueEt.addTextChangedListener(mTextWatcher);
+
             view.setTag(this);
         }
 
         public void bindModel(UpdateInputModel model) {
 
-            titleTv.setText(model.title);
-            valueEt.setText(model.value);
-            if (model.scannable) {
-                scannerBtn.setVisibility(View.VISIBLE);
+            // 释放旧的
+            if (weakInput != null) {
+                weakInput.get().setDelegate(null);
+            }
+
+            if (model != null) {
+                weakInput = new WeakReference<>(model);
+                weakInput.get().setDelegate(this);
             } else {
-                scannerBtn.setVisibility(View.GONE);
+                weakInput = null;
+            }
+            mTextWatcher.setInputModel(weakInput.get());
+
+            refresh();
+        }
+
+        @Override
+        public void refresh() {
+
+            if (weakInput != null) {
+                titleTv.setText(weakInput.get().title);
+                valueEt.setText(weakInput.get().value);
+                if (weakInput.get().scannable) {
+                    scannerBtn.setVisibility(View.VISIBLE);
+                } else {
+                    scannerBtn.setVisibility(View.GONE);
+                }
+            } else {
+                titleTv.setText(null);
+                valueEt.setText(null);
+                scannerBtn.setVisibility(View.VISIBLE);
             }
         }
     }
 
-    private class MultInputHolder {
+    private class MultInputHolder implements UpdateBaseModel.UpdateModelDelegate {
 
         TextView titleTv;
         EditText valueEt;
 
+        WeakReference<UpdateMultInputModel> weakInput;
+        UpdateTextWatcher mTextWatcher = new UpdateTextWatcher();
+
         MultInputHolder(View view) {
 
             titleTv = view.findViewById(R.id.update_multiple_input_title_tv);
             valueEt = view.findViewById(R.id.update_multiple_input_value_tv);
+            valueEt.addTextChangedListener(mTextWatcher);
+
             view.setTag(this);
         }
 
         public void bindModel(UpdateMultInputModel model) {
 
-            titleTv.setText(model.title);
-            valueEt.setText(model.value);
+            // 释放旧的
+            if (weakInput != null) {
+                weakInput.get().setDelegate(null);
+            }
+
+            if (model != null) {
+                weakInput = new WeakReference<>(model);
+                weakInput.get().setDelegate(this);
+            } else {
+                weakInput = null;
+            }
+            mTextWatcher.setInputModel(weakInput.get());
+
+            refresh();
+        }
+
+        @Override
+        public void refresh() {
+
+            if (weakInput != null) {
+                titleTv.setText(weakInput.get().title);
+                valueEt.setText(weakInput.get().value);
+            } else {
+                titleTv.setText(null);
+                valueEt.setText(null);
+            }
         }
     }
 
-    private class PhotoHolder {
+    private class PhotoHolder implements UpdateBaseModel.UpdateModelDelegate {
 
         TextView titleTv;
         ImageButton photoBtn;
@@ -313,10 +372,30 @@ public class UpdateAdapter extends BaseExpandableListAdapter {
         }
 
         public void bindModel(UpdatePhotoModel model) {
-            titleTv.setText(model.title);
-            photoBtn.setImageBitmap(model.photo);
 
-            weakPhoto = new WeakReference<>(model);
+            if (weakPhoto != null) {
+                weakPhoto.get().setDelegate(null);
+            }
+
+            if (model != null) {
+                weakPhoto = new WeakReference<>(model);
+                weakPhoto.get().setDelegate(this);
+            }
+
+            refresh();
+        }
+
+        @Override
+        public void refresh() {
+
+            if (weakPhoto != null) {
+                titleTv.setText(weakPhoto.get().title);
+                photoBtn.setImageBitmap(weakPhoto.get().photo);
+            } else {
+                titleTv.setText(null);
+                photoBtn.setImageBitmap(null);
+            }
+
         }
     }
 
@@ -324,4 +403,43 @@ public class UpdateAdapter extends BaseExpandableListAdapter {
         void scannerButtonDidClick(UpdateInputModel model);
         void photoButtonDidClick(UpdatePhotoModel model);
     }
+
+    public class UpdateTextWatcher implements TextWatcher {
+
+        private WeakReference<UpdateBaseModel> weakModel;
+
+        void setInputModel(UpdateBaseModel model) {
+            if (model != null) {
+                weakModel = new WeakReference<>(model);
+            } else {
+                weakModel = null;
+            }
+        }
+
+        @Override
+        public void beforeTextChanged(CharSequence s, int start, int count, int after) {
+
+        }
+
+        @Override
+        public void onTextChanged(CharSequence s, int start, int before, int count) {
+
+            if (weakModel.get() instanceof UpdateInputModel) {
+
+                UpdateInputModel inputModel = (UpdateInputModel)weakModel.get();
+                inputModel.value = s.toString();
+
+            } else if (weakModel.get() instanceof  UpdateMultInputModel) {
+
+                UpdateMultInputModel multInputModel = (UpdateMultInputModel) weakModel.get();
+                multInputModel.value = s.toString();
+            }
+
+        }
+
+        @Override
+        public void afterTextChanged(Editable s) {
+
+        }
+    }
 }

+ 202 - 0
ApexDrivers/app/src/main/java/com/usai/redant/apexdrivers/Update/XuanImageView/RotationGestureDetector.java

@@ -0,0 +1,202 @@
+package com.usai.redant.apexdrivers.Update.XuanImageView;
+
+import android.util.Log;
+import android.view.MotionEvent;
+
+/**
+ * Created by xuanyihuang on 9/4/16.
+ */
+
+public class RotationGestureDetector {
+    private int mXuanImageViewWith;
+    private static final int INVALID_POINTER_ID = -1;
+    private float sX, sY, fX, fY;
+    private float nfX, nfY, nsX, nsY;
+    private int ptrID1, ptrID2;
+    private int ptrID1_Index, ptrID2_Index, ptr_Index;
+    private float mAngle;
+    private float mPreviousAngle;
+    private float mAngleAtPresent;
+    private float mPreviousAngleAtPresent;
+    private float mPivotX;
+    private float mPivotY;
+    private boolean mIsRotated;
+    private int mPointerCount;
+    private float mBasicRotationTrigger;
+    private float mRotationTrigger;
+
+    private OnRotationGestureListener mListener;
+
+    public float getAngle() {
+        return mAngle;
+    }
+
+    public float getPreviousAngle(){
+        return  mPreviousAngle;
+    }
+
+    public float getPivotX(){
+        return mPivotX;
+    }
+
+    public float getPivotY(){
+        return mPivotY;
+    }
+
+    public void setAngle(float angle){
+        mAngle = angle;
+    }
+
+    public void setPreviousAngle(float angle){
+        mPreviousAngle = angle;
+    }
+
+    public boolean IsRotated(){
+        return  mIsRotated;
+    }
+
+    public RotationGestureDetector(OnRotationGestureListener listener, int XuanImageViewWidth){
+        mXuanImageViewWith = XuanImageViewWidth;
+        mListener = listener;
+        ptrID1 = INVALID_POINTER_ID;
+        ptrID2 = INVALID_POINTER_ID;
+        ptrID1_Index = INVALID_POINTER_ID;
+        ptrID2_Index = INVALID_POINTER_ID;
+        ptr_Index = INVALID_POINTER_ID;
+        mAngle = 0;
+        mPreviousAngle = 0;
+        mPivotX = 0;
+        mPivotY = 0;
+        mIsRotated = false;
+        mPointerCount = 0;
+        mBasicRotationTrigger = 10; //basic rotation trigger : 10 degrees
+        mRotationTrigger = mBasicRotationTrigger;
+    }
+
+    public boolean onTouchEvent(MotionEvent event){
+        Log.d("RotationGestureDetector", event + "");
+        switch (event.getAction() & MotionEvent.ACTION_MASK) {
+            case MotionEvent.ACTION_DOWN:
+                //ptrID1 : first finger pressing down
+                ptrID1 = event.getPointerId(event.getActionIndex());
+                break;
+            case MotionEvent.ACTION_POINTER_DOWN:
+                //ptrID2: second finger pressing down
+                mPointerCount = event.getPointerCount();
+                if(mPointerCount == 2) {
+                    ptrID2 = event.getPointerId(event.getActionIndex());
+                    ptrID1_Index = event.findPointerIndex(ptrID1);
+                    ptrID2_Index = event.findPointerIndex(ptrID2);
+                    try {
+                        sX = event.getX(ptrID1_Index);
+                        sY = event.getY(ptrID1_Index);
+                        fX = event.getX(ptrID2_Index);
+                        fY = event.getY(ptrID2_Index);
+                        determineRotationTrigger();
+                    }catch(Exception e){
+                        //pointer index out of range exception
+                        return true;
+                    }
+                }
+                break;
+            case MotionEvent.ACTION_MOVE:
+                Log.d("canStillRotate" , "" + canStillRotate());
+                if(canStillRotate()){
+                    ptrID1_Index = event.findPointerIndex(ptrID1);
+                    ptrID2_Index = event.findPointerIndex(ptrID2);
+                    try {
+                        nsX = event.getX(ptrID1_Index);
+                        nsY = event.getY(ptrID1_Index);
+                        nfX = event.getX(ptrID2_Index);
+                        nfY = event.getY(ptrID2_Index);
+                    } catch (Exception e){
+                        //pointer index out of range exception
+                        return true;
+                    }
+                    mPivotX = (nsX + nfX) / 2.0f;
+                    mPivotY = (nsY + nfY) / 2.0f;
+
+                    mAngleAtPresent = angleBetweenLines(fX, fY, sX, sY, nfX, nfY, nsX, nsY);
+
+                    if (mListener != null) {
+                        if(mIsRotated){
+                            mPreviousAngle = mAngle;
+                            mAngle = mPreviousAngle + (mAngleAtPresent - mPreviousAngleAtPresent);
+                            mPreviousAngleAtPresent = mAngleAtPresent;
+                            mListener.OnRotate(this);
+                        }
+                        else if(Math.abs(mAngleAtPresent) >= mRotationTrigger){
+                            sX = nsX;
+                            sY = nsY;
+                            fX = nfX;
+                            fY = nfY;
+                            mPreviousAngleAtPresent = 0;
+                            mIsRotated = true;
+                        }
+                    }
+                }
+                break;
+            case MotionEvent.ACTION_UP:
+                invalidateTouchPointers();
+                break;
+            case MotionEvent.ACTION_POINTER_UP:
+                ptr_Index = event.getActionIndex();
+                if(ptrID1 == event.getPointerId(ptr_Index) || ptrID2 == event.getPointerId(ptr_Index)){
+                    invalidateTouchPointers();
+                    if(mListener != null){
+                        if(mIsRotated) {
+                            mListener.StopRotate(this);
+                            mIsRotated = false;
+                        }
+                    }
+                }
+                break;
+            case MotionEvent.ACTION_CANCEL:
+                invalidateTouchPointers();
+                break;
+        }
+
+        return true;
+    }
+
+    private boolean canStillRotate() {
+        return (ptrID1 != INVALID_POINTER_ID) && (ptrID2 != INVALID_POINTER_ID) ;
+    }
+
+    private void invalidateTouchPointers(){
+        ptrID1 = INVALID_POINTER_ID;
+        ptrID2 = INVALID_POINTER_ID;
+    }
+
+    private float angleBetweenLines (float fX, float fY, float sX, float sY, float nfX, float nfY, float nsX, float nsY)
+    {
+        float angle1 = (float) Math.atan2( (fY - sY), (fX - sX) );
+        float angle2 = (float) Math.atan2( (nfY - nsY), (nfX - nsX) );
+
+        //angle range: [-180 degrees, +180 degrees]
+        float angle = ((float) Math.toDegrees(angle2 - angle1)) % 360;
+        if (angle < -180.f) angle += 360.0f;
+        if (angle > 180.f) angle -= 360.0f;
+
+        Log.d("AngleBetweenLines :","" + angle);
+        return angle;
+    }
+
+    private double lineDistance(float fX, float fY, float sX, float sY){
+        return  Math.sqrt((fX - sX) * (fX - sX) + (fY - sY) * (fY - sY));
+    }
+
+    private void determineRotationTrigger(){
+        // need further optimization
+        if(lineDistance(fX,fY,sX,sY) <= (mXuanImageViewWith / 3)){
+            mRotationTrigger = mBasicRotationTrigger * 2;
+        }
+        else
+            mRotationTrigger = mBasicRotationTrigger;
+    }
+
+    public interface OnRotationGestureListener {
+        boolean OnRotate(RotationGestureDetector rotationGestureDetector);
+        boolean StopRotate(RotationGestureDetector rotationGestureDetector);
+    }
+}

+ 1033 - 0
ApexDrivers/app/src/main/java/com/usai/redant/apexdrivers/Update/XuanImageView/XuanImageView.java

@@ -0,0 +1,1033 @@
+package com.usai.redant.apexdrivers.Update.XuanImageView;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Matrix;
+import android.graphics.RectF;
+import android.graphics.drawable.Drawable;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.GestureDetector;
+import android.view.MotionEvent;
+import android.view.ScaleGestureDetector;
+
+import com.usai.redant.apexdrivers.R;
+
+/**
+ * Created by xuanyihuang on 8/30/16.
+ */
+
+public class XuanImageView extends android.support.v7.widget.AppCompatImageView{
+    private int XuanImageViewWidth;
+    private int XuanImageViewHeight;
+    private int XuanImageViewCenterX;
+    private int XuanImageViewCenterY;
+    private int ImageCenterX;
+    private int ImageCenterY;
+    private int mOrientation;   //1 for portrait, 2 for landscape
+    private int mAutoRotateCategory;
+    private float mInitScale;   //for landscape
+    private float mInitPortraitScale;
+    private float mTempInitPortraitScale;
+    private float mMaxScale;
+    private float mPortraitMaxScale;
+    private float mTempPortraitMaxScale;
+    private boolean mRotationToggle;
+    private float mMaxScaleMultiple;
+    private float mDoubleTabScaleMultiple;
+    private float mDoubleTabScale;
+    private float mPortraitDoubleTabScale;
+    private float mTempPortraitDoubleTabScale;
+    private Matrix mScaleMatrix;
+    private ScaleGestureDetector mScaleGestureDetector;
+    private GestureDetector mGestureDetector;
+    private float mLastScaleFocusX;
+    private float mLastScaleFocusY;
+    private boolean isAutoScale;
+    private float mSpringBackGradientScaleUpLevel;
+    private float mSpringBackGradientScaleDownLevel;
+    private float mDoubleTapGradientScaleUpLevel;
+    private float mDoubleTapGradientScaleDownLevel;
+    private int mLastPointerCount;
+    private float mLastX;
+    private float mLastY;
+    private float mAngle;
+    private float mPreviousAngle;
+    private RotationGestureDetector mRotateGestureDetector;
+    private boolean isAutoRotated;
+    private double allowableFloatError;
+    private double allowablePortraitFloatError;
+    private float currentScaleLevel;
+    private float currentAbsScaleLevel;
+    private float autoRotationTrigger;
+    private int autoRotationRunnableDelay;
+    private int autoRotationRunnableTimes;
+    private int doubleTabScaleRunnableDelay;
+    private int springBackRunnableDelay;
+    private boolean hasDrawable;
+    private boolean knowViewSize;
+
+    public XuanImageView(Context context) {
+        this(context, null);
+    }
+
+    public XuanImageView(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public XuanImageView(Context context, AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+        initialize(context, attrs);
+    }
+
+    private void initialize(Context context, AttributeSet attrs) {
+        setScaleType(ScaleType.MATRIX);
+        mScaleMatrix = new Matrix();
+
+        mScaleGestureDetector = new ScaleGestureDetector(context, constructOnScaleGestureListener());
+        mGestureDetector = new GestureDetector(context, constructOnGestureListener());
+
+        initCustomAttrs(context, attrs);
+
+        mOrientation = XuanImageViewSettings.ORIENTATION_LANDSCAPE;   //1 for portrait, 2 for landscape
+        isAutoScale = false;
+        mLastPointerCount = 0;
+        mAngle = 0;
+        mPreviousAngle = 0;
+        currentScaleLevel = 1;
+    }
+
+    private ScaleGestureDetector.OnScaleGestureListener constructOnScaleGestureListener() {
+        return new ScaleGestureDetector.OnScaleGestureListener() {
+            @Override
+            public boolean onScale(ScaleGestureDetector scaleGestureDetector) {
+                float scaleFactor = scaleGestureDetector.getScaleFactor();
+
+                currentScaleLevel = getCurrentScaleLevel();
+                currentAbsScaleLevel = Math.abs(currentScaleLevel);
+                Log.d("CurrentAbsScaleLevel", "" + currentAbsScaleLevel);
+
+                boolean isRotating = false;
+                boolean justScale = false;
+
+                if(mRotateGestureDetector.IsRotated()){
+                    // is rotating
+                    isRotating = true;
+                }
+                else{
+                    // not rotating, just scaling
+                    if(mAutoRotateCategory == XuanImageViewSettings.AUTO_ROTATE_CATEGORY_RESTORATION){
+                        if((currentScaleLevel <= mMaxScale && scaleFactor > 1.0f) || (currentScaleLevel >= mInitScale && scaleFactor < 1.0f))
+                            justScale = true;
+
+                    }
+                    else if(mAutoRotateCategory == XuanImageViewSettings.AUTO_ROTATE_CATEGORY_MAGNETISM){
+                        if (mOrientation == XuanImageViewSettings.ORIENTATION_LANDSCAPE) {
+                            if ((currentAbsScaleLevel <= mMaxScale && scaleFactor > 1.0f) || (currentAbsScaleLevel >= mInitScale && scaleFactor < 1.0f))
+                                justScale = true;
+                        } else if (mOrientation == XuanImageViewSettings.ORIENTATION_PORTRAIT) {
+                            if ((currentAbsScaleLevel <= mTempPortraitMaxScale && scaleFactor > 1.0f) || (currentAbsScaleLevel >= mTempInitPortraitScale && scaleFactor < 1.0f))
+                                justScale = true;
+                        }
+                    }
+                }
+
+
+                if(isRotating) {
+                    mScaleMatrix.postScale(scaleFactor, scaleFactor, mRotateGestureDetector.getPivotX(), mRotateGestureDetector.getPivotY());
+                }
+                else if(justScale)
+                {
+                    mScaleMatrix.postScale(scaleFactor, scaleFactor, scaleGestureDetector.getFocusX(), scaleGestureDetector.getFocusY());
+                    checkBorderAndCenterWhenScale();
+                }
+
+
+                setImageMatrix(mScaleMatrix);
+                mLastScaleFocusX = scaleGestureDetector.getFocusX();
+                mLastScaleFocusY = scaleGestureDetector.getFocusY();
+
+                return true;
+            }
+
+            @Override
+            public boolean onScaleBegin(ScaleGestureDetector scaleGestureDetector) {
+                Log.d("onScaleBegin-->", "");
+                return true;
+            }
+
+            @Override
+            public void onScaleEnd(ScaleGestureDetector scaleGestureDetector) {
+                Log.d("onScaleEnd-->", "");
+            }
+        };
+    }
+
+    private GestureDetector.OnGestureListener constructOnGestureListener() {
+        return new GestureDetector.SimpleOnGestureListener() {
+            @Override
+            public boolean onDoubleTap(MotionEvent e) {
+                currentScaleLevel = getCurrentScaleLevel();
+                currentAbsScaleLevel = Math.abs(currentScaleLevel);
+                float x = e.getX();
+                float y = e.getY();
+
+                if (isAutoScale)
+                    return true;
+
+
+                if (mAutoRotateCategory == XuanImageViewSettings.AUTO_ROTATE_CATEGORY_RESTORATION) {
+                    if (currentScaleLevel < mDoubleTabScale) {
+                        postDelayed(new AutoScaleRunnable(mDoubleTabScale, x, y, mDoubleTapGradientScaleUpLevel, mDoubleTapGradientScaleDownLevel), doubleTabScaleRunnableDelay);
+                        isAutoScale = true;
+                    } else {
+                        postDelayed(new AutoScaleRunnable(mInitScale, x, y, mDoubleTapGradientScaleUpLevel, mDoubleTapGradientScaleDownLevel), doubleTabScaleRunnableDelay);
+                        isAutoScale = true;
+                    }
+                } else if (mAutoRotateCategory == XuanImageViewSettings.AUTO_ROTATE_CATEGORY_MAGNETISM) {
+                    if (mOrientation == XuanImageViewSettings.ORIENTATION_LANDSCAPE) {
+                        if (currentAbsScaleLevel < mDoubleTabScale) {
+                            postDelayed(new AutoScaleRunnable(mDoubleTabScale * (currentScaleLevel / currentAbsScaleLevel), x, y, mDoubleTapGradientScaleUpLevel, mDoubleTapGradientScaleDownLevel), doubleTabScaleRunnableDelay);
+                            isAutoScale = true;
+                        } else {
+                            postDelayed(new AutoScaleRunnable(mInitScale * (currentScaleLevel / currentAbsScaleLevel), x, y, mDoubleTapGradientScaleUpLevel, mDoubleTapGradientScaleDownLevel), doubleTabScaleRunnableDelay);
+                            isAutoScale = true;
+                        }
+                    } else if (mOrientation == XuanImageViewSettings.ORIENTATION_PORTRAIT) {
+                        if (currentAbsScaleLevel < mTempPortraitDoubleTabScale) {
+                            postDelayed(new AutoScaleRunnable(mTempPortraitDoubleTabScale * (currentScaleLevel / currentAbsScaleLevel), x, y, mDoubleTapGradientScaleUpLevel, mDoubleTapGradientScaleDownLevel), doubleTabScaleRunnableDelay);
+                            isAutoScale = true;
+                        } else {
+                            postDelayed(new AutoScaleRunnable(mTempInitPortraitScale * (currentScaleLevel / currentAbsScaleLevel), x, y, mDoubleTapGradientScaleUpLevel, mDoubleTapGradientScaleDownLevel), doubleTabScaleRunnableDelay);
+                            isAutoScale = true;
+                        }
+                    }
+                }
+
+                return true;
+            }
+        };
+    }
+
+
+    private RotationGestureDetector.OnRotationGestureListener constructOnRotationGestureListener(){
+        return new RotationGestureDetector.OnRotationGestureListener() {
+            @Override
+            public boolean OnRotate(RotationGestureDetector rotationGestureDetector) {
+                mAngle = rotationGestureDetector.getAngle();
+                mPreviousAngle = rotationGestureDetector.getPreviousAngle();
+                mScaleMatrix.postRotate(mAngle - mPreviousAngle, rotationGestureDetector.getPivotX(), rotationGestureDetector.getPivotY());
+
+                setImageMatrix(mScaleMatrix);
+
+                return true;
+            }
+
+            @Override
+            public boolean StopRotate(RotationGestureDetector rotationGestureDetector) {
+                if (mAutoRotateCategory == XuanImageViewSettings.AUTO_ROTATE_CATEGORY_RESTORATION)
+                    AutoRotateRestoration(rotationGestureDetector);
+                else if (mAutoRotateCategory == XuanImageViewSettings.AUTO_ROTATE_CATEGORY_MAGNETISM)
+                    AutoRotateMagnetism(rotationGestureDetector);
+
+                return true;
+            }
+        };
+    }
+
+    private void initCustomAttrs(Context context, AttributeSet attrs){
+
+        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.xuanimageview);
+        mRotationToggle = a.getBoolean(R.styleable.xuanimageview_RotationToggle, true);
+        mAutoRotateCategory = a.getInteger(R.styleable.xuanimageview_AutoRotateCategory, XuanImageViewSettings.AUTO_ROTATE_CATEGORY_RESTORATION);
+        mMaxScaleMultiple = a.getFloat(R.styleable.xuanimageview_MaxScaleMultiple, 4);
+        mDoubleTabScaleMultiple = a.getFloat(R.styleable.xuanimageview_DoubleTabScaleMultiple, 2);
+        mSpringBackGradientScaleUpLevel = a.getFloat(R.styleable.xuanimageview_SpringBackGradientScaleUpLevel, 1.01f);
+        mSpringBackGradientScaleDownLevel = a.getFloat(R.styleable.xuanimageview_SpringBackGradientScaleDownLevel, 0.99f);
+        mDoubleTapGradientScaleUpLevel = a.getFloat(R.styleable.xuanimageview_DoubleTapGradientScaleUpLevel, 1.05f);
+        mDoubleTapGradientScaleDownLevel = a.getFloat(R.styleable.xuanimageview_DoubleTapGradientScaleDownLevel, 0.95f);
+        autoRotationTrigger = a.getFloat(R.styleable.xuanimageview_AutoRotationTrigger, 60);
+        springBackRunnableDelay = a.getInteger(R.styleable.xuanimageview_SpringBackRunnableDelay, 10);
+        doubleTabScaleRunnableDelay = a.getInteger(R.styleable.xuanimageview_DoubleTapScaleRunnableDelay, 10);
+        autoRotationRunnableDelay = a.getInteger(R.styleable.xuanimageview_AutoRotationRunnableDelay, 5);
+        autoRotationRunnableTimes = a.getInteger(R.styleable.xuanimageview_AutoRotationRunnableTimes, 10);
+        try {
+            allowableFloatError = Double.parseDouble(a.getString(R.styleable.xuanimageview_AllowableFloatError));
+        }catch (Exception e){
+            allowableFloatError = 1E-6;   // for normal display aspect ratio
+//        allowableFloatError = 3E-3;   // for Galaxy S8
+        }
+        try {
+            allowablePortraitFloatError = Double.parseDouble(a.getString(R.styleable.xuanimageview_AllowablePortraitFloatError));
+        }catch (Exception e){
+            allowablePortraitFloatError = 1E-12;  // for normal display aspect ratio
+//        allowablePortraitFloatError = 5E-8;   //for Galaxy S8
+        }
+        a.recycle();
+    }
+
+    @Override
+    public void setImageDrawable(Drawable drawable) {
+        super.setImageDrawable(drawable);
+
+        if (drawable == null) {
+            hasDrawable = false;
+            return;
+        }
+
+        if (!drawableHasSize(drawable))
+            return;
+
+        hasDrawable = true;
+        initDrawableMatrix();
+    }
+
+    @Override
+    public void setImageResource(int resId) {
+        Drawable drawable = null;
+        try {
+            drawable = getResources().getDrawable(resId);
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+        setImageDrawable(drawable);
+    }
+
+    @Override
+    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
+        super.onSizeChanged(w, h, oldw, oldh);
+
+        knowViewSize = true;
+        initDrawableMatrix();
+    }
+
+    private boolean drawableHasSize(Drawable drawable) {
+        if ((drawable.getIntrinsicHeight() <= 0 || drawable.getIntrinsicWidth() <= 0)
+                && (drawable.getMinimumHeight() <= 0 || drawable.getMinimumWidth() <= 0)
+                && (drawable.getBounds().height() <= 0 || drawable.getBounds().width() <= 0))
+            return false;
+        else
+            return true;
+    }
+
+    private void initDrawableMatrix() {
+        if (!hasDrawable)
+            return;
+        if (!knowViewSize)
+            return;
+
+        if (mScaleMatrix == null)
+            mScaleMatrix = new Matrix();
+        else
+            mScaleMatrix.reset();
+
+        // get width and height of XuanImageView
+        XuanImageViewWidth = getWidth();
+        XuanImageViewHeight = getHeight();
+
+        //get the center point of XuanImageView
+        XuanImageViewCenterX = XuanImageViewWidth / 2;
+        XuanImageViewCenterY = XuanImageViewHeight / 2;
+
+        // instantiate mRotationGestureDetector after dimension of XuanImageView is got.
+        mRotateGestureDetector = new RotationGestureDetector(constructOnRotationGestureListener(), XuanImageViewWidth);
+
+        //get width and height of the image
+        Drawable imageDrawable = getDrawable();
+        if (imageDrawable == null)
+            return;
+        int imageWidth = imageDrawable.getIntrinsicWidth();
+        int imageHeight = imageDrawable.getIntrinsicHeight();
+
+        //image is scaled to fit the size of XuanImageView at the very beginning.
+        float scale = Math.min(XuanImageViewWidth * 1.0f / imageWidth, XuanImageViewHeight * 1.0f / imageHeight);
+        float portraitscale = Math.min(XuanImageViewWidth * 1.0f / imageHeight, XuanImageViewHeight * 1.0f / imageWidth);
+
+        mInitScale = scale;
+        mInitPortraitScale = portraitscale;
+        mMaxScale = mMaxScaleMultiple * scale;
+        mPortraitMaxScale = mMaxScaleMultiple * portraitscale;
+        mDoubleTabScale = mDoubleTabScaleMultiple * scale;
+        mPortraitDoubleTabScale = mDoubleTabScaleMultiple * portraitscale;
+
+        //center of image overlaps with that of XuanImageView
+        int deltaX = XuanImageViewWidth / 2 - imageWidth / 2;
+        int deltaY = XuanImageViewHeight / 2 - imageHeight / 2;
+
+        mScaleMatrix.postTranslate(deltaX, deltaY);
+        mScaleMatrix.postScale(scale, scale, XuanImageViewWidth / 2, XuanImageViewHeight / 2);
+        setImageMatrix(mScaleMatrix);
+    }
+
+    @Override
+    public boolean onTouchEvent(MotionEvent motionEvent) {
+        if (mRotateGestureDetector == null)
+            return true;
+
+        boolean parentDisallowInterceptTouchEventFlag = true;
+
+        // for DoubleTap gesture
+        mGestureDetector.onTouchEvent(motionEvent);
+
+        // for Scale gesture
+        mScaleGestureDetector.onTouchEvent(motionEvent);
+
+
+        currentScaleLevel = getCurrentScaleLevel();
+        currentAbsScaleLevel = Math.abs(currentScaleLevel);
+        if (mRotationToggle) {
+            if (mAutoRotateCategory == XuanImageViewSettings.AUTO_ROTATE_CATEGORY_RESTORATION) {
+                if (mRotateGestureDetector.IsRotated() || Math.abs(currentScaleLevel - mInitScale) < allowableFloatError
+                        || ((motionEvent.getAction() & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_UP)
+                        || ((motionEvent.getAction() & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_POINTER_UP)) {
+                    if (!mRotateGestureDetector.IsRotated())
+                        parentDisallowInterceptTouchEventFlag = false;
+
+                    // for Rotation gesture
+                    mRotateGestureDetector.onTouchEvent(motionEvent);
+                }
+            } else if (mAutoRotateCategory == XuanImageViewSettings.AUTO_ROTATE_CATEGORY_MAGNETISM) {
+                if (mOrientation == XuanImageViewSettings.ORIENTATION_LANDSCAPE) {
+                    if (mRotateGestureDetector.IsRotated() || Math.abs(currentAbsScaleLevel - mInitScale) < allowableFloatError
+                            || ((motionEvent.getAction() & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_UP)
+                            || ((motionEvent.getAction() & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_POINTER_UP)) {
+                        if (!mRotateGestureDetector.IsRotated())
+                            parentDisallowInterceptTouchEventFlag = false;
+
+                        // for Rotation gesture
+                        mRotateGestureDetector.onTouchEvent(motionEvent);
+
+                    }
+                } else if (mOrientation == XuanImageViewSettings.ORIENTATION_PORTRAIT) {
+                    if (mRotateGestureDetector.IsRotated() || Math.abs(currentAbsScaleLevel - mTempInitPortraitScale) < allowablePortraitFloatError
+                            || ((motionEvent.getAction() & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_UP)
+                            || ((motionEvent.getAction() & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_POINTER_UP)) {
+                        if (!mRotateGestureDetector.IsRotated())
+                            parentDisallowInterceptTouchEventFlag = false;
+
+                        // for Rotation gesture
+                        mRotateGestureDetector.onTouchEvent(motionEvent);
+                    }
+                }
+            }
+        }
+
+        //pointerCount won't be 0
+        int pointerCount = motionEvent.getPointerCount();
+        float pivotX = 0;
+        float pivotY = 0;
+        for (int i = 0; i < pointerCount; i++) {
+            pivotX += motionEvent.getX(i);
+            pivotY += motionEvent.getY(i);
+        }
+        pivotX /= pointerCount;   //get integer result of the division
+        pivotY /= pointerCount;
+
+        // when image is being dragged, generally, pointCount == mLastCount holds, so old state is not saved.
+        if (pointerCount != mLastPointerCount) {
+            mLastX = pivotX;
+            mLastY = pivotY;
+            mLastPointerCount = pointerCount;
+        }
+
+        RectF rectF = getMatrixRectF();
+
+        switch (motionEvent.getAction() & MotionEvent.ACTION_MASK) {
+            case MotionEvent.ACTION_DOWN:
+                if (getParent() != null)
+                    getParent().requestDisallowInterceptTouchEvent(parentDisallowInterceptTouchEventFlag);
+                break;
+            case MotionEvent.ACTION_MOVE:
+                if (getParent() != null)
+                    getParent().requestDisallowInterceptTouchEvent(parentDisallowInterceptTouchEventFlag);
+                float deltaX = pivotX - mLastX;
+                float deltaY = pivotY - mLastY;
+
+                if (getDrawable() != null) {
+                    if (!mRotateGestureDetector.IsRotated()) {
+                        if (rectF.width() <= XuanImageViewWidth)
+                            deltaX = 0;
+                        if (rectF.height() <= XuanImageViewHeight)
+                            deltaY = 0;
+                    }
+                    mScaleMatrix.postTranslate(deltaX, deltaY);
+
+                    if (!mRotateGestureDetector.IsRotated())
+                        checkBorderAndCenterWhenTranslate();
+                    setImageMatrix(mScaleMatrix);
+                }
+                mLastX = pivotX;
+                mLastY = pivotY;
+                break;
+            case MotionEvent.ACTION_POINTER_UP:
+                //one finger loosening (multi-touch)
+                if (pointerCount - 1 < 2) {
+                    if (mAutoRotateCategory == XuanImageViewSettings.AUTO_ROTATE_CATEGORY_RESTORATION) {
+                        //spring back to mInitScale or mMaxScale
+                        if ((currentScaleLevel < mInitScale) && !isAutoRotated) {
+                            postDelayed(new AutoScaleRunnable(mInitScale, mLastScaleFocusX, mLastScaleFocusY, mSpringBackGradientScaleUpLevel, mSpringBackGradientScaleDownLevel), springBackRunnableDelay);
+                            isAutoScale = true;
+                        } else if ((currentScaleLevel > mMaxScale) && !isAutoRotated) {
+                            postDelayed(new AutoScaleRunnable(mMaxScale, mLastScaleFocusX, mLastScaleFocusY, mSpringBackGradientScaleUpLevel, mSpringBackGradientScaleDownLevel), springBackRunnableDelay);
+                            isAutoScale = true;
+                        }
+                    } else if (mAutoRotateCategory == XuanImageViewSettings.AUTO_ROTATE_CATEGORY_MAGNETISM) {
+                        if (mOrientation == XuanImageViewSettings.ORIENTATION_LANDSCAPE) {
+                            if (currentAbsScaleLevel < mInitScale && !isAutoRotated) {
+                                postDelayed(new AutoScaleRunnable(mInitScale * (currentScaleLevel / currentAbsScaleLevel), mLastScaleFocusX, mLastScaleFocusY, mSpringBackGradientScaleUpLevel, mSpringBackGradientScaleDownLevel), springBackRunnableDelay);
+                                isAutoScale = true;
+                            } else if ((currentAbsScaleLevel > mMaxScale) && !isAutoRotated) {
+                                postDelayed(new AutoScaleRunnable(mMaxScale * (currentScaleLevel / currentAbsScaleLevel), mLastScaleFocusX, mLastScaleFocusY, mSpringBackGradientScaleUpLevel, mSpringBackGradientScaleDownLevel), springBackRunnableDelay);
+                                isAutoScale = true;
+                            }
+                        } else if (mOrientation == XuanImageViewSettings.ORIENTATION_PORTRAIT) {
+                            if (currentAbsScaleLevel < mTempInitPortraitScale && !isAutoRotated) {
+                                postDelayed(new AutoScaleRunnable(mTempInitPortraitScale * (currentScaleLevel / currentAbsScaleLevel), mLastScaleFocusX, mLastScaleFocusY, mSpringBackGradientScaleUpLevel, mSpringBackGradientScaleDownLevel), springBackRunnableDelay);
+                                isAutoScale = true;
+                            } else if ((currentAbsScaleLevel > mTempPortraitMaxScale) && !isAutoRotated) {
+                                postDelayed(new AutoScaleRunnable(mTempPortraitMaxScale * (currentScaleLevel / currentAbsScaleLevel), mLastScaleFocusX, mLastScaleFocusY, mSpringBackGradientScaleUpLevel, mSpringBackGradientScaleDownLevel), springBackRunnableDelay);
+                                isAutoScale = true;
+                            }
+                        }
+                    }
+                }
+                break;
+            case MotionEvent.ACTION_UP:
+                mLastPointerCount = 0;
+                break;
+            case MotionEvent.ACTION_CANCEL:
+                mLastPointerCount = 0;
+        }
+
+
+        return true;
+    }
+
+    private void checkBorderAndCenterWhenScale() {
+
+        RectF rectF = getMatrixRectF();
+        float deltaX = 0;
+        float deltaY = 0;
+
+        /**
+         * If width or height of image is bigger than that of XuanImageView,
+         * should prevent image's edge being far away from XuanImageView's edge.
+         */
+        if (rectF.width() >= XuanImageViewWidth) {
+            if (rectF.left > 0)
+                deltaX = -rectF.left;
+            if (rectF.right < XuanImageViewWidth)
+                deltaX = XuanImageViewWidth - rectF.right;
+
+        }
+        if (rectF.height() >= XuanImageViewHeight) {
+            if (rectF.top > 0)
+                deltaY = -rectF.top;
+            if (rectF.bottom < XuanImageViewHeight)
+                deltaY = XuanImageViewHeight - rectF.bottom;
+        }
+
+        /**
+         * If width or height of image is smaller than that of XuanImageView,
+         * make sure the image, in width dimension or height dimension, is centered.
+         */
+        if (rectF.width() < XuanImageViewWidth) {
+            deltaX = XuanImageViewWidth / 2.0f - rectF.left - rectF.width() / 2.0f;
+        }
+        if (rectF.height() < XuanImageViewHeight) {
+            deltaY = XuanImageViewHeight / 2.0f - rectF.top - rectF.height() / 2.0f;
+
+        }
+
+        mScaleMatrix.postTranslate(deltaX, deltaY);
+    }
+
+    private void checkBorderAndCenterWhenTranslate() {
+        /**
+         *  No need to check Center here because it has been handled before checkBorderAndCenterWhenTranslate() is invoked.
+         *  See "case MotionEvent.ACTION_MOVE: " in  onTouch(View view, MotionEvent motionEvent).
+         */
+        RectF rectF = getMatrixRectF();
+
+        float deltaX = 0;
+        float deltaY = 0;
+
+        /**
+         * If width or height of image is bigger than that of XuanImageView,
+         * should prevent image's edge being far away from XuanImageView's edge.
+         */
+        if (rectF.width() >= XuanImageViewWidth) {
+            if (rectF.left > 0)
+                deltaX = -rectF.left;
+            if (rectF.right < XuanImageViewWidth)
+                deltaX = XuanImageViewWidth - rectF.right;
+
+        }
+        if (rectF.height() >= XuanImageViewHeight) {
+            if (rectF.top > 0)
+                deltaY = -rectF.top;
+            if (rectF.bottom < XuanImageViewHeight)
+                deltaY = XuanImageViewHeight - rectF.bottom;
+        }
+
+        mScaleMatrix.postTranslate(deltaX, deltaY);
+    }
+
+
+    private float getCurrentScaleLevel() {
+        float matrixArray[] = new float[9];
+        mScaleMatrix.getValues(matrixArray);
+
+        return matrixArray[Matrix.MSCALE_X];
+    }
+
+    private RectF getMatrixRectF() {
+        Matrix matrix = mScaleMatrix;
+        RectF rectF = new RectF();
+        Drawable image = getDrawable();
+        if (image != null) {
+            rectF.set(0, 0, image.getIntrinsicWidth(), image.getIntrinsicHeight());
+            matrix.mapRect(rectF);
+        }
+
+        return rectF;
+    }
+
+    private boolean calculateImageCenterCoordinates() {
+        RectF rectF = getMatrixRectF();
+        ImageCenterX = (int) ((rectF.left + rectF.right) / 2);
+        ImageCenterY = (int) ((rectF.top + rectF.bottom) / 2);
+
+        return true;
+    }
+
+    public void AutoRotateMagnetism(RotationGestureDetector rotationGestureDetector) {
+        mAngle = rotationGestureDetector.getAngle();//mAngle's range: [-180 degress, 180 degress]
+        float autoRotateAngle;
+        int quotient;
+        float remainder;
+
+        quotient = ((int) mAngle) / 90;
+        remainder = mAngle % 90;
+
+        if (remainder >= autoRotationTrigger) {
+            autoRotateAngle = 90 - remainder;
+            if ((quotient + 1) % 2 == 0)
+                mOrientation = XuanImageViewSettings.ORIENTATION_LANDSCAPE;
+            else
+                mOrientation = XuanImageViewSettings.ORIENTATION_PORTRAIT;
+        } else if (remainder <= -autoRotationTrigger) {
+            autoRotateAngle = -90 - remainder;
+            if ((quotient - 1) % 2 == 0)
+                mOrientation = XuanImageViewSettings.ORIENTATION_LANDSCAPE;
+            else
+                mOrientation = XuanImageViewSettings.ORIENTATION_PORTRAIT;
+        } else {
+            autoRotateAngle = -remainder;
+            if (quotient % 2 == 0)
+                mOrientation = XuanImageViewSettings.ORIENTATION_LANDSCAPE;
+            else
+                mOrientation = XuanImageViewSettings.ORIENTATION_PORTRAIT;
+        }
+
+        postDelayed(new AutoRotateRunnable(autoRotateAngle, getCurrentScaleLevel() / (float) Math.cos(Math.toRadians(mAngle)), autoRotationRunnableTimes), autoRotationRunnableDelay);
+        isAutoRotated = true;
+
+        rotationGestureDetector.setAngle(mAngle + autoRotateAngle);
+        rotationGestureDetector.setPreviousAngle(mAngle + autoRotateAngle);
+    }
+
+    public void AutoRotateRestoration(RotationGestureDetector rotationGestureDetector) {
+        mAngle = rotationGestureDetector.getAngle();//mAngle's range: [-180 degress, 180 degress]
+        float autoRotateAngle;
+
+        if (mAngle >= autoRotationTrigger)
+            autoRotateAngle = 360 - mAngle;
+        else if (mAngle <= -autoRotationTrigger)
+            autoRotateAngle = -360 - mAngle;
+        else
+            autoRotateAngle = -mAngle;
+
+        postDelayed(new AutoRotateRunnable(autoRotateAngle, getCurrentScaleLevel() / (float) Math.cos(Math.toRadians(mAngle)), autoRotationRunnableTimes), autoRotationRunnableDelay);
+        isAutoRotated = true;
+
+        rotationGestureDetector.setAngle(0.0f);
+        rotationGestureDetector.setPreviousAngle(0.0f);
+    }
+
+    private class AutoScaleRunnable implements Runnable {
+        float targetScale;
+        float targetAbsScale;
+        float FocusX;
+        float FocusY;
+        float scaleFactor;
+
+        AutoScaleRunnable(float targetScale, float FocusX, float FocusY, float GradientScaleUp, float GradientScaleDown) {
+            this.targetScale = targetScale;
+            this.targetAbsScale = Math.abs(targetScale);
+            this.FocusX = FocusX;
+            this.FocusY = FocusY;
+            currentScaleLevel = getCurrentScaleLevel();
+            currentAbsScaleLevel = Math.abs(currentScaleLevel);
+            if (currentAbsScaleLevel < targetAbsScale)
+                scaleFactor = GradientScaleUp;
+            else if (currentAbsScaleLevel > targetAbsScale)
+                scaleFactor = GradientScaleDown;
+            else if (currentAbsScaleLevel == targetAbsScale)
+                scaleFactor = 1.0f;
+        }
+
+        @Override
+        public void run() {
+            mScaleMatrix.postScale(scaleFactor, scaleFactor, FocusX, FocusY);
+            checkBorderAndCenterWhenScale();
+            setImageMatrix(mScaleMatrix);
+
+            currentScaleLevel = getCurrentScaleLevel();
+            currentAbsScaleLevel = Math.abs(currentScaleLevel);
+            if ((scaleFactor < 1.0f && currentAbsScaleLevel > targetAbsScale)
+                    || (scaleFactor > 1.0f && currentAbsScaleLevel < targetAbsScale)) {
+                postDelayed(this, springBackRunnableDelay);
+            } else {
+                scaleFactor = targetScale / currentScaleLevel;
+
+                mScaleMatrix.postScale(scaleFactor, scaleFactor, FocusX, FocusY);
+                checkBorderAndCenterWhenScale();
+                setImageMatrix(mScaleMatrix);
+
+                isAutoScale = false;
+            }
+
+        }
+    }
+
+
+    private class AutoRotateRunnable implements Runnable {
+        float targetRotateAngle;
+        long TotalRotateTimes;
+        float AnglePerTime;
+        float AccumulativeRotateTimes;
+        float AccumulativeRotateAngles;
+        float initScaleLevel;
+        double ScalePerTime;
+
+        AutoRotateRunnable(float targetRotateAngle, float initScaleLevel, long TotalRotateTimes) {
+            this.targetRotateAngle = targetRotateAngle;
+            this.TotalRotateTimes = TotalRotateTimes;
+            AnglePerTime = targetRotateAngle / this.TotalRotateTimes;
+            AccumulativeRotateTimes = 0;
+            AccumulativeRotateAngles = 0.0f;
+            this.initScaleLevel = initScaleLevel;
+            if (mAutoRotateCategory == XuanImageViewSettings.AUTO_ROTATE_CATEGORY_RESTORATION) {
+                ScalePerTime = Math.pow(mInitScale / initScaleLevel, 1.0 / TotalRotateTimes);
+            } else if (mAutoRotateCategory == XuanImageViewSettings.AUTO_ROTATE_CATEGORY_MAGNETISM) {
+                if (mOrientation == XuanImageViewSettings.ORIENTATION_LANDSCAPE)
+                    ScalePerTime = Math.pow(mInitScale / initScaleLevel, 1.0 / TotalRotateTimes);
+                else if (mOrientation == XuanImageViewSettings.ORIENTATION_PORTRAIT)
+                    ScalePerTime = Math.pow(mInitPortraitScale / initScaleLevel, 1.0 / TotalRotateTimes);
+            }
+
+        }
+
+        @Override
+        public void run() {
+            calculateImageCenterCoordinates();
+
+            mScaleMatrix.postRotate(AnglePerTime, ImageCenterX, ImageCenterY);
+            mScaleMatrix.postScale((float) ScalePerTime, (float) ScalePerTime, ImageCenterX, ImageCenterY);
+            mScaleMatrix.postTranslate((XuanImageViewCenterX - ImageCenterX) / (TotalRotateTimes - AccumulativeRotateTimes), (XuanImageViewCenterY - ImageCenterY) / (TotalRotateTimes - AccumulativeRotateTimes));
+            setImageMatrix(mScaleMatrix);
+            AccumulativeRotateTimes++;
+            AccumulativeRotateAngles += AnglePerTime;
+
+
+            if (AccumulativeRotateTimes < TotalRotateTimes) {
+                postDelayed(this, autoRotationRunnableDelay);
+            } else {
+                currentScaleLevel = getCurrentScaleLevel();
+                currentAbsScaleLevel = Math.abs(currentScaleLevel);
+                calculateImageCenterCoordinates();
+                mScaleMatrix.postRotate(targetRotateAngle - AccumulativeRotateAngles, ImageCenterX, ImageCenterY);
+                if (mAutoRotateCategory == XuanImageViewSettings.AUTO_ROTATE_CATEGORY_RESTORATION) {
+                    mScaleMatrix.postScale(Math.abs(mInitScale / currentScaleLevel), Math.abs(mInitScale / currentScaleLevel), ImageCenterX, ImageCenterY);
+                } else if (mAutoRotateCategory == XuanImageViewSettings.AUTO_ROTATE_CATEGORY_MAGNETISM) {
+                    if (mOrientation == XuanImageViewSettings.ORIENTATION_LANDSCAPE) {
+                        mScaleMatrix.postScale(Math.abs(mInitScale / currentScaleLevel), Math.abs(mInitScale / currentScaleLevel), ImageCenterX, ImageCenterY);
+                    } else if (mOrientation == XuanImageViewSettings.ORIENTATION_PORTRAIT) {
+//                        mScaleMatrix.postScale(Math.abs(mInitPortraitScale / getCurrentScaleLevel()), Math.abs(mInitPortraitScale / getCurrentScaleLevel()), ImageCenterX, ImageCenterY);
+                        mTempInitPortraitScale = Math.abs(currentScaleLevel);
+                        mTempPortraitMaxScale = mTempInitPortraitScale * mMaxScaleMultiple;
+                        mTempPortraitDoubleTabScale = mTempInitPortraitScale * mDoubleTabScaleMultiple;
+                    }
+                }
+                mScaleMatrix.postTranslate(XuanImageViewCenterX - ImageCenterX, XuanImageViewCenterY - ImageCenterY);
+
+                checkBorderAndCenterWhenScale();
+
+                setImageMatrix(mScaleMatrix);
+                isAutoRotated = false;
+            }
+
+
+        }
+    }
+
+    /**
+     * Set a boolean value to determine whether rotation function is turned on.
+     *
+     * @param toggle determine whether rotation function is turned on
+     */
+    public void setRotationToggle(boolean toggle) {
+        mRotationToggle = toggle;
+    }
+
+    /**
+     * @return current RotationToggle
+     */
+    public boolean getRotationToggle() {
+        return mRotationToggle;
+    }
+
+    /**
+     * Set AutoRotateCategory, there are two alternative values of it : XuanImageViewSettings.AUTO_ROTATE_CATEGORY_RESTORATION, XuanImageViewSettings.AUTO_ROTATE_CATEGORY_MAGNETISM.
+     *
+     * @param category
+     */
+    public void setAutoRotateCategory(int category) {
+        if (category == XuanImageViewSettings.AUTO_ROTATE_CATEGORY_RESTORATION || category == XuanImageViewSettings.AUTO_ROTATE_CATEGORY_MAGNETISM)
+            mAutoRotateCategory = category;
+        else
+            mAutoRotateCategory = XuanImageViewSettings.AUTO_ROTATE_CATEGORY_RESTORATION;
+    }
+
+    /**
+     * @return current AutoRotateCategory
+     */
+    public int getAutoRotateCategory() {
+        return mAutoRotateCategory;
+    }
+
+    /**
+     * An image is scaled to an InitScale to fit the size of XuanImageView at the very beginning.
+     * MaxScale = MaxScaleMultiple * InitScale holds.
+     *
+     * @param maxScaleMultiple
+     */
+    public void setMaxScaleMultiple(float maxScaleMultiple) {
+        mMaxScaleMultiple = maxScaleMultiple;
+    }
+
+    /**
+     * @return current MaxScaleMultiple
+     */
+    public float getMaxScaleMultiple() {
+        return mMaxScaleMultiple;
+    }
+
+    /**
+     * When image's current scale level is smaller than DoubleTabScale, the image will scale up to DoubleTapScale if an double-tap gesture is detected.
+     * DoubleTapScale = DoubleTabScaleMultiple * InitScale holds.
+     *
+     * @param doubleTabScaleMultiple
+     */
+    public void setDoubleTabScaleMultiple(float doubleTabScaleMultiple) {
+        mDoubleTabScaleMultiple = doubleTabScaleMultiple;
+    }
+
+    /**
+     * @return current DoubleTabScaleMultiple
+     */
+    public float getDoubleTabScaleMultiple() {
+        return mDoubleTabScaleMultiple;
+    }
+
+    /**
+     * If current scale level is smaller than InitScale and image is not in rotation state,
+     * the image will scale up to InitScale with SpringBackGradientScaleUpLevel step by step.
+     * Default springBackGradientScaleUpLevel is  1.01f.
+     *
+     * @param springBackGradientScaleUpLevel
+     */
+    public void setSpringBackGradientScaleUpLevel(float springBackGradientScaleUpLevel) {
+        mSpringBackGradientScaleUpLevel = springBackGradientScaleUpLevel;
+    }
+
+    /**
+     * @return current SpringBackGradientScaleUpLevel
+     */
+    public float getSpringBackGradientScaleUpLevel() {
+        return mSpringBackGradientScaleUpLevel;
+    }
+
+    /**
+     * If current scale level is bigger than MaxScale and image is not in rotation state,
+     * the image will scale down to MaxScale with SpringBackGradientScaleDownLevel step by step.
+     * Default springBackGradientScaleDownLevel is 0.99f.
+     *
+     * @param springBackGradientScaleDownLevel
+     */
+    public void setSpringBackGradientScaleDownLevel(float springBackGradientScaleDownLevel) {
+        mSpringBackGradientScaleDownLevel = springBackGradientScaleDownLevel;
+    }
+
+    /**
+     * @return current SpringBackGradientScaleDownLevel
+     */
+    public float getSpringBackGradientScaleDownLevel() {
+        return mSpringBackGradientScaleDownLevel;
+    }
+
+    /**
+     * When image's current scale level is smaller than DoubleTabScale,
+     * the image will scale up to DoubleTapScale with DoubleTapGradientScaleUpLevel step by step if a double-tap gesture is detected.
+     * Default doubleTalGradientScaleUpLevel is 1.05f.
+     *
+     * @param doubleTapGradientScaleUpLevel
+     */
+    public void setDoubleTapGradientScaleUpLevel(float doubleTapGradientScaleUpLevel) {
+        mDoubleTapGradientScaleUpLevel = doubleTapGradientScaleUpLevel;
+    }
+
+    /**
+     * @return current DoubleTapGradientScaleUpLevel
+     */
+    public float getDoubleTapGradientScaleUpLevel() {
+        return mDoubleTapGradientScaleUpLevel;
+    }
+
+    /**
+     * When image's current scale level is bigger than DoubleTabScale,
+     * the image will scale down to InitScale with DoubleTapGradientScaleDownLevel step by step if a double-tap gesture is detected.
+     * Default doubleTabGradientScaleDownLevel is 0.95f.
+     *
+     * @param doubleTapGradientScaleDownLevel
+     */
+    public void setDoubleTabGradientScaleDownLevel(float doubleTapGradientScaleDownLevel) {
+        mDoubleTapGradientScaleDownLevel = doubleTapGradientScaleDownLevel;
+    }
+
+    /**
+     * @return current DoubleTapGradientScaleDownLevel
+     */
+    public float getDoubleTapGradientScaleDownLevel() {
+        return mDoubleTapGradientScaleDownLevel;
+    }
+
+    /**
+     * When image's current rotation angle is bigger than AutoRotationTrigger, the image will rotate in the same direction and scale back to it's initial state if rotation gesture is released.
+     * When image's current rotation angle is smaller than AutoRotationTrigger, the image will rotate in the opposite direction and scale back to it's initial state if rotation gesture is released.
+     * Default AutoRotationTrigger is 60 (degrees).
+     *
+     * @param autoRotationTrigger
+     */
+    public void setAutoRotationTrigger(float autoRotationTrigger) {
+        this.autoRotationTrigger = autoRotationTrigger;
+    }
+
+    /**
+     * @return current AutoRotationTrigger
+     */
+    public float getAutoRotationTrigger() {
+        return autoRotationTrigger;
+    }
+
+    /**
+     * Default SpringBackRunnableDelay is 10 (milliseconds).
+     *
+     * @param delay
+     */
+    public void setSpringBackRunnableDelay(int delay) {
+        springBackRunnableDelay = delay;
+    }
+
+    /**
+     * @return current SpringBackRunnableDelay
+     */
+    public int getSpringBackRunnableDelay() {
+        return springBackRunnableDelay;
+    }
+
+    /**
+     * Default DoubleTapRunnableDelay is 10 (milliseconds).
+     *
+     * @param delay
+     */
+    public void setDoubleTapScaleRunnableDelay(int delay) {
+        doubleTabScaleRunnableDelay = delay;
+    }
+
+    /**
+     * @return current DoubleTabScaleRunnableDelay
+     */
+    public int getDoubleTabScaleRunnableDelay() {
+        return doubleTabScaleRunnableDelay;
+    }
+
+    /**
+     * Default AutoRotationRunnableDelay is 5 (milliseconds).
+     *
+     * @param delay
+     */
+    public void setAutoRotationRunnableDelay(int delay) {
+        autoRotationRunnableDelay = delay;
+    }
+
+    /**
+     * @return current AutoRotationRunnableDelay
+     */
+    public int getAutoRotationRunnalbleDelay() {
+        return autoRotationRunnableDelay;
+    }
+
+    /**
+     * Default AutoRotationRunnableTimes is 10 (times).
+     *
+     * @param times
+     */
+    public void setAutoRotationRunnableTimes(int times) {
+        autoRotationRunnableTimes = times;
+    }
+
+    /**
+     * @return current AutoRotationRunnableTimes
+     */
+    public int getAutoRotationRunnableTimes() {
+        return autoRotationRunnableTimes;
+    }
+
+
+    /**
+     * Notice the Image can only start to be rotated when it's in initial state. But the image may be scaled up or down a little bit by ScaleGestureDetector
+     * in advance when you try to rotate it, hence, currentScaleLevel is not precisely equal to initScaleLevel. Here, an AllowableFloatError is existed to handle
+     * this situation. When Math.abs(currentScaleLevel - initScaleLevel) is smaller than allowableFloatError, RotateGestureDetector.onTouchEvent() can be invoked.
+     * Default allowableFloatError is 1E-6, it should be compatible with most of devices. For devices whose display resolution and aspect ratio is not normal, allowableFloatError may
+     * need to be tuned. eg., for Galaxy S8, 3E-3 works well. Of course, 3E-3 also works for most of devices because 1E-6 is smaller than 3E-3.
+     * @param allowableFloatError
+     */
+    public void setAllowableFloatError(double allowableFloatError){
+        this.allowableFloatError = allowableFloatError;
+    }
+
+    public double getAllowableFloatError(){
+        return allowableFloatError;
+    }
+
+    /**
+     * In  AUTO_ROTATE_CATEGORY_MAGNETISM mode, the image may be showed under a fixed rotation angle like 90 degrees, 270 degrees,
+     * 450 degrees, ect., then allowablePortraitFloatError should handle the situation when currentPortraitScaleLevel is not precisely
+     * equal to initPortraitScaleLevel.
+     * Default allowablePortraitFloatError is 1E-12, it should be compatible with most of devices. For devices whose display resolution and aspect ratio is not normal, allowablePortraitFloatError may
+     * need to be tuned. eg., for Galaxy S8, 5E-8 works well. Of course, 5E-8 also works for most of devices because 1E-12 is smaller than 5E-8.
+     * @see XuanImageView#setAllowableFloatError(double)
+     *
+     * @param allowablePortraitFloatError
+     */
+    public void setAllowablePortraitFloatError(double allowablePortraitFloatError){
+        this.allowablePortraitFloatError = allowablePortraitFloatError;
+    }
+
+    public double getAllowablePortraitFloatError(){
+        return allowablePortraitFloatError;
+    }
+
+}

+ 13 - 0
ApexDrivers/app/src/main/java/com/usai/redant/apexdrivers/Update/XuanImageView/XuanImageViewSettings.java

@@ -0,0 +1,13 @@
+package com.usai.redant.apexdrivers.Update.XuanImageView;
+
+/**
+ * Created by xuanyihuang on 20/12/2016.
+ */
+
+public class XuanImageViewSettings {
+    public static int ORIENTATION_PORTRAIT = 1;
+    public static int ORIENTATION_LANDSCAPE = 2;
+
+    public static int AUTO_ROTATE_CATEGORY_RESTORATION = 1;
+    public static int AUTO_ROTATE_CATEGORY_MAGNETISM = 2;
+}

+ 15 - 0
ApexDrivers/app/src/main/res/layout/activity_photo_preview.xml

@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="utf-8"?>
+<android.support.constraint.ConstraintLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    tools:context=".Update.PhotoPreviewActivity">
+
+    <com.usai.redant.apexdrivers.Update.XuanImageView.XuanImageView
+        android:id="@+id/preview_image_view"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"/>
+
+</android.support.constraint.ConstraintLayout>

+ 1 - 1
ApexDrivers/app/src/main/res/layout/capture.xml

@@ -20,7 +20,7 @@
                android:layout_width="fill_parent"
                android:layout_height="fill_parent"/>
 
-  <com.usai.redant.raimage.ViewfinderView
+  <com.usai.redant.apexdrivers.CodeScanner.ViewfinderView
       android:id="@+id/viewfinder_view"
       android:layout_width="fill_parent"
       android:layout_height="fill_parent"/>

+ 1 - 0
ApexDrivers/app/src/main/res/layout/update_photo_cell.xml

@@ -10,6 +10,7 @@
         android:layout_height="120dp"
         android:layout_alignParentRight="true"
         android:layout_margin="5dp"
+        android:scaleType="fitCenter"
         />
 
     <TextView

+ 14 - 0
ApexDrivers/app/src/main/res/menu/photo_preview_menu.xml

@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="utf-8"?>
+<menu xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto">
+
+    <item
+        android:id="@+id/delete_btn"
+        android:title="Delete"
+        app:showAsAction="always"
+        >
+
+    </item>
+
+
+</menu>

+ 14 - 0
ApexDrivers/app/src/main/res/menu/update_menu.xml

@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="utf-8"?>
+<menu xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto">
+
+    <item
+        android:id="@+id/update_btn"
+        android:title="Update"
+        app:showAsAction="always"
+        >
+
+    </item>
+
+
+</menu>

+ 20 - 0
ApexDrivers/app/src/main/res/values/attrs_xuanimageview.xml

@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <declare-styleable name="xuanimageview">
+        <attr name="RotationToggle" format="boolean"/>
+        <attr name="AutoRotateCategory" format="integer"/>
+        <attr name="MaxScaleMultiple" format="float"/>
+        <attr name="DoubleTabScaleMultiple" format="float"/>
+        <attr name="AllowableFloatError" format="string"/>
+        <attr name="AllowablePortraitFloatError" format="string"/>
+        <attr name="SpringBackGradientScaleUpLevel" format="float"/>
+        <attr name="SpringBackGradientScaleDownLevel" format="float"/>
+        <attr name="DoubleTapGradientScaleUpLevel" format="float"/>
+        <attr name="DoubleTapGradientScaleDownLevel" format="float"/>
+        <attr name="AutoRotationTrigger" format="float"/>
+        <attr name="SpringBackRunnableDelay" format="integer"/>
+        <attr name="DoubleTapScaleRunnableDelay" format="integer"/>
+        <attr name="AutoRotationRunnableDelay" format="integer"/>
+        <attr name="AutoRotationRunnableTimes" format="integer"/>
+    </declare-styleable>
+</resources>