diff --git a/YFDXJ/app/.gitignore b/YFDXJ/app/.gitignore old mode 100755 new mode 100644 diff --git a/YFDXJ/app/build.gradle b/YFDXJ/app/build.gradle old mode 100755 new mode 100644 index 72c6321..1caf6f1 --- a/YFDXJ/app/build.gradle +++ b/YFDXJ/app/build.gradle @@ -1,4 +1,7 @@ -apply plugin: 'com.android.application' +plugins { + id 'com.android.application' + id 'org.jetbrains.kotlin.android' +} android { namespace 'com.bjzc.yfdxj' @@ -15,9 +18,6 @@ android { resValue "string", "authorities", applicationId resourceConfigurations += ['zh', 'en', 'hdpi'] } - buildFeatures { - buildConfig = true - } signingConfigs { release { keyAlias 'key0' @@ -32,7 +32,6 @@ android { buildTypes { debug { minifyEnabled false - buildConfigField "boolean", "LOG_ERROR", "true" } release { lintOptions { @@ -43,7 +42,6 @@ android { zipAlignEnabled true // 对齐zip debuggable false // 是否debug proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' - buildConfigField "boolean", "LOG_ERROR", "false" signingConfig signingConfigs.release // 打包签名信息 } } @@ -62,14 +60,35 @@ dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) implementation 'androidx.appcompat:appcompat:1.2.0' implementation 'androidx.constraintlayout:constraintlayout:2.0.4' - implementation 'com.zhy:autolayout:1.4.5' - implementation 'com.yolanda.nohttp:nohttp:1.0.5' + +// implementation 'com.zhy:autolayout:1.4.5' +// implementation 'com.yolanda.nohttp:nohttp:1.0.5' +// implementation 'com.zhihu.android:matisse:0.5.3-beta3' +// implementation 'org.litepal.android:core:1.5.1' +// //图片压缩 +// implementation 'top.zibin:Luban:1.1.8' +// implementation 'id.zelory:compressor:1.0.3' + + + //第三方库自动布局 + implementation project(':autolayout') + //网络请求模块 + implementation project(':nohttp') + //第三方数据库封装,巡检,点检用到 + implementation project(':litepal') + //图片展示模块 + implementation project(':matisse') + //图片展示模块 + implementation project(':luban') + + + + implementation 'com.google.android.material:material:1.2.1' implementation 'com.squareup.okhttp3:logging-interceptor:4.9.0' implementation 'com.squareup.retrofit2:retrofit:2.9.0' implementation 'com.squareup.retrofit2:converter-gson:2.9.0' - implementation 'org.litepal.android:core:1.5.1' + implementation 'com.github.huangyanbin:SmartTable:2.2.0' - implementation 'com.google.android.material:material:1.2.1' implementation 'androidx.multidex:multidex:2.0.1' implementation 'androidx.recyclerview:recyclerview:1.1.0' implementation 'de.hdodenhof:circleimageview:3.1.0' @@ -81,10 +100,8 @@ dependencies { implementation files('libs/rinsunMT.jar') implementation 'org.greenrobot:eventbus:3.0.0' implementation 'com.github.bumptech.glide:glide:4.11.0' - implementation 'com.zhihu.android:matisse:0.5.3-beta3' - //图片压缩 - implementation 'top.zibin:Luban:1.1.8' - implementation 'id.zelory:compressor:1.0.3' + + //第三方工具类 implementation 'cn.hutool:hutool-all:5.8.5' //mqtt diff --git a/YFDXJ/app/libs/MiPush_SDK_Client_3_2_2.jar b/YFDXJ/app/libs/MiPush_SDK_Client_3_2_2.jar old mode 100755 new mode 100644 diff --git a/YFDXJ/app/libs/SangforSDK.jar b/YFDXJ/app/libs/SangforSDK.jar old mode 100755 new mode 100644 diff --git a/YFDXJ/app/libs/ZSDK_API.jar b/YFDXJ/app/libs/ZSDK_API.jar old mode 100755 new mode 100644 diff --git a/YFDXJ/app/libs/badgeview.jar b/YFDXJ/app/libs/badgeview.jar old mode 100755 new mode 100644 diff --git a/YFDXJ/app/libs/gson-2.7.jar b/YFDXJ/app/libs/gson-2.7.jar old mode 100755 new mode 100644 diff --git a/YFDXJ/app/libs/ksoap2-android-assembly-3.6.0-jar-with-dependencies.jar b/YFDXJ/app/libs/ksoap2-android-assembly-3.6.0-jar-with-dependencies.jar old mode 100755 new mode 100644 diff --git a/YFDXJ/app/libs/zxing.jar b/YFDXJ/app/libs/zxing.jar old mode 100755 new mode 100644 diff --git a/YFDXJ/app/proguard-rules.pro b/YFDXJ/app/proguard-rules.pro old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/androidTest/java/com/bjzc/yfdxj/ExampleInstrumentedTest.java b/YFDXJ/app/src/androidTest/java/com/bjzc/yfdxj/ExampleInstrumentedTest.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/androidTest/java/com/rehome/yfdxj/ExampleInstrumentedTest.java b/YFDXJ/app/src/androidTest/java/com/rehome/yfdxj/ExampleInstrumentedTest.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/AndroidManifest.xml b/YFDXJ/app/src/main/AndroidManifest.xml old mode 100755 new mode 100644 index 21f4ed4..a4b0f4e --- a/YFDXJ/app/src/main/AndroidManifest.xml +++ b/YFDXJ/app/src/main/AndroidManifest.xml @@ -68,10 +68,10 @@ android:allowBackup="false" android:fullBackupContent="false" android:dataExtractionRules="@xml/data_extraction_rules" - android:icon="@mipmap/logo1" + android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:networkSecurityConfig="@xml/network_security_config" - android:roundIcon="@mipmap/logo1" + android:roundIcon="@mipmap/ic_launcher_round" android:screenOrientation="portrait" android:supportsRtl="true" android:theme="@style/AppTheme" diff --git a/YFDXJ/app/src/main/assets/litepal.xml b/YFDXJ/app/src/main/assets/litepal.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/DBModel/AjhScInfo.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/DBModel/AjhScInfo.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/DBModel/Ajhjh.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/DBModel/Ajhjh.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/DBModel/AjhjhList.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/DBModel/AjhjhList.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/DBModel/AjhjhxzrwList.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/DBModel/AjhjhxzrwList.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/DBModel/Ajhxcjs.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/DBModel/Ajhxcjs.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/DBModel/Ajhxzrwqy.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/DBModel/Ajhxzrwqy.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/DBModel/Ajhxzrwqylist.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/DBModel/Ajhxzrwqylist.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/DBModel/Djjh.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/DBModel/Djjh.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/DBModel/DjjhList.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/DBModel/DjjhList.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/DBModel/DjjhRwList.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/DBModel/DjjhRwList.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/DBModel/DjjhRwQy.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/DBModel/DjjhRwQy.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/DBModel/DjjhRwQyList.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/DBModel/DjjhRwQyList.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/DBModel/Gwzyxcqrnr.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/DBModel/Gwzyxcqrnr.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/DBModel/LyXcjsInfo.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/DBModel/LyXcjsInfo.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/DBModel/LyYhpcInfo.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/DBModel/LyYhpcInfo.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/DBModel/LyxcXm.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/DBModel/LyxcXm.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/DBModel/LyxcXmJg.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/DBModel/LyxcXmJg.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/DBModel/LyxcXmList.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/DBModel/LyxcXmList.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/DBModel/LyxcqyList.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/DBModel/LyxcqyList.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/DBModel/Lyxcrwqy.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/DBModel/Lyxcrwqy.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/DBModel/Othersbsave.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/DBModel/Othersbsave.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/DBModel/QYAQFXDATABean.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/DBModel/QYAQFXDATABean.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/DBModel/QYDDATABean.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/DBModel/QYDDATABean.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/DBModel/QxgdInfo.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/DBModel/QxgdInfo.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/DBModel/Qy.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/DBModel/Qy.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/DBModel/QyList.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/DBModel/QyList.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/DBModel/Uploadaqjcsave.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/DBModel/Uploadaqjcsave.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/DBModel/Uploadsblcsave.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/DBModel/Uploadsblcsave.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/DBModel/Uploadzdcsrw.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/DBModel/Uploadzdcsrw.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/DBModel/Uploadzgjg.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/DBModel/Uploadzgjg.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/DBModel/Uploadzjcszg.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/DBModel/Uploadzjcszg.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/DBModel/WjbList.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/DBModel/WjbList.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/DBModel/WjbStateList.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/DBModel/WjbStateList.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/DBModel/WjbjxitemList.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/DBModel/WjbjxitemList.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/DBModel/WjbrwList.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/DBModel/WjbrwList.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/DBModel/WjbrwinfoList.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/DBModel/WjbrwinfoList.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/DBModel/WjbzjdList.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/DBModel/WjbzjdList.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/DBModel/XDJJHXZBean.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/DBModel/XDJJHXZBean.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/DBModel/XDJJHXZDataBean.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/DBModel/XDJJHXZDataBean.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/DBModel/XSJJHDataBean.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/DBModel/XSJJHDataBean.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/DBModel/XSJJHXZBean.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/DBModel/XSJJHXZBean.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/DBModel/XSJJHXZDataBean.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/DBModel/XSJJHXZDataBean.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/DBModel/XcjsInfo.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/DBModel/XcjsInfo.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/DBModel/XfBaxcRwqy.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/DBModel/XfBaxcRwqy.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/DBModel/XfBaxcRwqyList.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/DBModel/XfBaxcRwqyList.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/DBModel/XfDjjh.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/DBModel/XfDjjh.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/DBModel/XfDjjhList.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/DBModel/XfDjjhList.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/DBModel/XfDjjhRwList.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/DBModel/XfDjjhRwList.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/DBModel/XfDjjhRwqy.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/DBModel/XfDjjhRwqy.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/DBModel/XfDjjhRwqyList.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/DBModel/XfDjjhRwqyList.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/DBModel/XfXcjsInfo.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/DBModel/XfXcjsInfo.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/DBModel/XfXcmhqc.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/DBModel/XfXcmhqc.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/DBModel/XfXcmhqcList.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/DBModel/XfXcmhqcList.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/DBModel/XfXcxm.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/DBModel/XfXcxm.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/DBModel/XfXcxmList.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/DBModel/XfXcxmList.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/DBModel/XfXcxmjg.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/DBModel/XfXcxmjg.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/DBModel/Xjjh.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/DBModel/Xjjh.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/DBModel/XjjhList.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/DBModel/XjjhList.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/DBModel/Xjzjrw.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/DBModel/Xjzjrw.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/DBModel/XjzjrwList.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/DBModel/XjzjrwList.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/DBModel/XwaqgcJh.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/DBModel/XwaqgcJh.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/DBModel/XwaqgcJhList.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/DBModel/XwaqgcJhList.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/DBModel/XwaqgcJs.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/DBModel/XwaqgcJs.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/DBModel/XwaqgcSc.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/DBModel/XwaqgcSc.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/DBModel/YhpcInfo.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/DBModel/YhpcInfo.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/DBModel/Yhpctp.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/DBModel/Yhpctp.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/DBModel/Zjrwdata.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/DBModel/Zjrwdata.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/DBModel/Zjrwdatainfo.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/DBModel/Zjrwdatainfo.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/DBModel/Zjrwst.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/DBModel/Zjrwst.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/DBModel/Zy.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/DBModel/Zy.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/DBModel/ZyInfo.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/DBModel/ZyInfo.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/Listener/GlideEngine.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/Listener/GlideEngine.java index b7a2c6d..7d2984e 100644 --- a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/Listener/GlideEngine.java +++ b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/Listener/GlideEngine.java @@ -155,7 +155,7 @@ public class GlideEngine implements ImageEngine { .override(180, 180) .centerCrop() .sizeMultiplier(0.5f) - .apply(new RequestOptions().placeholder(R.drawable.picture_image_placeholder)) + .apply(new RequestOptions().placeholder(com.luck.picture.lib.R.drawable.picture_image_placeholder)) .into(new BitmapImageViewTarget(imageView) { @Override protected void setResource(Bitmap resource) { @@ -198,7 +198,7 @@ public class GlideEngine implements ImageEngine { .load(url) .override(200, 200) .centerCrop() - .apply(new RequestOptions().placeholder(R.drawable.picture_image_placeholder)) + .apply(new RequestOptions().placeholder(com.luck.picture.lib.R.drawable.picture_image_placeholder)) .into(imageView); } diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/MyApplication.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/MyApplication.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/activity/CheckVibrationActivity.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/activity/CheckVibrationActivity.java index c395422..3b9a87c 100644 --- a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/activity/CheckVibrationActivity.java +++ b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/activity/CheckVibrationActivity.java @@ -199,48 +199,81 @@ public class CheckVibrationActivity extends BaseActivity3 { rgLevel.setOnCheckedChangeListener(new RadioGroup.OnCheckedChangeListener() { @Override public void onCheckedChanged(RadioGroup radioGroup, int i) { - switch (i){ - case R.id.rb1: - level = 0; - if(isEdit){ - demo();//测量 - } - break; - case R.id.rb2: - level = 1; - if(isEdit){ - demo();//测量 - } - break; + if(i==R.id.rb1){ + level = 0; + if(isEdit){ + demo();//测量 + } } + if(i==R.id.rb2){ + level = 1; + if(isEdit){ + demo();//测量 + } + } +// switch (i){ +// case R.id.rb1: +// level = 0; +// if(isEdit){ +// demo();//测量 +// } +// break; +// case R.id.rb2: +// level = 1; +// if(isEdit){ +// demo();//测量 +// } +// break; +// } } }); rgType.setOnCheckedChangeListener(new RadioGroup.OnCheckedChangeListener() { @Override public void onCheckedChanged(RadioGroup radioGroup, int i) { - switch (i){ - case R.id.rb3: - checkVibrationType = MTnativeInterface.CMD_VIBD_R; - etRealTitle.setText("实际值(mm):"); - if(isEdit){ - demo();//测量 - } - break; - case R.id.rb4: - checkVibrationType = MTnativeInterface.CMD_VIBV_R; - etRealTitle.setText("实际值(mm/s):"); - if(isEdit){ - demo();//测量 - } - break; - case R.id.rb5: - checkVibrationType = MTnativeInterface.CMD_VIBA_R; - etRealTitle.setText("实际值(mm/(s^2)):"); - if(isEdit){ - demo();//测量 - } - break; + if(i==R.id.rb3){ + checkVibrationType = MTnativeInterface.CMD_VIBD_R; + etRealTitle.setText("实际值(mm):"); + if(isEdit){ + demo();//测量 + } + } + if(i==R.id.rb4){ + checkVibrationType = MTnativeInterface.CMD_VIBV_R; + etRealTitle.setText("实际值(mm/s):"); + if(isEdit){ + demo();//测量 + } + } + if(i==R.id.rb5){ + checkVibrationType = MTnativeInterface.CMD_VIBA_R; + etRealTitle.setText("实际值(mm/(s^2)):"); + if(isEdit){ + demo();//测量 + } } +// switch (i){ +// case R.id.rb3: +// checkVibrationType = MTnativeInterface.CMD_VIBD_R; +// etRealTitle.setText("实际值(mm):"); +// if(isEdit){ +// demo();//测量 +// } +// break; +// case R.id.rb4: +// checkVibrationType = MTnativeInterface.CMD_VIBV_R; +// etRealTitle.setText("实际值(mm/s):"); +// if(isEdit){ +// demo();//测量 +// } +// break; +// case R.id.rb5: +// checkVibrationType = MTnativeInterface.CMD_VIBA_R; +// etRealTitle.setText("实际值(mm/(s^2)):"); +// if(isEdit){ +// demo();//测量 +// } +// break; +// } } }); noticeValue.setOnLongClickListener(new View.OnLongClickListener() { diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/activity/ContactFragment.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/activity/ContactFragment.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/activity/LoginActivity.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/activity/LoginActivity.java old mode 100755 new mode 100644 index f64f82f..048c66b --- a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/activity/LoginActivity.java +++ b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/activity/LoginActivity.java @@ -19,7 +19,7 @@ import android.widget.TextView; import com.azhon.appupdate.listener.OnButtonClickListener; import com.azhon.appupdate.listener.OnDownloadListener; import com.azhon.appupdate.manager.DownloadManager; -import com.bjzc.yfdxj.BuildConfig; + import com.bjzc.yfdxj.base.BaseCallBackNoProgress; import com.bjzc.yfdxj.R; import com.bjzc.yfdxj.base.BaseActivity; @@ -181,7 +181,7 @@ public class LoginActivity extends BaseActivity { } } - if (BuildConfig.LOG_ERROR) { + if (isApkInDebug(context)) { etUse.setText("161619"); etPwd.setText("lgy12345"); diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/activity/MainActivity.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/activity/MainActivity.java index d408a96..c9c2cb1 100644 --- a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/activity/MainActivity.java +++ b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/activity/MainActivity.java @@ -48,6 +48,7 @@ import com.bjzc.yfdxj.utils.FlashUtil; import com.bjzc.yfdxj.utils.GsonUtils; import com.bjzc.yfdxj.utils.HttpUtils; import com.bjzc.yfdxj.utils.MqttSSLPublishServer; +import com.bjzc.yfdxj.utils.NetworkUtil; import com.bjzc.yfdxj.utils.OAToolbar; import com.bjzc.yfdxj.utils.RSAAndroid; import com.bjzc.yfdxj.utils.SPUtils; @@ -163,9 +164,11 @@ public class MainActivity extends BaseActivity3 { getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);//设置透明状态栏 getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);//设置透明导航栏 StatusBarUtil.transparencyBar(MainActivity.this);//设置透明状态栏 - //当选择外网时才会开启一键招回 + //当选择外网时,并且网络连接不是wifi才会开启一键招回 if(Contans.IP.equals(Contans.network_type_extranet)){ - initMqtt(); + if(!NetworkUtil.isWifiNetWorkStatus(context)){ + initMqtt(); + } } btnLogout.setOnClickListener(new View.OnClickListener() { @Override @@ -581,21 +584,24 @@ public class MainActivity extends BaseActivity3 { @Override protected void onResume() { super.onResume(); + if(Contans.IP.equals(Contans.network_type_extranet)){ - if(mqttPublishServer==null){ - mqttPublishServer = new MqttSSLPublishServer(this); - } - if(timer==null){ - try { - timer = new Timer(); - timer.schedule(new TimerTask() { - public void run() { - mqttPublishServer.start(); - } - }, 1000, 60000); - // 设定指定的时间time,此处为10000毫秒 - } catch (Exception e) { - e.printStackTrace(); + if(!NetworkUtil.isWifiNetWorkStatus(context)){ + if(mqttPublishServer==null){ + mqttPublishServer = new MqttSSLPublishServer(this); + } + if(timer==null){ + try { + timer = new Timer(); + timer.schedule(new TimerTask() { + public void run() { + mqttPublishServer.start(); + } + }, 1000, 60000); + // 设定指定的时间time,此处为10000毫秒 + } catch (Exception e) { + e.printStackTrace(); + } } } } diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/activity/MainFragment.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/activity/MainFragment.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/activity/MineFragment.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/activity/MineFragment.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/activity/TabMainActivity.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/activity/TabMainActivity.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/activity/regular/CheckVibrationDqgzActivity.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/activity/regular/CheckVibrationDqgzActivity.java index 2efe788..9af056a 100644 --- a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/activity/regular/CheckVibrationDqgzActivity.java +++ b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/activity/regular/CheckVibrationDqgzActivity.java @@ -189,38 +189,68 @@ public class CheckVibrationDqgzActivity extends BaseActivity3 { rgLevel.setOnCheckedChangeListener(new RadioGroup.OnCheckedChangeListener() { @Override public void onCheckedChanged(RadioGroup radioGroup, int i) { - switch (i) { - case R.id.rb1: - level = 0; - demo();//测量 - break; - case R.id.rb2: - level = 1; - demo();//测量 - break; + if(i==R.id.rb1){ + level = 0; + demo();//测量 + } + if(i==R.id.rb2){ + level = 1; + demo();//测量 } +// switch (i) { +// case R.id.rb1: +// level = 0; +// demo();//测量 +// break; +// case R.id.rb2: +// level = 1; +// demo();//测量 +// break; +// } } }); rgType.setOnCheckedChangeListener(new RadioGroup.OnCheckedChangeListener() { @Override public void onCheckedChanged(RadioGroup radioGroup, int i) { - switch (i) { - case R.id.rb3: - checkVibrationType = MTnativeInterface.CMD_VIBD_R; - etRealTitle.setText("实际值(mm):"); + if(i==R.id.rb3){ + checkVibrationType = MTnativeInterface.CMD_VIBD_R; + etRealTitle.setText("实际值(mm):"); + if(isEdit){ demo();//测量 - break; - case R.id.rb4: - checkVibrationType = MTnativeInterface.CMD_VIBV_R; - etRealTitle.setText("实际值(mm/s):"); + } + } + if(i==R.id.rb4){ + checkVibrationType = MTnativeInterface.CMD_VIBV_R; + etRealTitle.setText("实际值(mm/s):"); + if(isEdit){ demo();//测量 - break; - case R.id.rb5: - checkVibrationType = MTnativeInterface.CMD_VIBA_R; - etRealTitle.setText("实际值(mm/(s^2)):"); + } + } + if(i==R.id.rb5){ + checkVibrationType = MTnativeInterface.CMD_VIBA_R; + etRealTitle.setText("实际值(mm/(s^2)):"); + if(isEdit){ demo();//测量 - break; + } } + +// switch (i) { +// case R.id.rb3: +// checkVibrationType = MTnativeInterface.CMD_VIBD_R; +// etRealTitle.setText("实际值(mm):"); +// demo();//测量 +// break; +// case R.id.rb4: +// checkVibrationType = MTnativeInterface.CMD_VIBV_R; +// etRealTitle.setText("实际值(mm/s):"); +// demo();//测量 +// break; +// case R.id.rb5: +// checkVibrationType = MTnativeInterface.CMD_VIBA_R; +// etRealTitle.setText("实际值(mm/(s^2)):"); +// demo();//测量 +// break; +// } } }); noticeValue.setOnLongClickListener(new View.OnLongClickListener() { diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/activity/sbxdj/BzFragment.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/activity/sbxdj/BzFragment.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/activity/sbxdj/CJFragment.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/activity/sbxdj/CJFragment.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/activity/sbxdj/ChangeYulActivity.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/activity/sbxdj/ChangeYulActivity.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/activity/sbxdj/DjMainActivity.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/activity/sbxdj/DjMainActivity.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/activity/sbxdj/DjdscFragment.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/activity/sbxdj/DjdscFragment.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/activity/sbxdj/DjgwListActivity.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/activity/sbxdj/DjgwListActivity.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/activity/sbxdj/FXFragment.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/activity/sbxdj/FXFragment.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/activity/sbxdj/FfFragment.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/activity/sbxdj/FfFragment.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/activity/sbxdj/FragmentAdapter.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/activity/sbxdj/FragmentAdapter.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/activity/sbxdj/MainSbxdjglActivity.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/activity/sbxdj/MainSbxdjglActivity.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/activity/sbxdj/OldqxgdFragment.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/activity/sbxdj/OldqxgdFragment.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/activity/sbxdj/QxgdFragment.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/activity/sbxdj/QxgdFragment.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/activity/sbxdj/SbSelectActivity.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/activity/sbxdj/SbSelectActivity.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/activity/sbxdj/SbxdjcjsbActivity.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/activity/sbxdj/SbxdjcjsbActivity.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/activity/sbxdj/SdjSbListActivity.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/activity/sbxdj/SdjSbListActivity.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/activity/sbxdj/SdjgzActivity.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/activity/sbxdj/SdjgzActivity.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/activity/sbxdj/SdlbActivity.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/activity/sbxdj/SdlbActivity.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/activity/sbxdj/SxcdjActivity.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/activity/sbxdj/SxcdjActivity.java old mode 100755 new mode 100644 index b1e0bb4..fa44014 --- a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/activity/sbxdj/SxcdjActivity.java +++ b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/activity/sbxdj/SxcdjActivity.java @@ -66,21 +66,33 @@ public class SxcdjActivity extends BaseActivity { @Override public void onCheckedChanged(RadioGroup radioGroup, int i) { - switch (i) { - case R.id.rb1: - rb1.setTextColor(Color.WHITE); - rb2.setTextColor(Color.GRAY); - rb3.setTextColor(Color.GRAY); - vp.setCurrentItem(0, false); - break; - case R.id.rb2: - rb1.setTextColor(Color.GRAY); - rb2.setTextColor(Color.WHITE); - rb3.setTextColor(Color.GRAY); - vp.setCurrentItem(1, false); - break; - + if(i==R.id.rb1){ + rb1.setTextColor(Color.WHITE); + rb2.setTextColor(Color.GRAY); + rb3.setTextColor(Color.GRAY); + vp.setCurrentItem(0, false); + } + if(i==R.id.rb2){ + rb1.setTextColor(Color.GRAY); + rb2.setTextColor(Color.WHITE); + rb3.setTextColor(Color.GRAY); + vp.setCurrentItem(1, false); } + +// switch (i) { +// case R.id.rb1: +// rb1.setTextColor(Color.WHITE); +// rb2.setTextColor(Color.GRAY); +// rb3.setTextColor(Color.GRAY); +// vp.setCurrentItem(0, false); +// break; +// case R.id.rb2: +// rb1.setTextColor(Color.GRAY); +// rb2.setTextColor(Color.WHITE); +// rb3.setTextColor(Color.GRAY); +// vp.setCurrentItem(1, false); +// break; +// } } }); } diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/activity/sbxdj/TipsActivity.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/activity/sbxdj/TipsActivity.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/activity/sbxdj/WjFragment.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/activity/sbxdj/WjFragment.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/activity/sbxdj/XzjhFragment.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/activity/sbxdj/XzjhFragment.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/activity/sbxdj/YjwscFragment.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/activity/sbxdj/YjwscFragment.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/activity/sbxdj/YscFragment.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/activity/sbxdj/YscFragment.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/activity/sbxdj/YulActivity.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/activity/sbxdj/YulActivity.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/activity/sbxdj/Yul_SBActivity.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/activity/sbxdj/Yul_SBActivity.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/activity/sbxj/NFCInfoActivity.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/activity/sbxj/NFCInfoActivity.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/activity/sbxj/SbxjcjsbActivity.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/activity/sbxj/SbxjcjsbActivity.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/activity/sbxj/ScxsjhFragment.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/activity/sbxj/ScxsjhFragment.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/activity/sbxj/SxSbListActivity.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/activity/sbxj/SxSbListActivity.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/activity/sbxj/SxgzActivity.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/activity/sbxj/SxgzActivity.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/activity/sbxj/XJCJFragment.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/activity/sbxj/XJCJFragment.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/activity/sbxj/XjMainActivity.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/activity/sbxj/XjMainActivity.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/activity/sbxj/XjSbListActivity.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/activity/sbxj/XjSbListActivity.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/activity/sbxj/XjYulActivity.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/activity/sbxj/XjYulActivity.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/activity/sbxj/XsBackLogActivity.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/activity/sbxj/XsBackLogActivity.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/activity/sbxj/XsHistoryActivity.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/activity/sbxj/XsHistoryActivity.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/activity/sbxj/XscbglActivity.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/activity/sbxj/XscbglActivity.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/activity/sbxj/XscbqyActivity.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/activity/sbxj/XscbqyActivity.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/activity/sbxj/XscbqyWorkActivity.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/activity/sbxj/XscbqyWorkActivity.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/activity/sbxj/XscbqyWorkXsActivity.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/activity/sbxj/XscbqyWorkXsActivity.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/activity/sbxj/XzxsjhFragment.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/activity/sbxj/XzxsjhFragment.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/adapter/AqjclbAdapter.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/adapter/AqjclbAdapter.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/adapter/AqjcrwlbListAdapter.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/adapter/AqjcrwlbListAdapter.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/adapter/BpbjinfoAdapter.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/adapter/BpbjinfoAdapter.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/adapter/CommonAdapter.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/adapter/CommonAdapter.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/adapter/ContactAdapter.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/adapter/ContactAdapter.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/adapter/CxzjrwAdapter.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/adapter/CxzjrwAdapter.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/adapter/DlbAdapter.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/adapter/DlbAdapter.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/adapter/GridViewAdapter.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/adapter/GridViewAdapter.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/adapter/GwfxListAdapter.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/adapter/GwfxListAdapter.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/adapter/GzbAdapter.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/adapter/GzbAdapter.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/adapter/ItemViewDelegate.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/adapter/ItemViewDelegate.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/adapter/ItemViewDelegateManager.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/adapter/ItemViewDelegateManager.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/adapter/MultiItemTypeAdapter.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/adapter/MultiItemTypeAdapter.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/adapter/MyFragmentAdapter.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/adapter/MyFragmentAdapter.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/adapter/QfBmlbinfoAdapter.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/adapter/QfBmlbinfoAdapter.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/adapter/QfPmListAdapter.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/adapter/QfPmListAdapter.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/adapter/QfSblbinfoAdapter.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/adapter/QfSblbinfoAdapter.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/adapter/QfdjAdapter.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/adapter/QfdjAdapter.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/adapter/QffzrAdapter.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/adapter/QffzrAdapter.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/adapter/QfgdztinfoAdapter.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/adapter/QfgdztinfoAdapter.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/adapter/QxdAdapter.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/adapter/QxdAdapter.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/adapter/ScjhAdapter.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/adapter/ScjhAdapter.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/adapter/ScxsAdapter.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/adapter/ScxsAdapter.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/adapter/ViewHolder.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/adapter/ViewHolder.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/adapter/WjDetailsListAdapter.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/adapter/WjDetailsListAdapter.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/adapter/WjbxjAdapter.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/adapter/WjbxjAdapter.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/adapter/XsHistoryExpandableListAdapter.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/adapter/XsHistoryExpandableListAdapter.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/adapter/XsjhListAdapter.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/adapter/XsjhListAdapter.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/adapter/XsjhqyAdapter.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/adapter/XsjhqyAdapter.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/adapter/XsjhqyWorkCbAdapter.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/adapter/XsjhqyWorkCbAdapter.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/adapter/XsjhqyWorkDjAdapter.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/adapter/XsjhqyWorkDjAdapter.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/adapter/XsjhqyWorkXsAdapter.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/adapter/XsjhqyWorkXsAdapter.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/adapter/XzjhAdapter.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/adapter/XzjhAdapter.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/adapter/XzxsjhAdapter.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/adapter/XzxsjhAdapter.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/adapter/ZgrwSaveAdapter.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/adapter/ZgrwSaveAdapter.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/adapter/ZjbAdapter.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/adapter/ZjbAdapter.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/adapter/ZjdscAdapter.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/adapter/ZjdscAdapter.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/adapter/ZjrwAdapter.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/adapter/ZjrwAdapter.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/adapter/ZjrwxjAdapter.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/adapter/ZjrwxjAdapter.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/adapter/ZkdAdapter.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/adapter/ZkdAdapter.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/adapter/aqjcsaveAdapter.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/adapter/aqjcsaveAdapter.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/adapter/othersbAdapter.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/adapter/othersbAdapter.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/base/BaseActivity.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/base/BaseActivity.java old mode 100755 new mode 100644 index 2b70f8a..ae0d6c5 --- a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/base/BaseActivity.java +++ b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/base/BaseActivity.java @@ -4,6 +4,7 @@ import android.app.PendingIntent; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; +import android.content.pm.ApplicationInfo; import android.nfc.NdefMessage; import android.nfc.NdefRecord; import android.nfc.NfcAdapter; @@ -23,7 +24,7 @@ import android.view.WindowManager; import android.widget.TextView; import android.widget.Toast; -import com.bjzc.yfdxj.BuildConfig; + import com.bjzc.yfdxj.R; import com.bjzc.yfdxj.utils.ControllerActivity; import com.zhy.autolayout.AutoLayoutActivity; @@ -202,7 +203,7 @@ public abstract class BaseActivity extends AutoLayoutActivity { } public void showLog(String logText) { - if (BuildConfig.LOG_ERROR) { + if (isApkInDebug(context)) { if(TextUtils.isEmpty(logText)){ Log.i("app", "logText is null"); }else{ @@ -210,4 +211,17 @@ public abstract class BaseActivity extends AutoLayoutActivity { } } } + + /** + * 判断当前应用是否是debug状态 + */ + + public static boolean isApkInDebug(Context context) { + try { + ApplicationInfo info = context.getApplicationInfo(); + return (info.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0; + } catch (Exception e) { + return false; + } + } } diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/base/BaseActivity3.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/base/BaseActivity3.java old mode 100755 new mode 100644 index e15b4b6..5a16da6 --- a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/base/BaseActivity3.java +++ b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/base/BaseActivity3.java @@ -6,6 +6,7 @@ import android.app.PendingIntent; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; +import android.content.pm.ApplicationInfo; import android.nfc.NdefMessage; import android.nfc.NdefRecord; import android.nfc.NfcAdapter; @@ -24,7 +25,7 @@ import android.view.View; import android.widget.EditText; import android.widget.Toast; -import com.bjzc.yfdxj.BuildConfig; + import com.bjzc.yfdxj.R; import com.bjzc.yfdxj.utils.AppManager; import com.bjzc.yfdxj.utils.OAToolbar; @@ -238,7 +239,7 @@ public abstract class BaseActivity3 extends AutoLayoutActivity { } public void showLog(String logText) { - if (BuildConfig.LOG_ERROR) { + if (isApkInDebug(context)) { if(TextUtils.isEmpty(logText)){ Log.i("app", "logText is null"); }else{ @@ -247,4 +248,17 @@ public abstract class BaseActivity3 extends AutoLayoutActivity { } } + /** + * 判断当前应用是否是debug状态 + */ + + public static boolean isApkInDebug(Context context) { + try { + ApplicationInfo info = context.getApplicationInfo(); + return (info.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0; + } catch (Exception e) { + return false; + } + } + } diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/base/BaseCallBack.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/base/BaseCallBack.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/base/BaseFragment.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/base/BaseFragment.java old mode 100755 new mode 100644 index 8a23825..24e996f --- a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/base/BaseFragment.java +++ b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/base/BaseFragment.java @@ -3,6 +3,7 @@ package com.bjzc.yfdxj.base; import android.app.Activity; import android.content.Context; +import android.content.pm.ApplicationInfo; import android.os.Bundle; import android.text.TextUtils; import android.util.Log; @@ -14,7 +15,7 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.fragment.app.Fragment; -import com.bjzc.yfdxj.BuildConfig; + /** @@ -71,7 +72,7 @@ public abstract class BaseFragment extends Fragment { } public void showLog(String logText) { - if (BuildConfig.LOG_ERROR) { + if (isApkInDebug(context)) { if(TextUtils.isEmpty(logText)){ Log.i("app", "logText is null"); }else{ @@ -80,4 +81,17 @@ public abstract class BaseFragment extends Fragment { } } + /** + * 判断当前应用是否是debug状态 + */ + + public static boolean isApkInDebug(Context context) { + try { + ApplicationInfo info = context.getApplicationInfo(); + return (info.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0; + } catch (Exception e) { + return false; + } + } + } diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/base/MipcaActivityCapture.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/base/MipcaActivityCapture.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/bean/ApkUpdateBean.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/bean/ApkUpdateBean.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/bean/AqjclbBean.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/bean/AqjclbBean.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/bean/AqjcrwListBean.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/bean/AqjcrwListBean.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/bean/BasicDataBean.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/bean/BasicDataBean.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/bean/BmBean.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/bean/BmBean.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/bean/BmidBean.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/bean/BmidBean.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/bean/ContactListBean.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/bean/ContactListBean.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/bean/DjAjhGzInfo.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/bean/DjAjhGzInfo.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/bean/DlbInfo.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/bean/DlbInfo.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/bean/DxxSbListBean.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/bean/DxxSbListBean.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/bean/DxxSbUploadBean.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/bean/DxxSbUploadBean.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/bean/DxxZjrwBean.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/bean/DxxZjrwBean.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/bean/GridViewBean.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/bean/GridViewBean.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/bean/GzbBean.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/bean/GzbBean.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/bean/Gzqk.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/bean/Gzqk.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/bean/GzqkInfo.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/bean/GzqkInfo.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/bean/MessageEvent.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/bean/MessageEvent.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/bean/PMChangeRequestBean.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/bean/PMChangeRequestBean.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/bean/PMRequestBean.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/bean/PMRequestBean.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/bean/PhoneInfo.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/bean/PhoneInfo.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/bean/PminfoBean.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/bean/PminfoBean.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/bean/PminfoBean2.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/bean/PminfoBean2.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/bean/PminfoNewBean.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/bean/PminfoNewBean.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/bean/PushInfo.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/bean/PushInfo.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/bean/QXRequestBean.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/bean/QXRequestBean.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/bean/QfFzrBean.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/bean/QfFzrBean.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/bean/QfbmlistBean.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/bean/QfbmlistBean.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/bean/QfdjBean.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/bean/QfdjBean.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/bean/QfgdztlistBean.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/bean/QfgdztlistBean.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/bean/Qfkccxbean.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/bean/Qfkccxbean.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/bean/QfsblistBean.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/bean/QfsblistBean.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/bean/QfsblistRequestBean.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/bean/QfsblistRequestBean.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/bean/QxTjgdRequestBean.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/bean/QxTjgdRequestBean.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/bean/QxdBean.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/bean/QxdBean.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/bean/ResultBean.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/bean/ResultBean.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/bean/ResultBean2.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/bean/ResultBean2.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/bean/ResultBean3.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/bean/ResultBean3.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/bean/ScDjjhInfo.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/bean/ScDjjhInfo.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/bean/ScdjjhBean.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/bean/ScdjjhBean.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/bean/ScxjjhBean.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/bean/ScxjjhBean.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/bean/SczjdBean.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/bean/SczjdBean.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/bean/SetSbModel.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/bean/SetSbModel.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/bean/SetxjSbModel.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/bean/SetxjSbModel.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/bean/Sisbean.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/bean/Sisbean.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/bean/StatusInfo.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/bean/StatusInfo.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/bean/StatusInfo2.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/bean/StatusInfo2.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/bean/Statusinfozj.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/bean/Statusinfozj.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/bean/TJRequestBean.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/bean/TJRequestBean.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/bean/UploadPhotosBean.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/bean/UploadPhotosBean.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/bean/UploadPhotosBean2.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/bean/UploadPhotosBean2.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/bean/UserInfo.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/bean/UserInfo.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/bean/XJRequestBean.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/bean/XJRequestBean.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/bean/XjSbModel.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/bean/XjSbModel.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/bean/XjzhtjBean.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/bean/XjzhtjBean.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/bean/XsHistoryListBean.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/bean/XsHistoryListBean.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/bean/XsJhListBean.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/bean/XsJhListBean.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/bean/XsRequestInfo.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/bean/XsRequestInfo.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/bean/XsRequestInfo2.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/bean/XsRequestInfo2.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/bean/XsResultBaseBean.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/bean/XsResultBaseBean.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/bean/XsSaveDataInfo.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/bean/XsSaveDataInfo.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/bean/XscbRequestBean.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/bean/XscbRequestBean.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/bean/XsjhQyBean.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/bean/XsjhQyBean.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/bean/ZJinfoBean.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/bean/ZJinfoBean.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/bean/ZYDataBean.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/bean/ZYDataBean.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/bean/ZjbBean.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/bean/ZjbBean.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/bean/ZjrwBean.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/bean/ZjrwBean.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/bean/ZjrwdetailBean.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/bean/ZjrwdetailBean.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/bean/ZkdInfo.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/bean/ZkdInfo.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/bean/djuploadrzRequestBean.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/bean/djuploadrzRequestBean.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/bean/qfbpbjRequestBean.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/bean/qfbpbjRequestBean.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/bean/sbInfo.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/bean/sbInfo.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/bean/xjsbInfo.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/bean/xjsbInfo.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/bean/zdybean.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/bean/zdybean.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/contans/Contans.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/contans/Contans.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/service/PushService.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/service/PushService.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/utils/Api.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/utils/Api.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/utils/AppManager.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/utils/AppManager.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/utils/AutoToolbar.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/utils/AutoToolbar.java old mode 100755 new mode 100644 index 0ed6c45..e0aae74 --- a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/utils/AutoToolbar.java +++ b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/utils/AutoToolbar.java @@ -27,22 +27,22 @@ public class AutoToolbar extends Toolbar { public AutoToolbar(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); - TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.Toolbar, - defStyleAttr, R.style.Widget_AppCompat_Toolbar); + TypedArray a = context.obtainStyledAttributes(attrs, androidx.appcompat.R.styleable.Toolbar, + defStyleAttr, androidx.appcompat.R.style.Widget_AppCompat_Toolbar); - int titleTextAppearance = a.getResourceId(R.styleable.Toolbar_titleTextAppearance, - R.style.TextAppearance_Widget_AppCompat_Toolbar_Title); + int titleTextAppearance = a.getResourceId(androidx.appcompat.R.styleable.Toolbar_titleTextAppearance, + androidx.appcompat.R.style.TextAppearance_Widget_AppCompat_Toolbar_Title); - int subtitleTextAppearance = a.getResourceId(R.styleable.Toolbar_subtitleTextAppearance, - R.style.TextAppearance_Widget_AppCompat_Toolbar_Subtitle); + int subtitleTextAppearance = a.getResourceId(androidx.appcompat.R.styleable.Toolbar_subtitleTextAppearance, + androidx.appcompat.R.style.TextAppearance_Widget_AppCompat_Toolbar_Subtitle); mTextSize = loadTextSizeFromTextAppearance(titleTextAppearance); mSubTextSize = loadTextSizeFromTextAppearance(subtitleTextAppearance); - TypedArray menuA = context.getTheme().obtainStyledAttributes(attrs, R.styleable.ActionBar, - defStyleAttr, R.style.ThemeOverlay_AppCompat); - int menuTextAppearance = menuA.getResourceId(R.styleable.AutoLayout_Layout_layout_auto_basewidth, - R.style.ThemeOverlay_AppCompat_ActionBar); + TypedArray menuA = context.getTheme().obtainStyledAttributes(attrs, androidx.appcompat.R.styleable.ActionBar, + defStyleAttr, androidx.appcompat.R.style.ThemeOverlay_AppCompat); + int menuTextAppearance = menuA.getResourceId(com.zhy.autolayout.R.styleable.AutoLayout_Layout_layout_auto_basewidth, + androidx.appcompat.R.style.ThemeOverlay_AppCompat_ActionBar); int menuTextSize = loadTextSizeFromTextAppearance(menuTextAppearance); //防止 menu 定义 textSize,而 Toolbar 无定义 textSize 时,title 的 textSize 随 menu 变化 @@ -63,11 +63,11 @@ public class AutoToolbar extends Toolbar { private int loadTextSizeFromTextAppearance(int textAppearanceResId) { TypedArray a = getContext().obtainStyledAttributes(textAppearanceResId, - R.styleable.TextAppearance); + androidx.appcompat.R.styleable.TextAppearance); try { - if (!DimenUtils.isPxVal(a.peekValue(R.styleable.TextAppearance_android_textSize))) + if (!DimenUtils.isPxVal(a.peekValue(androidx.appcompat.R.styleable.TextAppearance_android_textSize))) return NO_VALID; - return a.getDimensionPixelSize(R.styleable.TextAppearance_android_textSize, NO_VALID); + return a.getDimensionPixelSize(androidx.appcompat.R.styleable.TextAppearance_android_textSize, NO_VALID); } finally { a.recycle(); } diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/utils/CanBanScrollViewPager.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/utils/CanBanScrollViewPager.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/utils/ContactDatas.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/utils/ContactDatas.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/utils/ControllerActivity.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/utils/ControllerActivity.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/utils/GsonUtils.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/utils/GsonUtils.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/utils/HttpListener.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/utils/HttpListener.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/utils/HttpResponseListener.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/utils/HttpResponseListener.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/utils/HttpUtils.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/utils/HttpUtils.java old mode 100755 new mode 100644 index 673261c..8119926 --- a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/utils/HttpUtils.java +++ b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/utils/HttpUtils.java @@ -1,12 +1,13 @@ package com.bjzc.yfdxj.utils; import android.content.Context; +import android.content.pm.ApplicationInfo; import android.text.TextUtils; import android.util.Log; import androidx.annotation.NonNull; -import com.bjzc.yfdxj.BuildConfig; + import com.bjzc.yfdxj.contans.Contans; import java.io.IOException; @@ -134,7 +135,7 @@ public class HttpUtils { } public static void showLog(String logText) { - if (BuildConfig.LOG_ERROR) { + if (isApkInDebug(context)) { if(TextUtils.isEmpty(logText)){ Log.i("app", "logText is null"); }else{ @@ -142,4 +143,16 @@ public class HttpUtils { } } } + /** + * 判断当前应用是否是debug状态 + */ + + public static boolean isApkInDebug(Context context) { + try { + ApplicationInfo info = context.getApplicationInfo(); + return (info.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0; + } catch (Exception e) { + return false; + } + } } diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/utils/MqttSSLPublishServer.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/utils/MqttSSLPublishServer.java index a6430fa..a6b32fb 100644 --- a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/utils/MqttSSLPublishServer.java +++ b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/utils/MqttSSLPublishServer.java @@ -37,8 +37,8 @@ public class MqttSSLPublishServer { * 代理服务器ip地址 */ //private final String HOST = "tcp://39.101.173.20:1883"; - private final String HOST = "ssl://39.101.173.20:8883"; - //private final String HOST = "tcp://47.242.184.139:8883"; + //private final String HOST = "ssl://39.101.173.20:8883"; + private final String HOST = "tcp://47.242.184.139:1883"; //private final String HOST = "ssl://47.242.184.139:8883"; //private final String HOST = "tcp://113.107.214.27:1883"; @@ -65,7 +65,7 @@ public class MqttSSLPublishServer { * MQTT服务端连接密码 */ //private final String passWord = "public452131wW452131wW$"; - private final String passWord = "public"; + private final String passWord = "publish452131wW452131wW$"; /** * 消息发布质量 * 0:最多一次,即:<=1 @@ -109,8 +109,8 @@ public class MqttSSLPublishServer { // SSLSocketFactory socketFactory = MqttSSLSocketFactory.getSingleSocketFactory(context); // options.setSocketFactory(socketFactory); //mqtt服务器端单双向加密 - SSLSocketFactory socketFactory = MqttSSLSocketFactory.getTwoDirSocketFactory(" ",context); - options.setSocketFactory(socketFactory); + //SSLSocketFactory socketFactory = MqttSSLSocketFactory.getTwoDirSocketFactory(" ",context); + //options.setSocketFactory(socketFactory); // 设置回调 client.setCallback(new MqttCallbackExtended() { diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/utils/NetworkAvailableUtils.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/utils/NetworkAvailableUtils.java old mode 100755 new mode 100644 index 76bd8dd..1ec74ba --- a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/utils/NetworkAvailableUtils.java +++ b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/utils/NetworkAvailableUtils.java @@ -1,11 +1,15 @@ package com.bjzc.yfdxj.utils; +import android.Manifest; import android.content.Context; +import android.content.pm.PackageManager; import android.location.LocationManager; import android.net.ConnectivityManager; import android.net.NetworkInfo; import android.telephony.TelephonyManager; +import androidx.core.app.ActivityCompat; + import java.util.List; /** @@ -65,6 +69,16 @@ public class NetworkAvailableUtils { .getSystemService(Context.CONNECTIVITY_SERVICE); TelephonyManager mgrTel = (TelephonyManager) context .getSystemService(Context.TELEPHONY_SERVICE); + if (ActivityCompat.checkSelfPermission(context, Manifest.permission.READ_PHONE_STATE) != PackageManager.PERMISSION_GRANTED) { + // TODO: Consider calling + // ActivityCompat#requestPermissions + // here to request the missing permissions, and then overriding + // public void onRequestPermissionsResult(int requestCode, String[] permissions, + // int[] grantResults) + // to handle the case where the user grants the permission. See the documentation + // for ActivityCompat#requestPermissions for more details. + return false; + } return ((mgrConn.getActiveNetworkInfo() != null && mgrConn .getActiveNetworkInfo().getState() == NetworkInfo.State.CONNECTED) || mgrTel .getNetworkType() == TelephonyManager.NETWORK_TYPE_UMTS); diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/utils/NetworkUtil.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/utils/NetworkUtil.java index 4abdec9..1f2704e 100644 --- a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/utils/NetworkUtil.java +++ b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/utils/NetworkUtil.java @@ -33,6 +33,22 @@ public class NetworkUtil { return false; } + /** + * 当前网络是否连接wifi状态 + * + * @param context 上下文 + */ + public static boolean isWifiNetWorkStatus(Context context) { + NetworkInfo info = getActiveNetwork(context); + if (info == null) { + return false; + } + if (info.getType() == ConnectivityManager.TYPE_WIFI) { + return info.isAvailable(); + } + return false; + } + /** * 获取活动网络连接信息 * diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/utils/NoProgresshttpUtils.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/utils/NoProgresshttpUtils.java index 808bde0..4fff99f 100644 --- a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/utils/NoProgresshttpUtils.java +++ b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/utils/NoProgresshttpUtils.java @@ -4,10 +4,11 @@ package com.bjzc.yfdxj.utils; import android.app.Activity; import android.content.Context; +import android.content.pm.ApplicationInfo; import android.text.TextUtils; import android.util.Log; -import com.bjzc.yfdxj.BuildConfig; + import com.bjzc.yfdxj.R; import com.bjzc.yfdxj.contans.Contans; import com.google.gson.Gson; @@ -75,7 +76,7 @@ public class NoProgresshttpUtils { String token = RSAUtils.decryptBASE64StrLocal(tokenTemp);; String credential = "Bearer " + token; request.addHeader("Authorization", credential); - showLog(request.url()); + showLog(request.url(),mActivity); // if(!TextUtils.isEmpty(token)){ // showLog(credential); // showLog(new Gson().toJson(request.headers())); @@ -84,8 +85,8 @@ public class NoProgresshttpUtils { mQueue.add(what, request, new HttpResponseListenerNoProgress(request, callback)); } - public void showLog(String logText) { - if (BuildConfig.LOG_ERROR) { + public void showLog(String logText,Context context) { + if (isApkInDebug(context)) { if(TextUtils.isEmpty(logText)){ Log.i("app", "logText is null"); }else{ @@ -94,6 +95,19 @@ public class NoProgresshttpUtils { } } + /** + * 判断当前应用是否是debug状态 + */ + + public static boolean isApkInDebug(Context context) { + try { + ApplicationInfo info = context.getApplicationInfo(); + return (info.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0; + } catch (Exception e) { + return false; + } + } + /** * 取消这个sign这个标记的所有请求 * diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/utils/NohttpUtils.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/utils/NohttpUtils.java old mode 100755 new mode 100644 index f901e8d..0b8b3dd --- a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/utils/NohttpUtils.java +++ b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/utils/NohttpUtils.java @@ -2,10 +2,11 @@ package com.bjzc.yfdxj.utils; import android.app.Activity; import android.content.Context; +import android.content.pm.ApplicationInfo; import android.text.TextUtils; import android.util.Log; -import com.bjzc.yfdxj.BuildConfig; + import com.bjzc.yfdxj.R; import com.bjzc.yfdxj.contans.Contans; import com.google.gson.Gson; @@ -84,13 +85,13 @@ public class NohttpUtils { String token = RSAUtils.decryptBASE64StrLocal(tokenTemp);; String credential = "Bearer " + token; request.addHeader("Authorization", credential); - showLog(request.url()); + showLog(request.url(),mActivity); // if(!TextUtils.isEmpty(token)){ // showLog(credential); // showLog(new Gson().toJson(request.headers())); // } }else{ - showLog(request.url()); + showLog(request.url(),mActivity); } mQueue.add(what, request, new HttpResponseListener(mActivity, request, callback, canCanel, isLoading, msg)); } @@ -102,7 +103,7 @@ public class NohttpUtils { String token = RSAUtils.decryptBASE64StrLocal(tokenTemp);; String credential = "Bearer " + token; request.addHeader("Authorization", credential); - showLog(request.url()); + showLog(request.url(),mActivity); // if(!TextUtils.isEmpty(token)){ // showLog(credential); // showLog(new Gson().toJson(request.headers())); @@ -117,7 +118,7 @@ public class NohttpUtils { String token = RSAUtils.decryptBASE64StrLocal(tokenTemp);; String credential = "Bearer " + token; request.addHeader("Authorization", credential); - showLog(request.url()); + showLog(request.url(),context); // if(!TextUtils.isEmpty(token)){ // showLog(credential); // showLog(new Gson().toJson(request.headers())); @@ -126,8 +127,8 @@ public class NohttpUtils { mQueue.add(what, request, new HttpResponseListenerNoProgress(request, callback)); } - public void showLog(String logText) { - if (BuildConfig.LOG_ERROR) { + public void showLog(String logText,Context context) { + if (isApkInDebug(context)) { if(TextUtils.isEmpty(logText)){ Log.i("app", "logText is null"); }else{ @@ -136,6 +137,19 @@ public class NohttpUtils { } } + /** + * 判断当前应用是否是debug状态 + */ + + public static boolean isApkInDebug(Context context) { + try { + ApplicationInfo info = context.getApplicationInfo(); + return (info.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0; + } catch (Exception e) { + return false; + } + } + /** * 取消这个sign这个标记的所有请求 * diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/utils/OAToolbar.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/utils/OAToolbar.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/utils/RetrofitHttpUtils.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/utils/RetrofitHttpUtils.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/utils/SPUtils.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/utils/SPUtils.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/utils/StatusBarUtil.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/utils/StatusBarUtil.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/utils/UiUtlis.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/utils/UiUtlis.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/weight/AuditDialog.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/weight/AuditDialog.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/weight/AutoGridView.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/weight/AutoGridView.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/weight/AutoListView.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/weight/AutoListView.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/weight/AutoRadioGroup.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/weight/AutoRadioGroup.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/weight/ClearEditText.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/weight/ClearEditText.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/weight/DateTimePickDialog.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/weight/DateTimePickDialog.java old mode 100755 new mode 100644 index 660f0ba..bccfab6 --- a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/weight/DateTimePickDialog.java +++ b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/weight/DateTimePickDialog.java @@ -78,15 +78,23 @@ public class DateTimePickDialog extends Dialog implements View.OnClickListener, @Override public void onClick(View v) { - switch (v.getId()) { - case R.id.dialog_cancel: - dismiss(); - break; - case R.id.dialog_commit: - commitClickListener.confirm(outPutDateTime, outPutDateTime1,outPutDateTime2,outPutDateTime3); - dismiss(); - break; + if(v.getId()==R.id.dialog_cancel){ + dismiss(); } + if(v.getId()==R.id.dialog_commit){ + commitClickListener.confirm(outPutDateTime, outPutDateTime1,outPutDateTime2,outPutDateTime3); + dismiss(); + } + +// switch (v.getId()) { +// case R.id.dialog_cancel: +// dismiss(); +// break; +// case R.id.dialog_commit: +// commitClickListener.confirm(outPutDateTime, outPutDateTime1,outPutDateTime2,outPutDateTime3); +// dismiss(); +// break; +// } } public void onTimeChanged(TimePicker view, int hourOfDay, int minute) { diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/weight/GlideEngine.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/weight/GlideEngine.java index a44a33c..9aace56 100644 --- a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/weight/GlideEngine.java +++ b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/weight/GlideEngine.java @@ -161,7 +161,7 @@ public class GlideEngine implements ImageEngine { .override(180, 180) .centerCrop() .sizeMultiplier(0.5f) - .apply(new RequestOptions().placeholder(R.drawable.picture_image_placeholder)) + .apply(new RequestOptions().placeholder(com.luck.picture.lib.R.drawable.picture_image_placeholder)) .into(new BitmapImageViewTarget(imageView) { @Override protected void setResource(Bitmap resource) { @@ -204,7 +204,7 @@ public class GlideEngine implements ImageEngine { .load(url) .override(200, 200) .centerCrop() - .apply(new RequestOptions().placeholder(R.drawable.picture_image_placeholder)) + .apply(new RequestOptions().placeholder(com.luck.picture.lib.R.drawable.picture_image_placeholder)) .into(imageView); } diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/weight/InputLayout.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/weight/InputLayout.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/weight/ListDialog.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/weight/ListDialog.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/weight/LoadDialog.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/weight/LoadDialog.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/weight/NoscrollViewPager.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/weight/NoscrollViewPager.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/weight/NumAddSubView.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/weight/NumAddSubView.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/weight/SettingIpDialog.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/weight/SettingIpDialog.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/weight/UpdateAuditDialog.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/weight/UpdateAuditDialog.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/weight/WaitDialog.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/weight/WaitDialog.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/weight/toastviewbymyself.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/weight/toastviewbymyself.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/zxing/camera/AutoFocusCallback.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/zxing/camera/AutoFocusCallback.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/zxing/camera/CameraConfigurationManager.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/zxing/camera/CameraConfigurationManager.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/zxing/camera/CameraManager.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/zxing/camera/CameraManager.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/zxing/camera/FlashlightManager.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/zxing/camera/FlashlightManager.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/zxing/camera/PlanarYUVLuminanceSource.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/zxing/camera/PlanarYUVLuminanceSource.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/zxing/camera/PreviewCallback.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/zxing/camera/PreviewCallback.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/zxing/decoding/CaptureActivityHandler.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/zxing/decoding/CaptureActivityHandler.java old mode 100755 new mode 100644 index 9758bf6..97c03be --- a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/zxing/decoding/CaptureActivityHandler.java +++ b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/zxing/decoding/CaptureActivityHandler.java @@ -65,48 +65,86 @@ public final class CaptureActivityHandler extends Handler { @Override public void handleMessage(Message message) { - switch (message.what) { - case R.id.auto_focus: - //Log.d(TAG, "Got auto-focus message"); - // When one auto focus pass finishes, start another. This is the closest thing to - // continuous AF. It does seem to hunt background bit, but I'm not sure what else to do. - if (state == State.PREVIEW) { - CameraManager.get().requestAutoFocus(this, R.id.auto_focus); - } - break; - case R.id.restart_preview: - Log.d(TAG, "Got restart preview message"); - restartPreviewAndDecode(); - break; - case R.id.decode_succeeded: - Log.d(TAG, "Got decode succeeded message"); - state = State.SUCCESS; - Bundle bundle = message.getData(); - - /***********************************************************************/ - Bitmap barcode = bundle == null ? null : - (Bitmap) bundle.getParcelable(DecodeThread.BARCODE_BITMAP);//���ñ����߳� - - activity.handleDecode((Result) message.obj, barcode);//���ؽ��? /***********************************************************************/ - break; - case R.id.decode_failed: - // We're decoding as fast as possible, so when one decode fails, start another. - state = State.PREVIEW; - CameraManager.get().requestPreviewFrame(decodeThread.getHandler(), R.id.decode); - break; - case R.id.return_scan_result: - Log.d(TAG, "Got return scan result message"); - activity.setResult(Activity.RESULT_OK, (Intent) message.obj); - activity.finish(); - break; - case R.id.launch_product_query: - Log.d(TAG, "Got product query message"); - String url = (String) message.obj; - Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url)); - intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET); - activity.startActivity(intent); - break; + if(message.what==R.id.auto_focus){ + if (state == State.PREVIEW) { + CameraManager.get().requestAutoFocus(this, R.id.auto_focus); + } } + if(message.what==R.id.restart_preview){ + Log.d(TAG, "Got restart preview message"); + restartPreviewAndDecode(); + } + if(message.what==R.id.decode_succeeded){ + Log.d(TAG, "Got decode succeeded message"); + state = State.SUCCESS; + Bundle bundle = message.getData(); + + /***********************************************************************/ + Bitmap barcode = bundle == null ? null : + (Bitmap) bundle.getParcelable(DecodeThread.BARCODE_BITMAP);//���ñ����߳� + + activity.handleDecode((Result) message.obj, barcode); + /***********************************************************************/ + } + if(message.what==R.id.decode_failed){ + state = State.PREVIEW; + CameraManager.get().requestPreviewFrame(decodeThread.getHandler(), R.id.decode); + } + if(message.what==R.id.return_scan_result){ + Log.d(TAG, "Got return scan result message"); + activity.setResult(Activity.RESULT_OK, (Intent) message.obj); + activity.finish(); + } + if(message.what==R.id.launch_product_query){ + Log.d(TAG, "Got product query message"); + String url = (String) message.obj; + Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url)); + intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET); + activity.startActivity(intent); + } + +// switch (message.what) { +// case R.id.auto_focus: +// //Log.d(TAG, "Got auto-focus message"); +// // When one auto focus pass finishes, start another. This is the closest thing to +// // continuous AF. It does seem to hunt background bit, but I'm not sure what else to do. +// if (state == State.PREVIEW) { +// CameraManager.get().requestAutoFocus(this, R.id.auto_focus); +// } +// break; +// case R.id.restart_preview: +// Log.d(TAG, "Got restart preview message"); +// restartPreviewAndDecode(); +// break; +// case R.id.decode_succeeded: +// Log.d(TAG, "Got decode succeeded message"); +// state = State.SUCCESS; +// Bundle bundle = message.getData(); +// +// /***********************************************************************/ +// Bitmap barcode = bundle == null ? null : +// (Bitmap) bundle.getParcelable(DecodeThread.BARCODE_BITMAP);//���ñ����߳� +// +// activity.handleDecode((Result) message.obj, barcode);//���ؽ��? /***********************************************************************/ +// break; +// case R.id.decode_failed: +// // We're decoding as fast as possible, so when one decode fails, start another. +// state = State.PREVIEW; +// CameraManager.get().requestPreviewFrame(decodeThread.getHandler(), R.id.decode); +// break; +// case R.id.return_scan_result: +// Log.d(TAG, "Got return scan result message"); +// activity.setResult(Activity.RESULT_OK, (Intent) message.obj); +// activity.finish(); +// break; +// case R.id.launch_product_query: +// Log.d(TAG, "Got product query message"); +// String url = (String) message.obj; +// Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url)); +// intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET); +// activity.startActivity(intent); +// break; +// } } public void quitSynchronously() { diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/zxing/decoding/DecodeFormatManager.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/zxing/decoding/DecodeFormatManager.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/zxing/decoding/DecodeHandler.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/zxing/decoding/DecodeHandler.java old mode 100755 new mode 100644 index 62b7dc1..29d4293 --- a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/zxing/decoding/DecodeHandler.java +++ b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/zxing/decoding/DecodeHandler.java @@ -50,15 +50,21 @@ final class DecodeHandler extends Handler { @Override public void handleMessage(Message message) { - switch (message.what) { - case R.id.decode: - //Log.d(TAG, "Got decode message"); - decode((byte[]) message.obj, message.arg1, message.arg2); - break; - case R.id.quit: - Looper.myLooper().quit(); - break; + if(message.what==R.id.decode){ + decode((byte[]) message.obj, message.arg1, message.arg2); } + if(message.what==R.id.quit){ + Looper.myLooper().quit(); + } +// switch (message.what) { +// case R.id.decode: +// //Log.d(TAG, "Got decode message"); +// decode((byte[]) message.obj, message.arg1, message.arg2); +// break; +// case R.id.quit: +// Looper.myLooper().quit(); +// break; +// } } /** diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/zxing/decoding/DecodeThread.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/zxing/decoding/DecodeThread.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/zxing/decoding/FinishListener.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/zxing/decoding/FinishListener.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/zxing/decoding/InactivityTimer.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/zxing/decoding/InactivityTimer.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/zxing/decoding/Intents.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/zxing/decoding/Intents.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/zxing/view/ViewfinderResultPointCallback.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/zxing/view/ViewfinderResultPointCallback.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/java/com/bjzc/yfdxj/zxing/view/ViewfinderView.java b/YFDXJ/app/src/main/java/com/bjzc/yfdxj/zxing/view/ViewfinderView.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/drawable-xhdpi/head_photo.png b/YFDXJ/app/src/main/res/drawable-xhdpi/head_photo.png old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/drawable-xhdpi/ic_delete_menu.png b/YFDXJ/app/src/main/res/drawable-xhdpi/ic_delete_menu.png old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/drawable-xhdpi/icon_apply.png b/YFDXJ/app/src/main/res/drawable-xhdpi/icon_apply.png old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/drawable-xhdpi/icon_mentions.png b/YFDXJ/app/src/main/res/drawable-xhdpi/icon_mentions.png old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/drawable-xhdpi/icon_myphone.png b/YFDXJ/app/src/main/res/drawable-xhdpi/icon_myphone.png old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/drawable-xhdpi/icon_order.png b/YFDXJ/app/src/main/res/drawable-xhdpi/icon_order.png old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/drawable-xhdpi/icon_phone.png b/YFDXJ/app/src/main/res/drawable-xhdpi/icon_phone.png old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/drawable-xhdpi/icon_phone1.png b/YFDXJ/app/src/main/res/drawable-xhdpi/icon_phone1.png old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/drawable-xhdpi/icon_started.png b/YFDXJ/app/src/main/res/drawable-xhdpi/icon_started.png old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/drawable-xhdpi/logo_hz.png b/YFDXJ/app/src/main/res/drawable-xhdpi/logo_hz.png old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/drawable-xhdpi/search.png b/YFDXJ/app/src/main/res/drawable-xhdpi/search.png old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/drawable-xhdpi/tab_contacts.png b/YFDXJ/app/src/main/res/drawable-xhdpi/tab_contacts.png old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/drawable-xhdpi/tab_contacts_hover.png b/YFDXJ/app/src/main/res/drawable-xhdpi/tab_contacts_hover.png old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/drawable-xhdpi/tab_home.png b/YFDXJ/app/src/main/res/drawable-xhdpi/tab_home.png old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/drawable-xhdpi/tab_home_hover.png b/YFDXJ/app/src/main/res/drawable-xhdpi/tab_home_hover.png old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/drawable-xhdpi/tab_me.png b/YFDXJ/app/src/main/res/drawable-xhdpi/tab_me.png old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/drawable-xhdpi/tab_me_hover.png b/YFDXJ/app/src/main/res/drawable-xhdpi/tab_me_hover.png old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/drawable-xhdpi/wechat.png b/YFDXJ/app/src/main/res/drawable-xhdpi/wechat.png old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/drawable/auth_button_login.xml b/YFDXJ/app/src/main/res/drawable/auth_button_login.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/drawable/auth_tab_header_text.xml b/YFDXJ/app/src/main/res/drawable/auth_tab_header_text.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/drawable/bg_dj_edittext.xml b/YFDXJ/app/src/main/res/drawable/bg_dj_edittext.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/drawable/bg_edittext.xml b/YFDXJ/app/src/main/res/drawable/bg_edittext.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/drawable/bg_gray_solid.xml b/YFDXJ/app/src/main/res/drawable/bg_gray_solid.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/drawable/bg_tab.xml b/YFDXJ/app/src/main/res/drawable/bg_tab.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/drawable/border_line_while.xml b/YFDXJ/app/src/main/res/drawable/border_line_while.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/drawable/border_red_roval_sign.xml b/YFDXJ/app/src/main/res/drawable/border_red_roval_sign.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/drawable/btn_bg_red.xml b/YFDXJ/app/src/main/res/drawable/btn_bg_red.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/drawable/btnshape.xml b/YFDXJ/app/src/main/res/drawable/btnshape.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/drawable/card_background.xml b/YFDXJ/app/src/main/res/drawable/card_background.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/drawable/card_background_selector.xml b/YFDXJ/app/src/main/res/drawable/card_background_selector.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/drawable/card_state_pressed.xml b/YFDXJ/app/src/main/res/drawable/card_state_pressed.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/drawable/contacts.xml b/YFDXJ/app/src/main/res/drawable/contacts.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/drawable/dialog_radius.xml b/YFDXJ/app/src/main/res/drawable/dialog_radius.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/drawable/edit_bg.xml b/YFDXJ/app/src/main/res/drawable/edit_bg.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/drawable/edittext.xml b/YFDXJ/app/src/main/res/drawable/edittext.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/drawable/home.xml b/YFDXJ/app/src/main/res/drawable/home.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/drawable/me.xml b/YFDXJ/app/src/main/res/drawable/me.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/drawable/pressed_bg_blue88.xml b/YFDXJ/app/src/main/res/drawable/pressed_bg_blue88.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/drawable/presslr.xml b/YFDXJ/app/src/main/res/drawable/presslr.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/drawable/radiobutton.xml b/YFDXJ/app/src/main/res/drawable/radiobutton.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/drawable/radius.xml b/YFDXJ/app/src/main/res/drawable/radius.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/drawable/radius_a1.xml b/YFDXJ/app/src/main/res/drawable/radius_a1.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/drawable/radius_a2.xml b/YFDXJ/app/src/main/res/drawable/radius_a2.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/drawable/radius_a3.xml b/YFDXJ/app/src/main/res/drawable/radius_a3.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/drawable/radius_a4.xml b/YFDXJ/app/src/main/res/drawable/radius_a4.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/drawable/radius_b1.xml b/YFDXJ/app/src/main/res/drawable/radius_b1.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/drawable/radius_b2.xml b/YFDXJ/app/src/main/res/drawable/radius_b2.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/drawable/radius_c1.xml b/YFDXJ/app/src/main/res/drawable/radius_c1.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/drawable/radius_c7.xml b/YFDXJ/app/src/main/res/drawable/radius_c7.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/drawable/radius_d1.xml b/YFDXJ/app/src/main/res/drawable/radius_d1.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/drawable/radius_e2.xml b/YFDXJ/app/src/main/res/drawable/radius_e2.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/drawable/radius_e3.xml b/YFDXJ/app/src/main/res/drawable/radius_e3.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/drawable/red_dot.xml b/YFDXJ/app/src/main/res/drawable/red_dot.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/drawable/selector_list_item.xml b/YFDXJ/app/src/main/res/drawable/selector_list_item.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/drawable/shape.xml b/YFDXJ/app/src/main/res/drawable/shape.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/drawable/shape_corner.xml b/YFDXJ/app/src/main/res/drawable/shape_corner.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/drawable/shape_dialog_bg.xml b/YFDXJ/app/src/main/res/drawable/shape_dialog_bg.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/drawable/shape_edittext.xml b/YFDXJ/app/src/main/res/drawable/shape_edittext.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/drawable/text_red_circ.xml b/YFDXJ/app/src/main/res/drawable/text_red_circ.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/layout/acticity_mainaqjc.xml b/YFDXJ/app/src/main/res/layout/acticity_mainaqjc.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/layout/activity_aqjclb.xml b/YFDXJ/app/src/main/res/layout/activity_aqjclb.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/layout/activity_aqjclbinfo.xml b/YFDXJ/app/src/main/res/layout/activity_aqjclbinfo.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/layout/activity_aqjcrwlb.xml b/YFDXJ/app/src/main/res/layout/activity_aqjcrwlb.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/layout/activity_aqjcrwlbinfo.xml b/YFDXJ/app/src/main/res/layout/activity_aqjcrwlbinfo.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/layout/activity_aqjcrwsave.xml b/YFDXJ/app/src/main/res/layout/activity_aqjcrwsave.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/layout/activity_brqxd.xml b/YFDXJ/app/src/main/res/layout/activity_brqxd.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/layout/activity_bzqxd.xml b/YFDXJ/app/src/main/res/layout/activity_bzqxd.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/layout/activity_bzqxdzdy.xml b/YFDXJ/app/src/main/res/layout/activity_bzqxdzdy.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/layout/activity_capture.xml b/YFDXJ/app/src/main/res/layout/activity_capture.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/layout/activity_changepm.xml b/YFDXJ/app/src/main/res/layout/activity_changepm.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/layout/activity_changesblist.xml b/YFDXJ/app/src/main/res/layout/activity_changesblist.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/layout/activity_djlist.xml b/YFDXJ/app/src/main/res/layout/activity_djlist.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/layout/activity_dxxsblist.xml b/YFDXJ/app/src/main/res/layout/activity_dxxsblist.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/layout/activity_fzrlist.xml b/YFDXJ/app/src/main/res/layout/activity_fzrlist.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/layout/activity_gzqk_pushck.xml b/YFDXJ/app/src/main/res/layout/activity_gzqk_pushck.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/layout/activity_kccx.xml b/YFDXJ/app/src/main/res/layout/activity_kccx.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/layout/activity_kccxinfo.xml b/YFDXJ/app/src/main/res/layout/activity_kccxinfo.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/layout/activity_login.xml b/YFDXJ/app/src/main/res/layout/activity_login.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/layout/activity_main.xml b/YFDXJ/app/src/main/res/layout/activity_main.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/layout/activity_maindj.xml b/YFDXJ/app/src/main/res/layout/activity_maindj.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/layout/activity_mainxj.xml b/YFDXJ/app/src/main/res/layout/activity_mainxj.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/layout/activity_mainzjrw.xml b/YFDXJ/app/src/main/res/layout/activity_mainzjrw.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/layout/activity_newyul.xml b/YFDXJ/app/src/main/res/layout/activity_newyul.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/layout/activity_nfcinfo.xml b/YFDXJ/app/src/main/res/layout/activity_nfcinfo.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/layout/activity_ohersbinfo.xml b/YFDXJ/app/src/main/res/layout/activity_ohersbinfo.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/layout/activity_othersb.xml b/YFDXJ/app/src/main/res/layout/activity_othersb.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/layout/activity_pm.xml b/YFDXJ/app/src/main/res/layout/activity_pm.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/layout/activity_pminfo.xml b/YFDXJ/app/src/main/res/layout/activity_pminfo.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/layout/activity_qfbmlist.xml b/YFDXJ/app/src/main/res/layout/activity_qfbmlist.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/layout/activity_qfbzgdlist.xml b/YFDXJ/app/src/main/res/layout/activity_qfbzgdlist.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/layout/activity_qfgdzt.xml b/YFDXJ/app/src/main/res/layout/activity_qfgdzt.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/layout/activity_qfpmgdchange.xml b/YFDXJ/app/src/main/res/layout/activity_qfpmgdchange.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/layout/activity_qfpmgdlist.xml b/YFDXJ/app/src/main/res/layout/activity_qfpmgdlist.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/layout/activity_qfsblist.xml b/YFDXJ/app/src/main/res/layout/activity_qfsblist.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/layout/activity_qxgdcx.xml b/YFDXJ/app/src/main/res/layout/activity_qxgdcx.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/layout/activity_sblist.xml b/YFDXJ/app/src/main/res/layout/activity_sblist.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/layout/activity_sbxdjcjsb.xml b/YFDXJ/app/src/main/res/layout/activity_sbxdjcjsb.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/layout/activity_sbxdjgl.xml b/YFDXJ/app/src/main/res/layout/activity_sbxdjgl.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/layout/activity_sbxjcjsb.xml b/YFDXJ/app/src/main/res/layout/activity_sbxjcjsb.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/layout/activity_sdjgw.xml b/YFDXJ/app/src/main/res/layout/activity_sdjgw.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/layout/activity_sdjgz.xml b/YFDXJ/app/src/main/res/layout/activity_sdjgz.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/layout/activity_sdlb.xml b/YFDXJ/app/src/main/res/layout/activity_sdlb.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/layout/activity_sis.xml b/YFDXJ/app/src/main/res/layout/activity_sis.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/layout/activity_sxcdj.xml b/YFDXJ/app/src/main/res/layout/activity_sxcdj.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/layout/activity_tabmain.xml b/YFDXJ/app/src/main/res/layout/activity_tabmain.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/layout/activity_test.xml b/YFDXJ/app/src/main/res/layout/activity_test.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/layout/activity_tips.xml b/YFDXJ/app/src/main/res/layout/activity_tips.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/layout/activity_tjqxd.xml b/YFDXJ/app/src/main/res/layout/activity_tjqxd.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/layout/activity_wj_details.xml b/YFDXJ/app/src/main/res/layout/activity_wj_details.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/layout/activity_wjbxz.xml b/YFDXJ/app/src/main/res/layout/activity_wjbxz.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/layout/activity_xj.xml b/YFDXJ/app/src/main/res/layout/activity_xj.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/layout/activity_xjsblist.xml b/YFDXJ/app/src/main/res/layout/activity_xjsblist.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/layout/activity_xjzhtj.xml b/YFDXJ/app/src/main/res/layout/activity_xjzhtj.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/layout/activity_xs_back_log.xml b/YFDXJ/app/src/main/res/layout/activity_xs_back_log.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/layout/activity_xs_history.xml b/YFDXJ/app/src/main/res/layout/activity_xs_history.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/layout/activity_xscbgl.xml b/YFDXJ/app/src/main/res/layout/activity_xscbgl.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/layout/activity_xscbqy.xml b/YFDXJ/app/src/main/res/layout/activity_xscbqy.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/layout/activity_xscbqy_work.xml b/YFDXJ/app/src/main/res/layout/activity_xscbqy_work.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/layout/activity_xsgz.xml b/YFDXJ/app/src/main/res/layout/activity_xsgz.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/layout/activity_xsxzsbstate.xml b/YFDXJ/app/src/main/res/layout/activity_xsxzsbstate.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/layout/activity_ydckgl.xml b/YFDXJ/app/src/main/res/layout/activity_ydckgl.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/layout/activity_yul.xml b/YFDXJ/app/src/main/res/layout/activity_yul.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/layout/activity_yul_sb.xml b/YFDXJ/app/src/main/res/layout/activity_yul_sb.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/layout/activity_zgrwsave.xml b/YFDXJ/app/src/main/res/layout/activity_zgrwsave.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/layout/activity_zhtj.xml b/YFDXJ/app/src/main/res/layout/activity_zhtj.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/layout/activity_zjcx.xml b/YFDXJ/app/src/main/res/layout/activity_zjcx.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/layout/activity_zjdwcqk.xml b/YFDXJ/app/src/main/res/layout/activity_zjdwcqk.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/layout/activity_zjrwck.xml b/YFDXJ/app/src/main/res/layout/activity_zjrwck.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/layout/activity_zjrwinfo.xml b/YFDXJ/app/src/main/res/layout/activity_zjrwinfo.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/layout/activity_zjrwinfodetail.xml b/YFDXJ/app/src/main/res/layout/activity_zjrwinfodetail.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/layout/activity_zjrwsb.xml b/YFDXJ/app/src/main/res/layout/activity_zjrwsb.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/layout/activity_zjrwsblr.xml b/YFDXJ/app/src/main/res/layout/activity_zjrwsblr.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/layout/activity_zjrwsbluwcqk.xml b/YFDXJ/app/src/main/res/layout/activity_zjrwsbluwcqk.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/layout/activity_zjrwwjb.xml b/YFDXJ/app/src/main/res/layout/activity_zjrwwjb.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/layout/content_dialog.xml b/YFDXJ/app/src/main/res/layout/content_dialog.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/layout/cxzjrw_item.xml b/YFDXJ/app/src/main/res/layout/cxzjrw_item.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/layout/dialog_certificate.xml b/YFDXJ/app/src/main/res/layout/dialog_certificate.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/layout/dialog_challenge.xml b/YFDXJ/app/src/main/res/layout/dialog_challenge.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/layout/dialog_force_update_pwd.xml b/YFDXJ/app/src/main/res/layout/dialog_force_update_pwd.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/layout/dialog_force_update_pwd_with_old_pwd.xml b/YFDXJ/app/src/main/res/layout/dialog_force_update_pwd_with_old_pwd.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/layout/dialog_graph_check.xml b/YFDXJ/app/src/main/res/layout/dialog_graph_check.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/layout/dialog_pwd.xml b/YFDXJ/app/src/main/res/layout/dialog_pwd.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/layout/dialog_showgrqxgd.xml b/YFDXJ/app/src/main/res/layout/dialog_showgrqxgd.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/layout/dialog_sms.xml b/YFDXJ/app/src/main/res/layout/dialog_sms.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/layout/dialog_token.xml b/YFDXJ/app/src/main/res/layout/dialog_token.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/layout/dialog_update_pwd.xml b/YFDXJ/app/src/main/res/layout/dialog_update_pwd.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/layout/dialog_uploaddjrz.xml b/YFDXJ/app/src/main/res/layout/dialog_uploaddjrz.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/layout/djajhgz_item.xml b/YFDXJ/app/src/main/res/layout/djajhgz_item.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/layout/djajhsb_item.xml b/YFDXJ/app/src/main/res/layout/djajhsb_item.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/layout/djgwlist_item.xml b/YFDXJ/app/src/main/res/layout/djgwlist_item.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/layout/dlb_item.xml b/YFDXJ/app/src/main/res/layout/dlb_item.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/layout/dxxsblist_item_layout.xml b/YFDXJ/app/src/main/res/layout/dxxsblist_item_layout.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/layout/fragment_bz.xml b/YFDXJ/app/src/main/res/layout/fragment_bz.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/layout/fragment_cj.xml b/YFDXJ/app/src/main/res/layout/fragment_cj.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/layout/fragment_contact.xml b/YFDXJ/app/src/main/res/layout/fragment_contact.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/layout/fragment_djdsc.xml b/YFDXJ/app/src/main/res/layout/fragment_djdsc.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/layout/fragment_ff.xml b/YFDXJ/app/src/main/res/layout/fragment_ff.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/layout/fragment_fx.xml b/YFDXJ/app/src/main/res/layout/fragment_fx.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/layout/fragment_main.xml b/YFDXJ/app/src/main/res/layout/fragment_main.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/layout/fragment_mine.xml b/YFDXJ/app/src/main/res/layout/fragment_mine.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/layout/fragment_oldqxgd.xml b/YFDXJ/app/src/main/res/layout/fragment_oldqxgd.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/layout/fragment_qxgd.xml b/YFDXJ/app/src/main/res/layout/fragment_qxgd.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/layout/fragment_wj.xml b/YFDXJ/app/src/main/res/layout/fragment_wj.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/layout/fragment_xjcj.xml b/YFDXJ/app/src/main/res/layout/fragment_xjcj.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/layout/fragment_xjsc.xml b/YFDXJ/app/src/main/res/layout/fragment_xjsc.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/layout/fragment_xssc.xml b/YFDXJ/app/src/main/res/layout/fragment_xssc.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/layout/fragment_xzjh.xml b/YFDXJ/app/src/main/res/layout/fragment_xzjh.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/layout/fragment_xzxjjh.xml b/YFDXJ/app/src/main/res/layout/fragment_xzxjjh.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/layout/fragment_xzxsjh.xml b/YFDXJ/app/src/main/res/layout/fragment_xzxsjh.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/layout/fragment_xzzjrw.xml b/YFDXJ/app/src/main/res/layout/fragment_xzzjrw.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/layout/fragment_yjwsc.xml b/YFDXJ/app/src/main/res/layout/fragment_yjwsc.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/layout/fragment_ysc.xml b/YFDXJ/app/src/main/res/layout/fragment_ysc.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/layout/fragment_zjdsc.xml b/YFDXJ/app/src/main/res/layout/fragment_zjdsc.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/layout/fragment_zjrwlrsbqk.xml b/YFDXJ/app/src/main/res/layout/fragment_zjrwlrsbqk.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/layout/fragment_zjsblr.xml b/YFDXJ/app/src/main/res/layout/fragment_zjsblr.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/layout/gridview_item.xml b/YFDXJ/app/src/main/res/layout/gridview_item.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/layout/gv_filter_image.xml b/YFDXJ/app/src/main/res/layout/gv_filter_image.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/layout/item_aqjclb.xml b/YFDXJ/app/src/main/res/layout/item_aqjclb.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/layout/item_bpbjinfo.xml b/YFDXJ/app/src/main/res/layout/item_bpbjinfo.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/layout/item_child.xml b/YFDXJ/app/src/main/res/layout/item_child.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/layout/item_child_wj_details.xml b/YFDXJ/app/src/main/res/layout/item_child_wj_details.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/layout/item_contact1.xml b/YFDXJ/app/src/main/res/layout/item_contact1.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/layout/item_content.xml b/YFDXJ/app/src/main/res/layout/item_content.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/layout/item_fxts.xml b/YFDXJ/app/src/main/res/layout/item_fxts.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/layout/item_group_wj_details.xml b/YFDXJ/app/src/main/res/layout/item_group_wj_details.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/layout/item_gzblist.xml b/YFDXJ/app/src/main/res/layout/item_gzblist.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/layout/item_parent.xml b/YFDXJ/app/src/main/res/layout/item_parent.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/layout/item_pminfo.xml b/YFDXJ/app/src/main/res/layout/item_pminfo.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/layout/item_qfgdlist.xml b/YFDXJ/app/src/main/res/layout/item_qfgdlist.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/layout/item_qfkccxlb.xml b/YFDXJ/app/src/main/res/layout/item_qfkccxlb.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/layout/item_qfsblist.xml b/YFDXJ/app/src/main/res/layout/item_qfsblist.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/layout/item_qxdinfo.xml b/YFDXJ/app/src/main/res/layout/item_qxdinfo.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/layout/item_sqjcrwlist.xml b/YFDXJ/app/src/main/res/layout/item_sqjcrwlist.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/layout/item_zjrw.xml b/YFDXJ/app/src/main/res/layout/item_zjrw.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/layout/layout_audit_dialog.xml b/YFDXJ/app/src/main/res/layout/layout_audit_dialog.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/layout/layout_base.xml b/YFDXJ/app/src/main/res/layout/layout_base.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/layout/layout_datetime_dialog.xml b/YFDXJ/app/src/main/res/layout/layout_datetime_dialog.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/layout/layout_dialog.xml b/YFDXJ/app/src/main/res/layout/layout_dialog.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/layout/layout_dialog_bottom.xml b/YFDXJ/app/src/main/res/layout/layout_dialog_bottom.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/layout/layout_input.xml b/YFDXJ/app/src/main/res/layout/layout_input.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/layout/layout_take_out_time.xml b/YFDXJ/app/src/main/res/layout/layout_take_out_time.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/layout/layout_update_audit_dialog.xml b/YFDXJ/app/src/main/res/layout/layout_update_audit_dialog.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/layout/layout_update_dialog_bottom.xml b/YFDXJ/app/src/main/res/layout/layout_update_dialog_bottom.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/layout/listview_dialog_item.xml b/YFDXJ/app/src/main/res/layout/listview_dialog_item.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/layout/num_add_sub_layout.xml b/YFDXJ/app/src/main/res/layout/num_add_sub_layout.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/layout/qcdialog_edt.xml b/YFDXJ/app/src/main/res/layout/qcdialog_edt.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/layout/qy_work_cb_item.xml b/YFDXJ/app/src/main/res/layout/qy_work_cb_item.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/layout/qy_work_dj_item.xml b/YFDXJ/app/src/main/res/layout/qy_work_dj_item.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/layout/qy_work_xs_item.xml b/YFDXJ/app/src/main/res/layout/qy_work_xs_item.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/layout/sblcsave_item.xml b/YFDXJ/app/src/main/res/layout/sblcsave_item.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/layout/sblcsavedata_item2.xml b/YFDXJ/app/src/main/res/layout/sblcsavedata_item2.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/layout/sblcsavedata_item_wtqy.xml b/YFDXJ/app/src/main/res/layout/sblcsavedata_item_wtqy.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/layout/scjh_item.xml b/YFDXJ/app/src/main/res/layout/scjh_item.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/layout/scxscb_item.xml b/YFDXJ/app/src/main/res/layout/scxscb_item.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/layout/scxsjh_item.xml b/YFDXJ/app/src/main/res/layout/scxsjh_item.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/layout/sczjdrw_item.xml b/YFDXJ/app/src/main/res/layout/sczjdrw_item.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/layout/search_view.xml b/YFDXJ/app/src/main/res/layout/search_view.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/layout/setting_ip.xml b/YFDXJ/app/src/main/res/layout/setting_ip.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/layout/srghdialog.xml b/YFDXJ/app/src/main/res/layout/srghdialog.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/layout/toastviewlayout.xml b/YFDXJ/app/src/main/res/layout/toastviewlayout.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/layout/toolbar.xml b/YFDXJ/app/src/main/res/layout/toolbar.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/layout/toolbar2.xml b/YFDXJ/app/src/main/res/layout/toolbar2.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/layout/wjbck_item.xml b/YFDXJ/app/src/main/res/layout/wjbck_item.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/layout/wjbxz_item.xml b/YFDXJ/app/src/main/res/layout/wjbxz_item.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/layout/xsgz_item.xml b/YFDXJ/app/src/main/res/layout/xsgz_item.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/layout/xsgzsblist_item.xml b/YFDXJ/app/src/main/res/layout/xsgzsblist_item.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/layout/xsjh_item2.xml b/YFDXJ/app/src/main/res/layout/xsjh_item2.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/layout/xsjhqy_item.xml b/YFDXJ/app/src/main/res/layout/xsjhqy_item.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/layout/xzjh_item.xml b/YFDXJ/app/src/main/res/layout/xzjh_item.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/layout/xzxsjh_item.xml b/YFDXJ/app/src/main/res/layout/xzxsjh_item.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/layout/zjb_item.xml b/YFDXJ/app/src/main/res/layout/zjb_item.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/layout/zjdtitle_item.xml b/YFDXJ/app/src/main/res/layout/zjdtitle_item.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/layout/zjrwsb_item.xml b/YFDXJ/app/src/main/res/layout/zjrwsb_item.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/layout/zjrwxj_item.xml b/YFDXJ/app/src/main/res/layout/zjrwxj_item.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/layout/zkd_item.xml b/YFDXJ/app/src/main/res/layout/zkd_item.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/menu/menu.xml b/YFDXJ/app/src/main/res/menu/menu.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/mipmap-hdpi/find_add_img.png b/YFDXJ/app/src/main/res/mipmap-hdpi/find_add_img.png old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/mipmap-hdpi/ic_launcher.png b/YFDXJ/app/src/main/res/mipmap-hdpi/ic_launcher.png old mode 100755 new mode 100644 index cde69bc..2493f61 Binary files a/YFDXJ/app/src/main/res/mipmap-hdpi/ic_launcher.png and b/YFDXJ/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/YFDXJ/app/src/main/res/mipmap-hdpi/ic_launcher_round.png b/YFDXJ/app/src/main/res/mipmap-hdpi/ic_launcher_round.png old mode 100755 new mode 100644 index 9a078e3..706d440 Binary files a/YFDXJ/app/src/main/res/mipmap-hdpi/ic_launcher_round.png and b/YFDXJ/app/src/main/res/mipmap-hdpi/ic_launcher_round.png differ diff --git a/YFDXJ/app/src/main/res/mipmap-hdpi/icon10.png b/YFDXJ/app/src/main/res/mipmap-hdpi/icon10.png old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/mipmap-hdpi/icon11.png b/YFDXJ/app/src/main/res/mipmap-hdpi/icon11.png old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/mipmap-hdpi/icon13.png b/YFDXJ/app/src/main/res/mipmap-hdpi/icon13.png old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/mipmap-hdpi/icon3.png b/YFDXJ/app/src/main/res/mipmap-hdpi/icon3.png old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/mipmap-hdpi/icon6.png b/YFDXJ/app/src/main/res/mipmap-hdpi/icon6.png old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/mipmap-hdpi/icon8.png b/YFDXJ/app/src/main/res/mipmap-hdpi/icon8.png old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/mipmap-hdpi/icon9.png b/YFDXJ/app/src/main/res/mipmap-hdpi/icon9.png old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/mipmap-hdpi/icon_dxj.png b/YFDXJ/app/src/main/res/mipmap-hdpi/icon_dxj.png old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/mipmap-hdpi/icon_psw.png b/YFDXJ/app/src/main/res/mipmap-hdpi/icon_psw.png old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/mipmap-hdpi/icon_user.png b/YFDXJ/app/src/main/res/mipmap-hdpi/icon_user.png old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/mipmap-hdpi/icon_xs1.png b/YFDXJ/app/src/main/res/mipmap-hdpi/icon_xs1.png old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/mipmap-hdpi/icon_xs5.png b/YFDXJ/app/src/main/res/mipmap-hdpi/icon_xs5.png old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/mipmap-mdpi/ic_launcher.png b/YFDXJ/app/src/main/res/mipmap-mdpi/ic_launcher.png old mode 100755 new mode 100644 index c133a0c..92fa4e4 Binary files a/YFDXJ/app/src/main/res/mipmap-mdpi/ic_launcher.png and b/YFDXJ/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/YFDXJ/app/src/main/res/mipmap-mdpi/ic_launcher_round.png b/YFDXJ/app/src/main/res/mipmap-mdpi/ic_launcher_round.png old mode 100755 new mode 100644 index efc028a..50fb718 Binary files a/YFDXJ/app/src/main/res/mipmap-mdpi/ic_launcher_round.png and b/YFDXJ/app/src/main/res/mipmap-mdpi/ic_launcher_round.png differ diff --git a/YFDXJ/app/src/main/res/mipmap-xhdpi/ac_back_icon.png b/YFDXJ/app/src/main/res/mipmap-xhdpi/ac_back_icon.png old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/mipmap-xhdpi/add.png b/YFDXJ/app/src/main/res/mipmap-xhdpi/add.png old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/mipmap-xhdpi/add_gray.png b/YFDXJ/app/src/main/res/mipmap-xhdpi/add_gray.png old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/mipmap-xhdpi/back.png b/YFDXJ/app/src/main/res/mipmap-xhdpi/back.png old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/mipmap-xhdpi/background.jpeg b/YFDXJ/app/src/main/res/mipmap-xhdpi/background.jpeg old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/mipmap-xhdpi/del.png b/YFDXJ/app/src/main/res/mipmap-xhdpi/del.png old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/mipmap-xhdpi/edit.png b/YFDXJ/app/src/main/res/mipmap-xhdpi/edit.png old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/YFDXJ/app/src/main/res/mipmap-xhdpi/ic_launcher.png old mode 100755 new mode 100644 index bfa42f0..39d7b2f Binary files a/YFDXJ/app/src/main/res/mipmap-xhdpi/ic_launcher.png and b/YFDXJ/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/YFDXJ/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/YFDXJ/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png old mode 100755 new mode 100644 index 3af2608..e0de4c8 Binary files a/YFDXJ/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png and b/YFDXJ/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png differ diff --git a/YFDXJ/app/src/main/res/mipmap-xhdpi/icon_phone.png b/YFDXJ/app/src/main/res/mipmap-xhdpi/icon_phone.png old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/mipmap-xhdpi/icon_plan.png b/YFDXJ/app/src/main/res/mipmap-xhdpi/icon_plan.png old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/mipmap-xhdpi/logo1.png b/YFDXJ/app/src/main/res/mipmap-xhdpi/logo1.png old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/mipmap-xhdpi/logo_hz.png b/YFDXJ/app/src/main/res/mipmap-xhdpi/logo_hz.png old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/mipmap-xhdpi/packup.png b/YFDXJ/app/src/main/res/mipmap-xhdpi/packup.png old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/mipmap-xhdpi/sub.png b/YFDXJ/app/src/main/res/mipmap-xhdpi/sub.png old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/mipmap-xhdpi/unfold.png b/YFDXJ/app/src/main/res/mipmap-xhdpi/unfold.png old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/mipmap-xhdpi/zx.png b/YFDXJ/app/src/main/res/mipmap-xhdpi/zx.png old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/YFDXJ/app/src/main/res/mipmap-xxhdpi/ic_launcher.png old mode 100755 new mode 100644 index 324e72c..1b55422 Binary files a/YFDXJ/app/src/main/res/mipmap-xxhdpi/ic_launcher.png and b/YFDXJ/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/YFDXJ/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/YFDXJ/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png old mode 100755 new mode 100644 index 9bec2e6..dbdec8b Binary files a/YFDXJ/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png and b/YFDXJ/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png differ diff --git a/YFDXJ/app/src/main/res/mipmap-xxhdpi/icon_area.png b/YFDXJ/app/src/main/res/mipmap-xxhdpi/icon_area.png old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/YFDXJ/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png old mode 100755 new mode 100644 index aee44e1..10c8506 Binary files a/YFDXJ/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png and b/YFDXJ/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/YFDXJ/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/YFDXJ/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png old mode 100755 new mode 100644 index 34947cd..27f71fc Binary files a/YFDXJ/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png and b/YFDXJ/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png differ diff --git a/YFDXJ/app/src/main/res/raw/beep.ogg b/YFDXJ/app/src/main/res/raw/beep.ogg old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/values/arrays.xml b/YFDXJ/app/src/main/res/values/arrays.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/values/attrs.xml b/YFDXJ/app/src/main/res/values/attrs.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/values/colors.xml b/YFDXJ/app/src/main/res/values/colors.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/values/dimens.xml b/YFDXJ/app/src/main/res/values/dimens.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/values/ids.xml b/YFDXJ/app/src/main/res/values/ids.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/values/strings.xml b/YFDXJ/app/src/main/res/values/strings.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/values/styles.xml b/YFDXJ/app/src/main/res/values/styles.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/main/res/xml/file_paths.xml b/YFDXJ/app/src/main/res/xml/file_paths.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/test/java/com/bjzc/yfdxj/ExampleUnitTest.java b/YFDXJ/app/src/test/java/com/bjzc/yfdxj/ExampleUnitTest.java old mode 100755 new mode 100644 diff --git a/YFDXJ/app/src/test/java/com/rehome/yfdxj/ExampleUnitTest.java b/YFDXJ/app/src/test/java/com/rehome/yfdxj/ExampleUnitTest.java old mode 100755 new mode 100644 diff --git a/YFDXJ/autolayout/build.gradle b/YFDXJ/autolayout/build.gradle new file mode 100644 index 0000000..3986e80 --- /dev/null +++ b/YFDXJ/autolayout/build.gradle @@ -0,0 +1,15 @@ +apply plugin: 'com.android.library' + +android { + compileSdk 34 + defaultConfig { + minSdk 24 + targetSdk 34 + testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" + } + namespace 'com.zhy.autolayout' +} + +dependencies { + implementation 'androidx.appcompat:appcompat:1.7.0' +} \ No newline at end of file diff --git a/YFDXJ/autolayout/src/androidTest/java/zhy/com/autolayout/ApplicationTest.java b/YFDXJ/autolayout/src/androidTest/java/zhy/com/autolayout/ApplicationTest.java new file mode 100644 index 0000000..5ca6320 --- /dev/null +++ b/YFDXJ/autolayout/src/androidTest/java/zhy/com/autolayout/ApplicationTest.java @@ -0,0 +1,15 @@ +package zhy.com.autolayout; + +import android.app.Application; +import android.test.ApplicationTestCase; + +/** + * Testing Fundamentals + */ +public class ApplicationTest extends ApplicationTestCase +{ + public ApplicationTest() + { + super(Application.class); + } +} \ No newline at end of file diff --git a/YFDXJ/autolayout/src/main/AndroidManifest.xml b/YFDXJ/autolayout/src/main/AndroidManifest.xml new file mode 100644 index 0000000..972c3a8 --- /dev/null +++ b/YFDXJ/autolayout/src/main/AndroidManifest.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/YFDXJ/autolayout/src/main/java/com/zhy/autolayout/AutoFrameLayout.java b/YFDXJ/autolayout/src/main/java/com/zhy/autolayout/AutoFrameLayout.java new file mode 100644 index 0000000..595d933 --- /dev/null +++ b/YFDXJ/autolayout/src/main/java/com/zhy/autolayout/AutoFrameLayout.java @@ -0,0 +1,125 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.zhy.autolayout; + +import android.annotation.TargetApi; +import android.content.Context; +import android.os.Build; +import android.util.AttributeSet; +import android.view.ViewGroup; +import android.widget.FrameLayout; +import com.zhy.autolayout.utils.AutoLayoutHelper; + +public class AutoFrameLayout extends FrameLayout +{ + private final AutoLayoutHelper mHelper = new AutoLayoutHelper(this); + + public AutoFrameLayout(Context context) + { + super(context); + } + + public AutoFrameLayout(Context context, AttributeSet attrs) + { + super(context, attrs); + } + + public AutoFrameLayout(Context context, AttributeSet attrs, int defStyleAttr) + { + super(context, attrs, defStyleAttr); + } + + @TargetApi(Build.VERSION_CODES.LOLLIPOP) + public AutoFrameLayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + } + + @Override + public LayoutParams generateLayoutParams(AttributeSet attrs) + { + return new LayoutParams(getContext(), attrs); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) + { + if (!isInEditMode()) + { + mHelper.adjustChildren(); + } + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + } + + @Override + protected void onLayout(boolean changed, int left, int top, int right, int bottom) + { + super.onLayout(changed, left, top, right, bottom); + } + + public static class LayoutParams extends FrameLayout.LayoutParams + implements AutoLayoutHelper.AutoLayoutParams + { + private AutoLayoutInfo mAutoLayoutInfo; + + public LayoutParams(Context c, AttributeSet attrs) + { + super(c, attrs); + + mAutoLayoutInfo = AutoLayoutHelper.getAutoLayoutInfo(c, attrs); + } + + public LayoutParams(int width, int height) + { + super(width, height); + } + + public LayoutParams(int width, int height, int gravity) + { + super(width, height, gravity); + } + + public LayoutParams(ViewGroup.LayoutParams source) + { + super(source); + } + + public LayoutParams(MarginLayoutParams source) + { + super(source); + } + + public LayoutParams(FrameLayout.LayoutParams source) + { + super((MarginLayoutParams) source); + gravity = source.gravity; + } + + public LayoutParams(LayoutParams source) + { + this((FrameLayout.LayoutParams) source); + mAutoLayoutInfo = source.mAutoLayoutInfo; + } + + @Override + public AutoLayoutInfo getAutoLayoutInfo() + { + return mAutoLayoutInfo; + } + + + } +} diff --git a/YFDXJ/autolayout/src/main/java/com/zhy/autolayout/AutoLayoutActivity.java b/YFDXJ/autolayout/src/main/java/com/zhy/autolayout/AutoLayoutActivity.java new file mode 100644 index 0000000..1138d9c --- /dev/null +++ b/YFDXJ/autolayout/src/main/java/com/zhy/autolayout/AutoLayoutActivity.java @@ -0,0 +1,45 @@ +package com.zhy.autolayout; + +import android.content.Context; +import android.util.AttributeSet; +import android.view.View; +;import androidx.appcompat.app.AppCompatActivity; + +/** + * Created by zhy on 15/11/19. + */ +public class AutoLayoutActivity extends AppCompatActivity +{ + private static final String LAYOUT_LINEARLAYOUT = "LinearLayout"; + private static final String LAYOUT_FRAMELAYOUT = "FrameLayout"; + private static final String LAYOUT_RELATIVELAYOUT = "RelativeLayout"; + + + @Override + public View onCreateView(String name, Context context, AttributeSet attrs) + { + View view = null; + if (name.equals(LAYOUT_FRAMELAYOUT)) + { + view = new AutoFrameLayout(context, attrs); + } + + if (name.equals(LAYOUT_LINEARLAYOUT)) + { + view = new AutoLinearLayout(context, attrs); + } + + if (name.equals(LAYOUT_RELATIVELAYOUT)) + { + view = new AutoRelativeLayout(context, attrs); + } + + if (view != null) return view; + + return super.onCreateView(name, context, attrs); + } + + + + +} diff --git a/YFDXJ/autolayout/src/main/java/com/zhy/autolayout/AutoLayoutInfo.java b/YFDXJ/autolayout/src/main/java/com/zhy/autolayout/AutoLayoutInfo.java new file mode 100644 index 0000000..ebe2a2a --- /dev/null +++ b/YFDXJ/autolayout/src/main/java/com/zhy/autolayout/AutoLayoutInfo.java @@ -0,0 +1,155 @@ +package com.zhy.autolayout; + +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; + +import com.zhy.autolayout.attr.Attrs; +import com.zhy.autolayout.attr.AutoAttr; +import com.zhy.autolayout.attr.HeightAttr; +import com.zhy.autolayout.attr.MarginBottomAttr; +import com.zhy.autolayout.attr.MarginLeftAttr; +import com.zhy.autolayout.attr.MarginRightAttr; +import com.zhy.autolayout.attr.MarginTopAttr; +import com.zhy.autolayout.attr.MaxHeightAttr; +import com.zhy.autolayout.attr.MaxWidthAttr; +import com.zhy.autolayout.attr.MinHeightAttr; +import com.zhy.autolayout.attr.MinWidthAttr; +import com.zhy.autolayout.attr.PaddingBottomAttr; +import com.zhy.autolayout.attr.PaddingLeftAttr; +import com.zhy.autolayout.attr.PaddingRightAttr; +import com.zhy.autolayout.attr.PaddingTopAttr; +import com.zhy.autolayout.attr.TextSizeAttr; +import com.zhy.autolayout.attr.WidthAttr; + +import java.util.ArrayList; +import java.util.List; + +public class AutoLayoutInfo +{ + private List autoAttrs = new ArrayList<>(); + + public void addAttr(AutoAttr autoAttr) + { + autoAttrs.add(autoAttr); + } + + + public void fillAttrs(View view) + { + for (AutoAttr autoAttr : autoAttrs) + { + autoAttr.apply(view); + } + } + + + public static AutoLayoutInfo getAttrFromView(View view, int attrs, int base) + { + ViewGroup.LayoutParams params = view.getLayoutParams(); + if (params == null) return null; + AutoLayoutInfo autoLayoutInfo = new AutoLayoutInfo(); + + // width & height + if ((attrs & Attrs.WIDTH) != 0 && params.width > 0) + { + autoLayoutInfo.addAttr(WidthAttr.generate(params.width, base)); + } + + if ((attrs & Attrs.HEIGHT) != 0 && params.height > 0) + { + autoLayoutInfo.addAttr(HeightAttr.generate(params.height, base)); + } + + //margin + if (params instanceof ViewGroup.MarginLayoutParams) + { + if ((attrs & Attrs.MARGIN) != 0) + { + autoLayoutInfo.addAttr(MarginLeftAttr.generate(((ViewGroup.MarginLayoutParams) params).leftMargin, base)); + autoLayoutInfo.addAttr(MarginTopAttr.generate(((ViewGroup.MarginLayoutParams) params).topMargin, base)); + autoLayoutInfo.addAttr(MarginRightAttr.generate(((ViewGroup.MarginLayoutParams) params).rightMargin, base)); + autoLayoutInfo.addAttr(MarginBottomAttr.generate(((ViewGroup.MarginLayoutParams) params).bottomMargin, base)); + } + if ((attrs & Attrs.MARGIN_LEFT) != 0) + { + autoLayoutInfo.addAttr(MarginLeftAttr.generate(((ViewGroup.MarginLayoutParams) params).leftMargin, base)); + } + if ((attrs & Attrs.MARGIN_TOP) != 0) + { + autoLayoutInfo.addAttr(MarginTopAttr.generate(((ViewGroup.MarginLayoutParams) params).topMargin, base)); + } + if ((attrs & Attrs.MARGIN_RIGHT) != 0) + { + autoLayoutInfo.addAttr(MarginRightAttr.generate(((ViewGroup.MarginLayoutParams) params).rightMargin, base)); + } + if ((attrs & Attrs.MARGIN_BOTTOM) != 0) + { + autoLayoutInfo.addAttr(MarginBottomAttr.generate(((ViewGroup.MarginLayoutParams) params).bottomMargin, base)); + } + } + + //padding + if ((attrs & Attrs.PADDING) != 0) + { + autoLayoutInfo.addAttr(PaddingLeftAttr.generate(view.getPaddingLeft(), base)); + autoLayoutInfo.addAttr(PaddingTopAttr.generate(view.getPaddingTop(), base)); + autoLayoutInfo.addAttr(PaddingRightAttr.generate(view.getPaddingRight(), base)); + autoLayoutInfo.addAttr(PaddingBottomAttr.generate(view.getPaddingBottom(), base)); + } + if ((attrs & Attrs.PADDING_LEFT) != 0) + { + autoLayoutInfo.addAttr(MarginLeftAttr.generate(view.getPaddingLeft(), base)); + } + if ((attrs & Attrs.PADDING_TOP) != 0) + { + autoLayoutInfo.addAttr(MarginTopAttr.generate(view.getPaddingTop(), base)); + } + if ((attrs & Attrs.PADDING_RIGHT) != 0) + { + autoLayoutInfo.addAttr(MarginRightAttr.generate(view.getPaddingRight(), base)); + } + if ((attrs & Attrs.PADDING_BOTTOM) != 0) + { + autoLayoutInfo.addAttr(MarginBottomAttr.generate(view.getPaddingBottom(), base)); + } + + //minWidth ,maxWidth , minHeight , maxHeight + if ((attrs & Attrs.MIN_WIDTH) != 0) + { + autoLayoutInfo.addAttr(MinWidthAttr.generate(MinWidthAttr.getMinWidth(view), base)); + } + if ((attrs & Attrs.MAX_WIDTH) != 0) + { + autoLayoutInfo.addAttr(MaxWidthAttr.generate(MaxWidthAttr.getMaxWidth(view), base)); + } + if ((attrs & Attrs.MIN_HEIGHT) != 0) + { + autoLayoutInfo.addAttr(MinHeightAttr.generate(MinHeightAttr.getMinHeight(view), base)); + } + if ((attrs & Attrs.MAX_HEIGHT) != 0) + { + autoLayoutInfo.addAttr(MaxHeightAttr.generate(MaxHeightAttr.getMaxHeight(view), base)); + } + + //textsize + + if (view instanceof TextView) + { + if ((attrs & Attrs.TEXTSIZE) != 0) + { + autoLayoutInfo.addAttr(TextSizeAttr.generate((int) ((TextView) view).getTextSize(), base)); + } + } + return autoLayoutInfo; + } + + + @Override + public String toString() + { + return "AutoLayoutInfo{" + + "autoAttrs=" + autoAttrs + + '}'; + } +} \ No newline at end of file diff --git a/YFDXJ/autolayout/src/main/java/com/zhy/autolayout/AutoLinearLayout.java b/YFDXJ/autolayout/src/main/java/com/zhy/autolayout/AutoLinearLayout.java new file mode 100644 index 0000000..f0f93d6 --- /dev/null +++ b/YFDXJ/autolayout/src/main/java/com/zhy/autolayout/AutoLinearLayout.java @@ -0,0 +1,97 @@ +package com.zhy.autolayout; + +import android.annotation.TargetApi; +import android.content.Context; +import android.os.Build; +import android.util.AttributeSet; +import android.view.ViewGroup; +import android.widget.LinearLayout; + +import com.zhy.autolayout.utils.AutoLayoutHelper; + +/** + * Created by zhy on 15/6/30. + */ +public class AutoLinearLayout extends LinearLayout +{ + + private AutoLayoutHelper mHelper = new AutoLayoutHelper(this); + + public AutoLinearLayout(Context context) { + super(context); + } + + public AutoLinearLayout(Context context, AttributeSet attrs) { + super(context, attrs); + } + + @TargetApi(Build.VERSION_CODES.HONEYCOMB) + public AutoLinearLayout(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + } + + @TargetApi(Build.VERSION_CODES.LOLLIPOP) + public AutoLinearLayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) + { + if (!isInEditMode()) + mHelper.adjustChildren(); + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + } + + + @Override + protected void onLayout(boolean changed, int l, int t, int r, int b) + { + super.onLayout(changed, l, t, r, b); + } + + + @Override + public LayoutParams generateLayoutParams(AttributeSet attrs) + { + return new AutoLinearLayout.LayoutParams(getContext(), attrs); + } + + + public static class LayoutParams extends LinearLayout.LayoutParams + implements AutoLayoutHelper.AutoLayoutParams + { + private AutoLayoutInfo mAutoLayoutInfo; + + public LayoutParams(Context c, AttributeSet attrs) + { + super(c, attrs); + mAutoLayoutInfo = AutoLayoutHelper.getAutoLayoutInfo(c, attrs); + } + + @Override + public AutoLayoutInfo getAutoLayoutInfo() + { + return mAutoLayoutInfo; + } + + + public LayoutParams(int width, int height) + { + super(width, height); + } + + + public LayoutParams(ViewGroup.LayoutParams source) + { + super(source); + } + + public LayoutParams(MarginLayoutParams source) + { + super(source); + } + + } + +} diff --git a/YFDXJ/autolayout/src/main/java/com/zhy/autolayout/AutoRelativeLayout.java b/YFDXJ/autolayout/src/main/java/com/zhy/autolayout/AutoRelativeLayout.java new file mode 100644 index 0000000..c107e57 --- /dev/null +++ b/YFDXJ/autolayout/src/main/java/com/zhy/autolayout/AutoRelativeLayout.java @@ -0,0 +1,107 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.zhy.autolayout; + +import android.annotation.TargetApi; +import android.content.Context; +import android.os.Build; +import android.util.AttributeSet; +import android.view.ViewGroup; +import android.widget.RelativeLayout; + +import com.zhy.autolayout.utils.AutoLayoutHelper; + +public class AutoRelativeLayout extends RelativeLayout +{ + private final AutoLayoutHelper mHelper = new AutoLayoutHelper(this); + + public AutoRelativeLayout(Context context) + { + super(context); + } + + public AutoRelativeLayout(Context context, AttributeSet attrs) + { + super(context, attrs); + } + + public AutoRelativeLayout(Context context, AttributeSet attrs, int defStyle) + { + super(context, attrs, defStyle); + } + + @TargetApi(Build.VERSION_CODES.LOLLIPOP) + public AutoRelativeLayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + } + + @Override + public LayoutParams generateLayoutParams(AttributeSet attrs) + { + return new LayoutParams(getContext(), attrs); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) + { + if (!isInEditMode()) + mHelper.adjustChildren(); + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + } + + @Override + protected void onLayout(boolean changed, int left, int top, int right, int bottom) + { + super.onLayout(changed, left, top, right, bottom); + } + + + public static class LayoutParams extends RelativeLayout.LayoutParams + implements AutoLayoutHelper.AutoLayoutParams + { + private AutoLayoutInfo mAutoLayoutInfo; + + public LayoutParams(Context c, AttributeSet attrs) + { + super(c, attrs); + mAutoLayoutInfo = AutoLayoutHelper.getAutoLayoutInfo(c, attrs); + } + + public LayoutParams(int width, int height) + { + super(width, height); + } + + public LayoutParams(ViewGroup.LayoutParams source) + { + super(source); + } + + public LayoutParams(MarginLayoutParams source) + { + super(source); + } + + @Override + public AutoLayoutInfo getAutoLayoutInfo() + { + return mAutoLayoutInfo; + } + + + } +} diff --git a/YFDXJ/autolayout/src/main/java/com/zhy/autolayout/attr/Attrs.java b/YFDXJ/autolayout/src/main/java/com/zhy/autolayout/attr/Attrs.java new file mode 100644 index 0000000..774d803 --- /dev/null +++ b/YFDXJ/autolayout/src/main/java/com/zhy/autolayout/attr/Attrs.java @@ -0,0 +1,28 @@ +package com.zhy.autolayout.attr; + +/** + * Created by zhy on 15/12/5. + *

+ * 与attrs.xml中数值对应 + */ +public interface Attrs +{ + public static final int WIDTH = 1; + public static final int HEIGHT = WIDTH << 1; + public static final int TEXTSIZE = HEIGHT << 1; + public static final int PADDING = TEXTSIZE << 1; + public static final int MARGIN = PADDING << 1; + public static final int MARGIN_LEFT = MARGIN << 1; + public static final int MARGIN_TOP = MARGIN_LEFT << 1; + public static final int MARGIN_RIGHT = MARGIN_TOP << 1; + public static final int MARGIN_BOTTOM = MARGIN_RIGHT << 1; + public static final int PADDING_LEFT = MARGIN_BOTTOM << 1; + public static final int PADDING_TOP = PADDING_LEFT << 1; + public static final int PADDING_RIGHT = PADDING_TOP << 1; + public static final int PADDING_BOTTOM = PADDING_RIGHT << 1; + public static final int MIN_WIDTH = PADDING_BOTTOM << 1; + public static final int MAX_WIDTH = MIN_WIDTH << 1; + public static final int MIN_HEIGHT = MAX_WIDTH << 1; + public static final int MAX_HEIGHT = MIN_HEIGHT << 1; + +} diff --git a/YFDXJ/autolayout/src/main/java/com/zhy/autolayout/attr/AutoAttr.java b/YFDXJ/autolayout/src/main/java/com/zhy/autolayout/attr/AutoAttr.java new file mode 100644 index 0000000..0f1259f --- /dev/null +++ b/YFDXJ/autolayout/src/main/java/com/zhy/autolayout/attr/AutoAttr.java @@ -0,0 +1,126 @@ +package com.zhy.autolayout.attr; + +import android.view.View; + +import com.zhy.autolayout.utils.AutoUtils; +import com.zhy.autolayout.utils.L; + + +/** + * Created by zhy on 15/12/4. + */ +public abstract class AutoAttr +{ + public static final int BASE_WIDTH = 1; + public static final int BASE_HEIGHT = 2; + public static final int BASE_DEFAULT = 3; + + protected int pxVal; + protected int baseWidth; + protected int baseHeight; + + /* + protected boolean isBaseWidth; + protected boolean isBaseDefault; + + public AutoAttr(int pxVal) + { + this.pxVal = pxVal; + isBaseDefault = true; + } + + public AutoAttr(int pxVal, boolean isBaseWidth) + { + this.pxVal = pxVal; + this.isBaseWidth = isBaseWidth; + } + */ + + public AutoAttr(int pxVal, int baseWidth, int baseHeight) + { + this.pxVal = pxVal; + this.baseWidth = baseWidth; + this.baseHeight = baseHeight; + } + + public void apply(View view) + { + + boolean log = view.getTag() != null && view.getTag().toString().equals("auto"); + + if (log) + { + L.e(" pxVal = " + pxVal + " ," + this.getClass().getSimpleName()); + } + int val; + if (useDefault()) + { + val = defaultBaseWidth() ? getPercentWidthSize() : getPercentHeightSize(); + if (log) + { + L.e(" useDefault val= " + val); + } + } else if (baseWidth()) + { + val = getPercentWidthSize(); + if (log) + { + L.e(" baseWidth val= " + val); + } + } else + { + val = getPercentHeightSize(); + if (log) + { + L.e(" baseHeight val= " + val); + } + } + + if (val > 0) + val = Math.max(val, 1);//for very thin divider + execute(view, val); + } + + protected int getPercentWidthSize() + { + return AutoUtils.getPercentWidthSizeBigger(pxVal); + } + + protected int getPercentHeightSize() + { + return AutoUtils.getPercentHeightSizeBigger(pxVal); + } + + + protected boolean baseWidth() + { + return contains(baseWidth, attrVal()); + } + + protected boolean useDefault() + { + return !contains(baseHeight, attrVal()) && !contains(baseWidth, attrVal()); + } + + protected boolean contains(int baseVal, int flag) + { + return (baseVal & flag) != 0; + } + + protected abstract int attrVal(); + + protected abstract boolean defaultBaseWidth(); + + protected abstract void execute(View view, int val); + + + @Override + public String toString() + { + return "AutoAttr{" + + "pxVal=" + pxVal + + ", baseWidth=" + baseWidth() + + ", defaultBaseWidth=" + defaultBaseWidth() + + '}'; + } +} diff --git a/YFDXJ/autolayout/src/main/java/com/zhy/autolayout/attr/HeightAttr.java b/YFDXJ/autolayout/src/main/java/com/zhy/autolayout/attr/HeightAttr.java new file mode 100644 index 0000000..c754c90 --- /dev/null +++ b/YFDXJ/autolayout/src/main/java/com/zhy/autolayout/attr/HeightAttr.java @@ -0,0 +1,54 @@ +package com.zhy.autolayout.attr; + +import android.view.View; +import android.view.ViewGroup; + +/** + * Created by zhy on 15/12/5. + */ +public class HeightAttr extends AutoAttr +{ + public HeightAttr(int pxVal, int baseWidth, int baseHeight) + { + super(pxVal, baseWidth, baseHeight); + } + + @Override + protected int attrVal() + { + return Attrs.HEIGHT; + } + + @Override + protected boolean defaultBaseWidth() + { + return false; + } + + @Override + protected void execute(View view, int val) + { + ViewGroup.LayoutParams lp = view.getLayoutParams(); + lp.height = val; + } + + public static HeightAttr generate(int val, int baseFlag) + { + HeightAttr heightAttr = null; + switch (baseFlag) + { + case AutoAttr.BASE_WIDTH: + heightAttr = new HeightAttr(val, Attrs.HEIGHT, 0); + break; + case AutoAttr.BASE_HEIGHT: + heightAttr = new HeightAttr(val, 0, Attrs.HEIGHT); + break; + case AutoAttr.BASE_DEFAULT: + heightAttr = new HeightAttr(val, 0, 0); + break; + } + return heightAttr; + } + + +} diff --git a/YFDXJ/autolayout/src/main/java/com/zhy/autolayout/attr/MarginAttr.java b/YFDXJ/autolayout/src/main/java/com/zhy/autolayout/attr/MarginAttr.java new file mode 100644 index 0000000..8769fbe --- /dev/null +++ b/YFDXJ/autolayout/src/main/java/com/zhy/autolayout/attr/MarginAttr.java @@ -0,0 +1,52 @@ +package com.zhy.autolayout.attr; + +import android.view.View; +import android.view.ViewGroup; + +/** + * Created by zhy on 15/12/5. + */ +public class MarginAttr extends AutoAttr +{ + public MarginAttr(int pxVal, int baseWidth, int baseHeight) + { + super(pxVal, baseWidth, baseHeight); + } + + @Override + protected int attrVal() + { + return Attrs.MARGIN; + } + + @Override + protected boolean defaultBaseWidth() + { + return false; + } + + @Override + public void apply(View view) + { + if (!(view.getLayoutParams() instanceof ViewGroup.MarginLayoutParams)) + { + return; + } + if (useDefault()) + { + ViewGroup.MarginLayoutParams lp = (ViewGroup.MarginLayoutParams) view.getLayoutParams(); + lp.leftMargin = lp.rightMargin = getPercentWidthSize(); + lp.topMargin = lp.bottomMargin = getPercentHeightSize(); + return; + } + super.apply(view); + } + + @Override + protected void execute(View view, int val) + { + ViewGroup.MarginLayoutParams lp = (ViewGroup.MarginLayoutParams) view.getLayoutParams(); + lp.leftMargin = lp.rightMargin = lp.topMargin = lp.bottomMargin = val; + } + +} diff --git a/YFDXJ/autolayout/src/main/java/com/zhy/autolayout/attr/MarginBottomAttr.java b/YFDXJ/autolayout/src/main/java/com/zhy/autolayout/attr/MarginBottomAttr.java new file mode 100644 index 0000000..7d78272 --- /dev/null +++ b/YFDXJ/autolayout/src/main/java/com/zhy/autolayout/attr/MarginBottomAttr.java @@ -0,0 +1,56 @@ +package com.zhy.autolayout.attr; + +import android.view.View; +import android.view.ViewGroup; + +/** + * Created by zhy on 15/12/5. + */ +public class MarginBottomAttr extends AutoAttr +{ + public MarginBottomAttr(int pxVal, int baseWidth, int baseHeight) + { + super(pxVal, baseWidth, baseHeight); + } + + @Override + protected int attrVal() + { + return Attrs.MARGIN_BOTTOM; + } + + @Override + protected boolean defaultBaseWidth() + { + return false; + } + + @Override + protected void execute(View view, int val) + { + if(!(view.getLayoutParams() instanceof ViewGroup.MarginLayoutParams)) + { + return ; + } + ViewGroup.MarginLayoutParams lp = (ViewGroup.MarginLayoutParams) view.getLayoutParams(); + lp.bottomMargin = val; + } + + public static MarginBottomAttr generate(int val, int baseFlag) + { + MarginBottomAttr attr = null; + switch (baseFlag) + { + case AutoAttr.BASE_WIDTH: + attr = new MarginBottomAttr(val, Attrs.MARGIN_BOTTOM, 0); + break; + case AutoAttr.BASE_HEIGHT: + attr = new MarginBottomAttr(val, 0, Attrs.MARGIN_BOTTOM); + break; + case AutoAttr.BASE_DEFAULT: + attr = new MarginBottomAttr(val, 0, 0); + break; + } + return attr; + } +} diff --git a/YFDXJ/autolayout/src/main/java/com/zhy/autolayout/attr/MarginLeftAttr.java b/YFDXJ/autolayout/src/main/java/com/zhy/autolayout/attr/MarginLeftAttr.java new file mode 100644 index 0000000..4bc5feb --- /dev/null +++ b/YFDXJ/autolayout/src/main/java/com/zhy/autolayout/attr/MarginLeftAttr.java @@ -0,0 +1,56 @@ +package com.zhy.autolayout.attr; + +import android.view.View; +import android.view.ViewGroup; + +/** + * Created by zhy on 15/12/5. + */ +public class MarginLeftAttr extends AutoAttr +{ + public MarginLeftAttr(int pxVal, int baseWidth, int baseHeight) + { + super(pxVal, baseWidth, baseHeight); + } + + @Override + protected int attrVal() + { + return Attrs.MARGIN_LEFT; + } + + @Override + protected boolean defaultBaseWidth() + { + return true; + } + + @Override + protected void execute(View view, int val) + { + if (!(view.getLayoutParams() instanceof ViewGroup.MarginLayoutParams)) + { + return; + } + ViewGroup.MarginLayoutParams lp = (ViewGroup.MarginLayoutParams) view.getLayoutParams(); + lp.leftMargin = val; + } + + public static MarginLeftAttr generate(int val, int baseFlag) + { + MarginLeftAttr attr = null; + switch (baseFlag) + { + case AutoAttr.BASE_WIDTH: + attr = new MarginLeftAttr(val, Attrs.MARGIN_LEFT, 0); + break; + case AutoAttr.BASE_HEIGHT: + attr = new MarginLeftAttr(val, 0, Attrs.MARGIN_LEFT); + break; + case AutoAttr.BASE_DEFAULT: + attr = new MarginLeftAttr(val, 0, 0); + break; + } + return attr; + } +} diff --git a/YFDXJ/autolayout/src/main/java/com/zhy/autolayout/attr/MarginRightAttr.java b/YFDXJ/autolayout/src/main/java/com/zhy/autolayout/attr/MarginRightAttr.java new file mode 100644 index 0000000..4f80198 --- /dev/null +++ b/YFDXJ/autolayout/src/main/java/com/zhy/autolayout/attr/MarginRightAttr.java @@ -0,0 +1,57 @@ +package com.zhy.autolayout.attr; + +import android.view.View; +import android.view.ViewGroup; + +/** + * Created by zhy on 15/12/5. + */ +public class MarginRightAttr extends AutoAttr +{ + public MarginRightAttr(int pxVal, int baseWidth, int baseHeight) + { + super(pxVal, baseWidth, baseHeight); + } + + @Override + protected int attrVal() + { + return Attrs.MARGIN_RIGHT; + } + + @Override + protected boolean defaultBaseWidth() + { + return true; + } + + @Override + protected void execute(View view, int val) + { + if (!(view.getLayoutParams() instanceof ViewGroup.MarginLayoutParams)) + { + return; + } + ViewGroup.MarginLayoutParams lp = (ViewGroup.MarginLayoutParams) view.getLayoutParams(); + lp.rightMargin = val; + } + + + public static MarginRightAttr generate(int val, int baseFlag) + { + MarginRightAttr attr = null; + switch (baseFlag) + { + case AutoAttr.BASE_WIDTH: + attr = new MarginRightAttr(val, Attrs.MARGIN_RIGHT, 0); + break; + case AutoAttr.BASE_HEIGHT: + attr = new MarginRightAttr(val, 0, Attrs.MARGIN_RIGHT); + break; + case AutoAttr.BASE_DEFAULT: + attr = new MarginRightAttr(val, 0, 0); + break; + } + return attr; + } +} diff --git a/YFDXJ/autolayout/src/main/java/com/zhy/autolayout/attr/MarginTopAttr.java b/YFDXJ/autolayout/src/main/java/com/zhy/autolayout/attr/MarginTopAttr.java new file mode 100644 index 0000000..d0821b5 --- /dev/null +++ b/YFDXJ/autolayout/src/main/java/com/zhy/autolayout/attr/MarginTopAttr.java @@ -0,0 +1,58 @@ +package com.zhy.autolayout.attr; + +import android.view.View; +import android.view.ViewGroup; + +/** + * Created by zhy on 15/12/5. + */ +public class MarginTopAttr extends AutoAttr +{ + public MarginTopAttr(int pxVal, int baseWidth, int baseHeight) + { + super(pxVal, baseWidth, baseHeight); + } + + @Override + protected int attrVal() + { + return Attrs.MARGIN_TOP; + } + + @Override + protected boolean defaultBaseWidth() + { + return false; + } + + @Override + protected void execute(View view, int val) + { + if (!(view.getLayoutParams() instanceof ViewGroup.MarginLayoutParams)) + { + return; + } + ViewGroup.MarginLayoutParams lp = (ViewGroup.MarginLayoutParams) view.getLayoutParams(); + lp.topMargin = val; + + } + + + public static MarginTopAttr generate(int val, int baseFlag) + { + MarginTopAttr attr = null; + switch (baseFlag) + { + case AutoAttr.BASE_WIDTH: + attr = new MarginTopAttr(val, Attrs.MARGIN_TOP, 0); + break; + case AutoAttr.BASE_HEIGHT: + attr = new MarginTopAttr(val, 0, Attrs.MARGIN_TOP); + break; + case AutoAttr.BASE_DEFAULT: + attr = new MarginTopAttr(val, 0, 0); + break; + } + return attr; + } +} diff --git a/YFDXJ/autolayout/src/main/java/com/zhy/autolayout/attr/MaxHeightAttr.java b/YFDXJ/autolayout/src/main/java/com/zhy/autolayout/attr/MaxHeightAttr.java new file mode 100644 index 0000000..9af66af --- /dev/null +++ b/YFDXJ/autolayout/src/main/java/com/zhy/autolayout/attr/MaxHeightAttr.java @@ -0,0 +1,70 @@ +package com.zhy.autolayout.attr; + +import android.view.View; + +import java.lang.reflect.Method; + +/** + * Created by zhy on 15/12/24. + */ +public class MaxHeightAttr extends AutoAttr +{ + public MaxHeightAttr(int pxVal, int baseWidth, int baseHeight) + { + super(pxVal, baseWidth, baseHeight); + } + + @Override + protected int attrVal() + { + return Attrs.MAX_HEIGHT; + } + + @Override + protected boolean defaultBaseWidth() + { + return false; + } + + @Override + protected void execute(View view, int val) + { + try + { + Method setMaxWidthMethod = view.getClass().getMethod("setMaxHeight", int.class); + setMaxWidthMethod.invoke(view, val); + } catch (Exception ignore) + { + } + } + + public static MaxHeightAttr generate(int val, int baseFlag) + { + MaxHeightAttr attr = null; + switch (baseFlag) + { + case AutoAttr.BASE_WIDTH: + attr = new MaxHeightAttr(val, Attrs.MAX_HEIGHT, 0); + break; + case AutoAttr.BASE_HEIGHT: + attr = new MaxHeightAttr(val, 0, Attrs.MAX_HEIGHT); + break; + case AutoAttr.BASE_DEFAULT: + attr = new MaxHeightAttr(val, 0, 0); + break; + } + return attr; + } + + public static int getMaxHeight(View view) + { + try + { + Method setMaxWidthMethod = view.getClass().getMethod("getMaxHeight"); + return (int) setMaxWidthMethod.invoke(view); + } catch (Exception ignore) + { + } + return 0; + } +} diff --git a/YFDXJ/autolayout/src/main/java/com/zhy/autolayout/attr/MaxWidthAttr.java b/YFDXJ/autolayout/src/main/java/com/zhy/autolayout/attr/MaxWidthAttr.java new file mode 100644 index 0000000..51a4a95 --- /dev/null +++ b/YFDXJ/autolayout/src/main/java/com/zhy/autolayout/attr/MaxWidthAttr.java @@ -0,0 +1,70 @@ +package com.zhy.autolayout.attr; + +import android.view.View; + +import java.lang.reflect.Method; + +/** + * Created by zhy on 15/12/24. + */ +public class MaxWidthAttr extends AutoAttr +{ + public MaxWidthAttr(int pxVal, int baseWidth, int baseHeight) + { + super(pxVal, baseWidth, baseHeight); + } + + @Override + protected int attrVal() + { + return Attrs.MAX_WIDTH; + } + + @Override + protected boolean defaultBaseWidth() + { + return true; + } + + @Override + protected void execute(View view, int val) + { + try + { + Method setMaxWidthMethod = view.getClass().getMethod("setMaxWidth", int.class); + setMaxWidthMethod.invoke(view, val); + } catch (Exception ignore) + { + } + } + + public static MaxWidthAttr generate(int val, int baseFlag) + { + MaxWidthAttr attr = null; + switch (baseFlag) + { + case AutoAttr.BASE_WIDTH: + attr = new MaxWidthAttr(val, Attrs.MAX_WIDTH, 0); + break; + case AutoAttr.BASE_HEIGHT: + attr = new MaxWidthAttr(val, 0, Attrs.MAX_WIDTH); + break; + case AutoAttr.BASE_DEFAULT: + attr = new MaxWidthAttr(val, 0, 0); + break; + } + return attr; + } + + public static int getMaxWidth(View view) + { + try + { + Method setMaxWidthMethod = view.getClass().getMethod("getMaxWidth"); + return (int) setMaxWidthMethod.invoke(view); + } catch (Exception ignore) + { + } + return 0; + } +} diff --git a/YFDXJ/autolayout/src/main/java/com/zhy/autolayout/attr/MinHeightAttr.java b/YFDXJ/autolayout/src/main/java/com/zhy/autolayout/attr/MinHeightAttr.java new file mode 100644 index 0000000..4b271bb --- /dev/null +++ b/YFDXJ/autolayout/src/main/java/com/zhy/autolayout/attr/MinHeightAttr.java @@ -0,0 +1,81 @@ +package com.zhy.autolayout.attr; + +import android.os.Build; +import android.view.View; + +import java.lang.reflect.Field; + +/** + * Created by zhy on 15/12/24. + */ +public class MinHeightAttr extends AutoAttr +{ + public MinHeightAttr(int pxVal, int baseWidth, int baseHeight) + { + super(pxVal, baseWidth, baseHeight); + } + + @Override + protected int attrVal() + { + return Attrs.MIN_HEIGHT; + } + + @Override + protected boolean defaultBaseWidth() + { + return false; + } + + @Override + protected void execute(View view, int val) + { + try + { + view.setMinimumHeight(val); +// Method setMaxWidthMethod = view.getClass().getMethod("setMinHeight", int.class); +// setMaxWidthMethod.invoke(view, val); + } catch (Exception ignore) + { + } + } + + public static MinHeightAttr generate(int val, int baseFlag) + { + MinHeightAttr attr = null; + switch (baseFlag) + { + case AutoAttr.BASE_WIDTH: + attr = new MinHeightAttr(val, Attrs.MIN_HEIGHT, 0); + break; + case AutoAttr.BASE_HEIGHT: + attr = new MinHeightAttr(val, 0, Attrs.MIN_HEIGHT); + break; + case AutoAttr.BASE_DEFAULT: + attr = new MinHeightAttr(val, 0, 0); + break; + } + return attr; + } + + public static int getMinHeight(View view) + { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) + { + return view.getMinimumHeight(); + } else + { + try + { + Field minHeight = view.getClass().getField("mMinHeight"); + minHeight.setAccessible(true); + return (int) minHeight.get(view); + } catch (Exception e) + { + } + } + + return 0; + } + +} diff --git a/YFDXJ/autolayout/src/main/java/com/zhy/autolayout/attr/MinWidthAttr.java b/YFDXJ/autolayout/src/main/java/com/zhy/autolayout/attr/MinWidthAttr.java new file mode 100644 index 0000000..438ef59 --- /dev/null +++ b/YFDXJ/autolayout/src/main/java/com/zhy/autolayout/attr/MinWidthAttr.java @@ -0,0 +1,77 @@ +package com.zhy.autolayout.attr; + +import android.os.Build; +import android.view.View; + +import java.lang.reflect.Field; + +/** + * Created by zhy on 15/12/24. + */ +public class MinWidthAttr extends AutoAttr +{ + public MinWidthAttr(int pxVal, int baseWidth, int baseHeight) + { + super(pxVal, baseWidth, baseHeight); + } + + @Override + protected int attrVal() + { + return Attrs.MIN_WIDTH; + } + + @Override + protected boolean defaultBaseWidth() + { + return true; + } + + @Override + protected void execute(View view, int val) + { + try + { +// Method setMaxWidthMethod = view.getClass().getMethod("setMinWidth", int.class); +// setMaxWidthMethod.invoke(view, val); + } catch (Exception ignore) + { + } + + view.setMinimumWidth(val); + } + + public static int getMinWidth(View view) + { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) + return view.getMinimumWidth(); + try + { + Field minWidth = view.getClass().getField("mMinWidth"); + minWidth.setAccessible(true); + return (int) minWidth.get(view); + } catch (Exception ignore) + { + } + return 0; + } + + + public static MinWidthAttr generate(int val, int baseFlag) + { + MinWidthAttr attr = null; + switch (baseFlag) + { + case AutoAttr.BASE_WIDTH: + attr = new MinWidthAttr(val, Attrs.MIN_WIDTH, 0); + break; + case AutoAttr.BASE_HEIGHT: + attr = new MinWidthAttr(val, 0, Attrs.MIN_WIDTH); + break; + case AutoAttr.BASE_DEFAULT: + attr = new MinWidthAttr(val, 0, 0); + break; + } + return attr; + } +} diff --git a/YFDXJ/autolayout/src/main/java/com/zhy/autolayout/attr/PaddingAttr.java b/YFDXJ/autolayout/src/main/java/com/zhy/autolayout/attr/PaddingAttr.java new file mode 100644 index 0000000..b0869af --- /dev/null +++ b/YFDXJ/autolayout/src/main/java/com/zhy/autolayout/attr/PaddingAttr.java @@ -0,0 +1,49 @@ +package com.zhy.autolayout.attr; + +import android.view.View; + +/** + * Created by zhy on 15/12/5. + */ +public class PaddingAttr extends AutoAttr +{ + public PaddingAttr(int pxVal, int baseWidth, int baseHeight) + { + super(pxVal, baseWidth, baseHeight); + } + + @Override + protected int attrVal() + { + return Attrs.PADDING; + } + + @Override + public void apply(View view) + { + int l, t, r, b; + if (useDefault()) + { + l = r = getPercentWidthSize(); + t = b = getPercentHeightSize(); + view.setPadding(l, t, r, b); + return; + } + super.apply(view); + } + + @Override + protected boolean defaultBaseWidth() + { + return false; + } + + @Override + protected void execute(View view, int val) + { + view.setPadding(val, val, val, val); + } + + + +} diff --git a/YFDXJ/autolayout/src/main/java/com/zhy/autolayout/attr/PaddingBottomAttr.java b/YFDXJ/autolayout/src/main/java/com/zhy/autolayout/attr/PaddingBottomAttr.java new file mode 100644 index 0000000..cbb4af9 --- /dev/null +++ b/YFDXJ/autolayout/src/main/java/com/zhy/autolayout/attr/PaddingBottomAttr.java @@ -0,0 +1,56 @@ +package com.zhy.autolayout.attr; + +import android.view.View; + +/** + * Created by zhy on 15/12/5. + */ +public class PaddingBottomAttr extends AutoAttr +{ + public PaddingBottomAttr(int pxVal, int baseWidth, int baseHeight) + { + super(pxVal, baseWidth, baseHeight); + } + + @Override + protected int attrVal() + { + return Attrs.PADDING_BOTTOM; + } + + @Override + protected boolean defaultBaseWidth() + { + return false; + } + + @Override + protected void execute(View view, int val) + { + int l = view.getPaddingLeft(); + int t = view.getPaddingTop(); + int r = view.getPaddingRight(); + int b = val; + view.setPadding(l, t, r, b); + + } + + + public static PaddingBottomAttr generate(int val, int baseFlag) + { + PaddingBottomAttr attr = null; + switch (baseFlag) + { + case AutoAttr.BASE_WIDTH: + attr = new PaddingBottomAttr(val, Attrs.PADDING_BOTTOM, 0); + break; + case AutoAttr.BASE_HEIGHT: + attr = new PaddingBottomAttr(val, 0, Attrs.PADDING_BOTTOM); + break; + case AutoAttr.BASE_DEFAULT: + attr = new PaddingBottomAttr(val, 0, 0); + break; + } + return attr; + } +} diff --git a/YFDXJ/autolayout/src/main/java/com/zhy/autolayout/attr/PaddingLeftAttr.java b/YFDXJ/autolayout/src/main/java/com/zhy/autolayout/attr/PaddingLeftAttr.java new file mode 100644 index 0000000..960fbd2 --- /dev/null +++ b/YFDXJ/autolayout/src/main/java/com/zhy/autolayout/attr/PaddingLeftAttr.java @@ -0,0 +1,56 @@ +package com.zhy.autolayout.attr; + +import android.view.View; + +/** + * Created by zhy on 15/12/5. + */ +public class PaddingLeftAttr extends AutoAttr +{ + public PaddingLeftAttr(int pxVal, int baseWidth, int baseHeight) + { + super(pxVal, baseWidth, baseHeight); + } + + @Override + protected int attrVal() + { + return Attrs.PADDING_LEFT; + } + + @Override + protected boolean defaultBaseWidth() + { + return true; + } + + @Override + protected void execute(View view, int val) + { + int l = val; + int t = view.getPaddingTop(); + int r = view.getPaddingRight(); + int b = view.getPaddingBottom(); + view.setPadding(l, t, r, b); + + } + + + public static PaddingLeftAttr generate(int val, int baseFlag) + { + PaddingLeftAttr attr = null; + switch (baseFlag) + { + case AutoAttr.BASE_WIDTH: + attr = new PaddingLeftAttr(val, Attrs.PADDING_LEFT, 0); + break; + case AutoAttr.BASE_HEIGHT: + attr = new PaddingLeftAttr(val, 0, Attrs.PADDING_LEFT); + break; + case AutoAttr.BASE_DEFAULT: + attr = new PaddingLeftAttr(val, 0, 0); + break; + } + return attr; + } +} diff --git a/YFDXJ/autolayout/src/main/java/com/zhy/autolayout/attr/PaddingRightAttr.java b/YFDXJ/autolayout/src/main/java/com/zhy/autolayout/attr/PaddingRightAttr.java new file mode 100644 index 0000000..a7db5a9 --- /dev/null +++ b/YFDXJ/autolayout/src/main/java/com/zhy/autolayout/attr/PaddingRightAttr.java @@ -0,0 +1,56 @@ +package com.zhy.autolayout.attr; + +import android.view.View; + +/** + * Created by zhy on 15/12/5. + */ +public class PaddingRightAttr extends AutoAttr +{ + public PaddingRightAttr(int pxVal, int baseWidth, int baseHeight) + { + super(pxVal, baseWidth, baseHeight); + } + + @Override + protected int attrVal() + { + return Attrs.PADDING_RIGHT; + } + + @Override + protected boolean defaultBaseWidth() + { + return true; + } + + @Override + protected void execute(View view, int val) + { + int l = view.getPaddingLeft(); + int t = view.getPaddingTop(); + int r = val; + int b = view.getPaddingBottom(); + view.setPadding(l, t, r, b); + + } + + + public static PaddingRightAttr generate(int val, int baseFlag) + { + PaddingRightAttr attr = null; + switch (baseFlag) + { + case AutoAttr.BASE_WIDTH: + attr = new PaddingRightAttr(val, Attrs.PADDING_RIGHT, 0); + break; + case AutoAttr.BASE_HEIGHT: + attr = new PaddingRightAttr(val, 0, Attrs.PADDING_RIGHT); + break; + case AutoAttr.BASE_DEFAULT: + attr = new PaddingRightAttr(val, 0, 0); + break; + } + return attr; + } +} diff --git a/YFDXJ/autolayout/src/main/java/com/zhy/autolayout/attr/PaddingTopAttr.java b/YFDXJ/autolayout/src/main/java/com/zhy/autolayout/attr/PaddingTopAttr.java new file mode 100644 index 0000000..d8a5e15 --- /dev/null +++ b/YFDXJ/autolayout/src/main/java/com/zhy/autolayout/attr/PaddingTopAttr.java @@ -0,0 +1,54 @@ +package com.zhy.autolayout.attr; + +import android.view.View; + +/** + * Created by zhy on 15/12/5. + */ +public class PaddingTopAttr extends AutoAttr +{ + public PaddingTopAttr(int pxVal, int baseWidth, int baseHeight) + { + super(pxVal, baseWidth, baseHeight); + } + + @Override + protected int attrVal() + { + return Attrs.PADDING_TOP; + } + + @Override + protected boolean defaultBaseWidth() + { + return false; + } + + @Override + protected void execute(View view, int val) + { + int l = view.getPaddingLeft(); + int t = val; + int r = view.getPaddingRight(); + int b = view.getPaddingBottom(); + view.setPadding(l, t, r, b); + } + + public static PaddingTopAttr generate(int val, int baseFlag) + { + PaddingTopAttr attr = null; + switch (baseFlag) + { + case AutoAttr.BASE_WIDTH: + attr = new PaddingTopAttr(val, Attrs.PADDING_TOP, 0); + break; + case AutoAttr.BASE_HEIGHT: + attr = new PaddingTopAttr(val, 0, Attrs.PADDING_TOP); + break; + case AutoAttr.BASE_DEFAULT: + attr = new PaddingTopAttr(val, 0, 0); + break; + } + return attr; + } +} diff --git a/YFDXJ/autolayout/src/main/java/com/zhy/autolayout/attr/TextSizeAttr.java b/YFDXJ/autolayout/src/main/java/com/zhy/autolayout/attr/TextSizeAttr.java new file mode 100644 index 0000000..bb4e2a5 --- /dev/null +++ b/YFDXJ/autolayout/src/main/java/com/zhy/autolayout/attr/TextSizeAttr.java @@ -0,0 +1,58 @@ +package com.zhy.autolayout.attr; + +import android.util.TypedValue; +import android.view.View; +import android.widget.TextView; + +/** + * Created by zhy on 15/12/4. + */ +public class TextSizeAttr extends AutoAttr +{ + + public TextSizeAttr(int pxVal, int baseWidth, int baseHeight) + { + super(pxVal, baseWidth, baseHeight); + } + + @Override + protected int attrVal() + { + return Attrs.TEXTSIZE; + } + + @Override + protected boolean defaultBaseWidth() + { + return false; + } + + @Override + protected void execute(View view, int val) + { + if (!(view instanceof TextView)) + return; + ((TextView) view).setIncludeFontPadding(false); + ((TextView) view).setTextSize(TypedValue.COMPLEX_UNIT_PX, val); + } + + public static TextSizeAttr generate(int val, int baseFlag) + { + TextSizeAttr attr = null; + switch (baseFlag) + { + case AutoAttr.BASE_WIDTH: + attr = new TextSizeAttr(val, Attrs.TEXTSIZE, 0); + break; + case AutoAttr.BASE_HEIGHT: + attr = new TextSizeAttr(val, 0, Attrs.TEXTSIZE); + break; + case AutoAttr.BASE_DEFAULT: + attr = new TextSizeAttr(val, 0, 0); + break; + } + return attr; + } + + +} diff --git a/YFDXJ/autolayout/src/main/java/com/zhy/autolayout/attr/WidthAttr.java b/YFDXJ/autolayout/src/main/java/com/zhy/autolayout/attr/WidthAttr.java new file mode 100644 index 0000000..2de66c9 --- /dev/null +++ b/YFDXJ/autolayout/src/main/java/com/zhy/autolayout/attr/WidthAttr.java @@ -0,0 +1,53 @@ +package com.zhy.autolayout.attr; + +import android.view.View; +import android.view.ViewGroup; + +/** + * Created by zhy on 15/12/5. + */ +public class WidthAttr extends AutoAttr +{ + public WidthAttr(int pxVal, int baseWidth, int baseHeight) + { + super(pxVal, baseWidth, baseHeight); + } + + @Override + protected int attrVal() + { + return Attrs.WIDTH; + } + + @Override + protected boolean defaultBaseWidth() + { + return true; + } + + @Override + protected void execute(View view, int val) + { + ViewGroup.LayoutParams lp = view.getLayoutParams(); + lp.width = val; + } + + public static WidthAttr generate(int val, int baseFlag) + { + WidthAttr widthAttr = null; + switch (baseFlag) + { + case AutoAttr.BASE_WIDTH: + widthAttr = new WidthAttr(val, Attrs.WIDTH, 0); + break; + case AutoAttr.BASE_HEIGHT: + widthAttr = new WidthAttr(val, 0, Attrs.WIDTH); + break; + case AutoAttr.BASE_DEFAULT: + widthAttr = new WidthAttr(val, 0, 0); + break; + } + return widthAttr; + } + +} diff --git a/YFDXJ/autolayout/src/main/java/com/zhy/autolayout/config/AutoLayoutConifg.java b/YFDXJ/autolayout/src/main/java/com/zhy/autolayout/config/AutoLayoutConifg.java new file mode 100644 index 0000000..3261b67 --- /dev/null +++ b/YFDXJ/autolayout/src/main/java/com/zhy/autolayout/config/AutoLayoutConifg.java @@ -0,0 +1,111 @@ +package com.zhy.autolayout.config; + +import android.content.Context; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; + +import com.zhy.autolayout.utils.L; +import com.zhy.autolayout.utils.ScreenUtils; + +/** + * Created by zhy on 15/11/18. + */ +public class AutoLayoutConifg +{ + + private static AutoLayoutConifg sIntance = new AutoLayoutConifg(); + + + private static final String KEY_DESIGN_WIDTH = "design_width"; + private static final String KEY_DESIGN_HEIGHT = "design_height"; + + private int mScreenWidth; + private int mScreenHeight; + + private int mDesignWidth; + private int mDesignHeight; + + private boolean useDeviceSize; + + + private AutoLayoutConifg() + { + } + + public void checkParams() + { + if (mDesignHeight <= 0 || mDesignWidth <= 0) + { + throw new RuntimeException( + "you must set " + KEY_DESIGN_WIDTH + " and " + KEY_DESIGN_HEIGHT + " in your manifest file."); + } + } + + public AutoLayoutConifg useDeviceSize() + { + useDeviceSize = true; + return this; + } + + + public static AutoLayoutConifg getInstance() + { + return sIntance; + } + + + public int getScreenWidth() + { + return mScreenWidth; + } + + public int getScreenHeight() + { + return mScreenHeight; + } + + public int getDesignWidth() + { + return mDesignWidth; + } + + public int getDesignHeight() + { + return mDesignHeight; + } + + + public void init(Context context) + { + getMetaData(context); + + int[] screenSize = ScreenUtils.getScreenSize(context, useDeviceSize); + mScreenWidth = screenSize[0]; + mScreenHeight = screenSize[1]; + L.e(" screenWidth =" + mScreenWidth + " ,screenHeight = " + mScreenHeight); + } + + private void getMetaData(Context context) + { + PackageManager packageManager = context.getPackageManager(); + ApplicationInfo applicationInfo; + try + { + applicationInfo = packageManager.getApplicationInfo(context + .getPackageName(), PackageManager.GET_META_DATA); + if (applicationInfo != null && applicationInfo.metaData != null) + { + mDesignWidth = (int) applicationInfo.metaData.get(KEY_DESIGN_WIDTH); + mDesignHeight = (int) applicationInfo.metaData.get(KEY_DESIGN_HEIGHT); + } + } catch (PackageManager.NameNotFoundException e) + { + throw new RuntimeException( + "you must set " + KEY_DESIGN_WIDTH + " and " + KEY_DESIGN_HEIGHT + " in your manifest file.", e); + } + + L.e(" designWidth =" + mDesignWidth + " , designHeight = " + mDesignHeight); + } + + +} diff --git a/YFDXJ/autolayout/src/main/java/com/zhy/autolayout/config/UseLandscape.java b/YFDXJ/autolayout/src/main/java/com/zhy/autolayout/config/UseLandscape.java new file mode 100644 index 0000000..34fbdab --- /dev/null +++ b/YFDXJ/autolayout/src/main/java/com/zhy/autolayout/config/UseLandscape.java @@ -0,0 +1,9 @@ +package com.zhy.autolayout.config; + +/** + * Created by zhy on 15/12/5. + * 如果Activity设计稿是横屏,继承该接口即可 + */ +public interface UseLandscape +{ +} diff --git a/YFDXJ/autolayout/src/main/java/com/zhy/autolayout/utils/AutoLayoutHelper.java b/YFDXJ/autolayout/src/main/java/com/zhy/autolayout/utils/AutoLayoutHelper.java new file mode 100644 index 0000000..9ac666e --- /dev/null +++ b/YFDXJ/autolayout/src/main/java/com/zhy/autolayout/utils/AutoLayoutHelper.java @@ -0,0 +1,233 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.zhy.autolayout.utils; + +import android.content.Context; +import android.content.res.TypedArray; +import android.util.AttributeSet; +import android.view.View; +import android.view.ViewGroup; + +import com.zhy.autolayout.AutoLayoutInfo; +import com.zhy.autolayout.R; +import com.zhy.autolayout.attr.HeightAttr; +import com.zhy.autolayout.attr.MarginAttr; +import com.zhy.autolayout.attr.MarginBottomAttr; +import com.zhy.autolayout.attr.MarginLeftAttr; +import com.zhy.autolayout.attr.MarginRightAttr; +import com.zhy.autolayout.attr.MarginTopAttr; +import com.zhy.autolayout.attr.MaxHeightAttr; +import com.zhy.autolayout.attr.MaxWidthAttr; +import com.zhy.autolayout.attr.MinHeightAttr; +import com.zhy.autolayout.attr.MinWidthAttr; +import com.zhy.autolayout.attr.PaddingAttr; +import com.zhy.autolayout.attr.PaddingBottomAttr; +import com.zhy.autolayout.attr.PaddingLeftAttr; +import com.zhy.autolayout.attr.PaddingRightAttr; +import com.zhy.autolayout.attr.PaddingTopAttr; +import com.zhy.autolayout.attr.TextSizeAttr; +import com.zhy.autolayout.attr.WidthAttr; +import com.zhy.autolayout.config.AutoLayoutConifg; + +public class AutoLayoutHelper +{ + private final ViewGroup mHost; + + private static final int[] LL = new int[] + { // + android.R.attr.textSize, + android.R.attr.padding,// + android.R.attr.paddingLeft,// + android.R.attr.paddingTop,// + android.R.attr.paddingRight,// + android.R.attr.paddingBottom,// + android.R.attr.layout_width,// + android.R.attr.layout_height,// + android.R.attr.layout_margin,// + android.R.attr.layout_marginLeft,// + android.R.attr.layout_marginTop,// + android.R.attr.layout_marginRight,// + android.R.attr.layout_marginBottom,// + android.R.attr.maxWidth,// + android.R.attr.maxHeight,// + android.R.attr.minWidth,// + android.R.attr.minHeight,//16843072 + + + }; + + private static final int INDEX_TEXT_SIZE = 0; + private static final int INDEX_PADDING = 1; + private static final int INDEX_PADDING_LEFT = 2; + private static final int INDEX_PADDING_TOP = 3; + private static final int INDEX_PADDING_RIGHT = 4; + private static final int INDEX_PADDING_BOTTOM = 5; + private static final int INDEX_WIDTH = 6; + private static final int INDEX_HEIGHT = 7; + private static final int INDEX_MARGIN = 8; + private static final int INDEX_MARGIN_LEFT = 9; + private static final int INDEX_MARGIN_TOP = 10; + private static final int INDEX_MARGIN_RIGHT = 11; + private static final int INDEX_MARGIN_BOTTOM = 12; + private static final int INDEX_MAX_WIDTH = 13; + private static final int INDEX_MAX_HEIGHT = 14; + private static final int INDEX_MIN_WIDTH = 15; + private static final int INDEX_MIN_HEIGHT = 16; + + + /** + * move to other place? + */ + private static AutoLayoutConifg mAutoLayoutConifg; + + public AutoLayoutHelper(ViewGroup host) + { + mHost = host; + + if (mAutoLayoutConifg == null) + { + initAutoLayoutConfig(host); + } + + } + + private void initAutoLayoutConfig(ViewGroup host) + { + mAutoLayoutConifg = AutoLayoutConifg.getInstance(); + mAutoLayoutConifg.init(host.getContext()); + } + + + public void adjustChildren() + { + AutoLayoutConifg.getInstance().checkParams(); + + for (int i = 0, n = mHost.getChildCount(); i < n; i++) + { + View view = mHost.getChildAt(i); + ViewGroup.LayoutParams params = view.getLayoutParams(); + + if (params instanceof AutoLayoutParams) + { + AutoLayoutInfo info = + ((AutoLayoutParams) params).getAutoLayoutInfo(); + if (info != null) + { + info.fillAttrs(view); + } + } + } + + } + + public static AutoLayoutInfo getAutoLayoutInfo(Context context, + AttributeSet attrs) + { + + AutoLayoutInfo info = new AutoLayoutInfo(); + + TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.AutoLayout_Layout); + int baseWidth = a.getInt(R.styleable.AutoLayout_Layout_layout_auto_basewidth, 0); + int baseHeight = a.getInt(R.styleable.AutoLayout_Layout_layout_auto_baseheight, 0); + a.recycle(); + + TypedArray array = context.obtainStyledAttributes(attrs, LL); + + int n = array.getIndexCount(); + + + for (int i = 0; i < n; i++) + { + int index = array.getIndex(i); +// String val = array.getString(index); +// if (!isPxVal(val)) continue; + + if (!DimenUtils.isPxVal(array.peekValue(index))) continue; + + int pxVal = 0; + try + { + pxVal = array.getDimensionPixelOffset(index, 0); + } catch (Exception ignore)//not dimension + { + continue; + } + switch (index) + { + case INDEX_TEXT_SIZE: + info.addAttr(new TextSizeAttr(pxVal, baseWidth, baseHeight)); + break; + case INDEX_PADDING: + info.addAttr(new PaddingAttr(pxVal, baseWidth, baseHeight)); + break; + case INDEX_PADDING_LEFT: + info.addAttr(new PaddingLeftAttr(pxVal, baseWidth, baseHeight)); + break; + case INDEX_PADDING_TOP: + info.addAttr(new PaddingTopAttr(pxVal, baseWidth, baseHeight)); + break; + case INDEX_PADDING_RIGHT: + info.addAttr(new PaddingRightAttr(pxVal, baseWidth, baseHeight)); + break; + case INDEX_PADDING_BOTTOM: + info.addAttr(new PaddingBottomAttr(pxVal, baseWidth, baseHeight)); + break; + case INDEX_WIDTH: + info.addAttr(new WidthAttr(pxVal, baseWidth, baseHeight)); + break; + case INDEX_HEIGHT: + info.addAttr(new HeightAttr(pxVal, baseWidth, baseHeight)); + break; + case INDEX_MARGIN: + info.addAttr(new MarginAttr(pxVal, baseWidth, baseHeight)); + break; + case INDEX_MARGIN_LEFT: + info.addAttr(new MarginLeftAttr(pxVal, baseWidth, baseHeight)); + break; + case INDEX_MARGIN_TOP: + info.addAttr(new MarginTopAttr(pxVal, baseWidth, baseHeight)); + break; + case INDEX_MARGIN_RIGHT: + info.addAttr(new MarginRightAttr(pxVal, baseWidth, baseHeight)); + break; + case INDEX_MARGIN_BOTTOM: + info.addAttr(new MarginBottomAttr(pxVal, baseWidth, baseHeight)); + break; + case INDEX_MAX_WIDTH: + info.addAttr(new MaxWidthAttr(pxVal, baseWidth, baseHeight)); + break; + case INDEX_MAX_HEIGHT: + info.addAttr(new MaxHeightAttr(pxVal, baseWidth, baseHeight)); + break; + case INDEX_MIN_WIDTH: + info.addAttr(new MinWidthAttr(pxVal, baseWidth, baseHeight)); + break; + case INDEX_MIN_HEIGHT: + info.addAttr(new MinHeightAttr(pxVal, baseWidth, baseHeight)); + break; + } + } + array.recycle(); + L.e(" getAutoLayoutInfo " + info.toString()); + return info; + } + + public interface AutoLayoutParams + { + AutoLayoutInfo getAutoLayoutInfo(); + } +} diff --git a/YFDXJ/autolayout/src/main/java/com/zhy/autolayout/utils/AutoUtils.java b/YFDXJ/autolayout/src/main/java/com/zhy/autolayout/utils/AutoUtils.java new file mode 100644 index 0000000..1252648 --- /dev/null +++ b/YFDXJ/autolayout/src/main/java/com/zhy/autolayout/utils/AutoUtils.java @@ -0,0 +1,151 @@ +package com.zhy.autolayout.utils; + +import android.view.View; + +import com.zhy.autolayout.AutoLayoutInfo; +import com.zhy.autolayout.R; +import com.zhy.autolayout.attr.Attrs; +import com.zhy.autolayout.attr.AutoAttr; +import com.zhy.autolayout.config.AutoLayoutConifg; + +/** + * Created by zhy on 15/12/4. + */ +public class AutoUtils +{ + + /** + * 会直接将view的LayoutParams上设置的width,height直接进行百分比处理 + * + * @param view + */ + public static void auto(View view) + { + autoSize(view); + autoPadding(view); + autoMargin(view); + autoTextSize(view, AutoAttr.BASE_DEFAULT); + } + + /** + * @param view + * @param attrs #Attrs.WIDTH|Attrs.HEIGHT + * @param base AutoAttr.BASE_WIDTH|AutoAttr.BASE_HEIGHT|AutoAttr.BASE_DEFAULT + */ + public static void auto(View view, int attrs, int base) + { + AutoLayoutInfo autoLayoutInfo = AutoLayoutInfo.getAttrFromView(view, attrs, base); + if (autoLayoutInfo != null) + autoLayoutInfo.fillAttrs(view); + } + + public static void autoTextSize(View view) + { + auto(view, Attrs.TEXTSIZE, AutoAttr.BASE_DEFAULT); + } + + public static void autoTextSize(View view, int base) + { + auto(view, Attrs.TEXTSIZE, base); + } + + public static void autoMargin(View view) + { + auto(view, Attrs.MARGIN, AutoAttr.BASE_DEFAULT); + } + + public static void autoMargin(View view, int base) + { + auto(view, Attrs.MARGIN, base); + } + + public static void autoPadding(View view) + { + auto(view, Attrs.PADDING, AutoAttr.BASE_DEFAULT); + } + + public static void autoPadding(View view, int base) + { + auto(view, Attrs.PADDING, base); + } + + public static void autoSize(View view) + { + auto(view, Attrs.WIDTH | Attrs.HEIGHT, AutoAttr.BASE_DEFAULT); + } + + public static void autoSize(View view, int base) + { + auto(view, Attrs.WIDTH | Attrs.HEIGHT, base); + } + + public static boolean autoed(View view) + { + Object tag = view.getTag(R.id.id_tag_autolayout_size); + if (tag != null) return true; + view.setTag(R.id.id_tag_autolayout_size, "Just Identify"); + return false; + } + + public static float getPercentWidth1px() + { + int screenWidth = AutoLayoutConifg.getInstance().getScreenWidth(); + int designWidth = AutoLayoutConifg.getInstance().getDesignWidth(); + return 1.0f * screenWidth / designWidth; + } + + public static float getPercentHeight1px() + { + int screenHeight = AutoLayoutConifg.getInstance().getScreenHeight(); + int designHeight = AutoLayoutConifg.getInstance().getDesignHeight(); + return 1.0f * screenHeight / designHeight; + } + + + public static int getPercentWidthSize(int val) + { + int screenWidth = AutoLayoutConifg.getInstance().getScreenWidth(); + int designWidth = AutoLayoutConifg.getInstance().getDesignWidth(); + return (int) (val * 1.0f / designWidth * screenWidth); + } + + + public static int getPercentWidthSizeBigger(int val) + { + int screenWidth = AutoLayoutConifg.getInstance().getScreenWidth(); + int designWidth = AutoLayoutConifg.getInstance().getDesignWidth(); + + int res = val * screenWidth; + if (res % designWidth == 0) + { + return res / designWidth; + } else + { + return res / designWidth + 1; + } + + } + + public static int getPercentHeightSizeBigger(int val) + { + int screenHeight = AutoLayoutConifg.getInstance().getScreenHeight(); + int designHeight = AutoLayoutConifg.getInstance().getDesignHeight(); + + int res = val * screenHeight; + if (res % designHeight == 0) + { + return res / designHeight; + } else + { + return res / designHeight + 1; + } + } + + public static int getPercentHeightSize(int val) + { + int screenHeight = AutoLayoutConifg.getInstance().getScreenHeight(); + int designHeight = AutoLayoutConifg.getInstance().getDesignHeight(); + + return (int) (val * 1.0f / designHeight * screenHeight); + } +} diff --git a/YFDXJ/autolayout/src/main/java/com/zhy/autolayout/utils/DimenUtils.java b/YFDXJ/autolayout/src/main/java/com/zhy/autolayout/utils/DimenUtils.java new file mode 100644 index 0000000..a055670 --- /dev/null +++ b/YFDXJ/autolayout/src/main/java/com/zhy/autolayout/utils/DimenUtils.java @@ -0,0 +1,24 @@ +package com.zhy.autolayout.utils; + +import android.util.TypedValue; + +/** + * Created by zhy on 16/3/3. + */ +public class DimenUtils +{ + private static int getComplexUnit(int data) + { + return TypedValue.COMPLEX_UNIT_MASK & (data >> TypedValue.COMPLEX_UNIT_SHIFT); + } + + public static boolean isPxVal(TypedValue val) + { + if (val != null && val.type == TypedValue.TYPE_DIMENSION && + getComplexUnit(val.data) == TypedValue.COMPLEX_UNIT_PX) + { + return true; + } + return false; + } +} diff --git a/YFDXJ/autolayout/src/main/java/com/zhy/autolayout/utils/L.java b/YFDXJ/autolayout/src/main/java/com/zhy/autolayout/utils/L.java new file mode 100644 index 0000000..11db454 --- /dev/null +++ b/YFDXJ/autolayout/src/main/java/com/zhy/autolayout/utils/L.java @@ -0,0 +1,22 @@ +package com.zhy.autolayout.utils; + +import android.util.Log; + +/** + * Created by zhy on 15/11/18. + */ +public class L +{ + public static boolean debug = false; + private static final String TAG = "AUTO_LAYOUT"; + + public static void e(String msg) + { + if (debug) + { + Log.e(TAG, msg); + } + } + + +} diff --git a/YFDXJ/autolayout/src/main/java/com/zhy/autolayout/utils/ScreenUtils.java b/YFDXJ/autolayout/src/main/java/com/zhy/autolayout/utils/ScreenUtils.java new file mode 100644 index 0000000..d7db439 --- /dev/null +++ b/YFDXJ/autolayout/src/main/java/com/zhy/autolayout/utils/ScreenUtils.java @@ -0,0 +1,82 @@ +package com.zhy.autolayout.utils; + +import android.content.Context; +import android.content.res.Resources; +import android.graphics.Point; +import android.os.Build; +import android.util.DisplayMetrics; +import android.view.Display; +import android.view.WindowManager; + +/** + * Created by zhy on 15/12/4.
+ * form http://stackoverflow.com/questions/1016896/get-screen-dimensions-in-pixels/15699681#15699681 + */ +public class ScreenUtils +{ + + public static int getStatusBarHeight(Context context) + { + int result = 0; + try + { + int resourceId = context.getResources().getIdentifier("status_bar_height", "dimen", "android"); + if (resourceId > 0) + { + result = context.getResources().getDimensionPixelSize(resourceId); + } + } catch (Resources.NotFoundException e) + { + e.printStackTrace(); + } + return result; + } + + + public static int[] getScreenSize(Context context, boolean useDeviceSize) + { + + int[] size = new int[2]; + + WindowManager w = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); + Display d = w.getDefaultDisplay(); + DisplayMetrics metrics = new DisplayMetrics(); + d.getMetrics(metrics); +// since SDK_INT = 1; + int widthPixels = metrics.widthPixels; + int heightPixels = metrics.heightPixels; + + if (!useDeviceSize) + { + size[0] = widthPixels; + size[1] = heightPixels - getStatusBarHeight(context); + + return size; + } + +// includes window decorations (statusbar bar/menu bar) + if (Build.VERSION.SDK_INT >= 14 && Build.VERSION.SDK_INT < 17) + try + { + widthPixels = (Integer) Display.class.getMethod("getRawWidth").invoke(d); + heightPixels = (Integer) Display.class.getMethod("getRawHeight").invoke(d); + } catch (Exception ignored) + { + } +// includes window decorations (statusbar bar/menu bar) + if (Build.VERSION.SDK_INT >= 17) + try + { + Point realSize = new Point(); + Display.class.getMethod("getRealSize", Point.class).invoke(d, realSize); + widthPixels = realSize.x; + heightPixels = realSize.y; + } catch (Exception ignored) + { + } + size[0] = widthPixels; + size[1] = heightPixels; + return size; + } + +} diff --git a/YFDXJ/autolayout/src/main/java/com/zhy/autolayout/widget/MetroLayout.java b/YFDXJ/autolayout/src/main/java/com/zhy/autolayout/widget/MetroLayout.java new file mode 100644 index 0000000..44c732e --- /dev/null +++ b/YFDXJ/autolayout/src/main/java/com/zhy/autolayout/widget/MetroLayout.java @@ -0,0 +1,233 @@ +package com.zhy.autolayout.widget; + +import android.content.Context; +import android.content.res.TypedArray; +import android.graphics.Color; +import android.util.AttributeSet; +import android.view.View; +import android.view.ViewGroup; + +import com.zhy.autolayout.AutoLayoutInfo; +import com.zhy.autolayout.R; +import com.zhy.autolayout.utils.AutoLayoutHelper; +import com.zhy.autolayout.utils.AutoUtils; + +import java.util.ArrayList; +import java.util.List; +import java.util.Random; + +/** + * Created by zhy on 15/12/10. + * + * //do not use + */ +public class MetroLayout extends ViewGroup +{ + + private final AutoLayoutHelper mHelper = new AutoLayoutHelper(this); + + private static class MetroBlock + { + int left; + int top; + int width; + } + + private List mAvailablePos = new ArrayList<>(); + private int mDivider; + + public MetroLayout(Context context, AttributeSet attrs) + { + super(context, attrs); + TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.MetroLayout); + mDivider = a.getDimensionPixelOffset(R.styleable.MetroLayout_metro_divider, 0); + mDivider = AutoUtils.getPercentWidthSizeBigger(mDivider); + a.recycle(); + + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) + { + + if (true) + randomColor(); + + if (!isInEditMode()) + mHelper.adjustChildren(); + + measureChildren(widthMeasureSpec, heightMeasureSpec); + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + + } + + private void randomColor() + { + Random r = new Random(255); + + for (int i = 0, n = getChildCount(); i < n; i++) + { + View v = getChildAt(i); + + v.setBackgroundColor(Color.argb(100, r.nextInt(), r.nextInt(), r.nextInt())); + } + } + + + @Override + protected void onLayout(boolean changed, int l, int t, int r, int b) + { + + initAvailablePosition(); + + int left = 0; + int top = 0; + int divider = mDivider; + + for (int i = 0, n = getChildCount(); i < n; i++) + { + View v = getChildAt(i); + if (v.getVisibility() == View.GONE) continue; + + MetroBlock newPos = findAvailablePos(v); + left = newPos.left; + top = newPos.top; + + int childWidth = v.getMeasuredWidth(); + int childHeight = v.getMeasuredHeight(); + + int right = left + childWidth; + int bottom = top + childHeight; + + v.layout(left, top, right, bottom); + + if (childWidth + divider < newPos.width) + { + newPos.left += childWidth + divider; + newPos.width -= childWidth + divider; + } else + { + mAvailablePos.remove(newPos); + } + + MetroBlock p = new MetroBlock(); + p.left = left; + p.top = bottom + divider; + p.width = childWidth; + mAvailablePos.add(p); + + mergeAvailablePosition(); + + } + } + + private void mergeAvailablePosition() + { + if (mAvailablePos.size() <= 1) return; + + List needRemoveBlocks = new ArrayList<>(); + + MetroBlock one = mAvailablePos.get(0); + MetroBlock two = mAvailablePos.get(1); + + for (int i = 1, n = mAvailablePos.size(); i < n - 1; i++) + { + if (one.top == two.top) + { + one.width = one.width + two.width; + needRemoveBlocks.add(one); + two.left = one.left; + two = mAvailablePos.get(i + 1); + } else + { + one = mAvailablePos.get(i); + two = mAvailablePos.get(i + 1); + } + } + + mAvailablePos.removeAll(needRemoveBlocks); + + } + + private void initAvailablePosition() + { + mAvailablePos.clear(); + MetroBlock first = new MetroBlock(); + first.left = getPaddingLeft(); + first.top = getPaddingTop(); + first.width = getMeasuredWidth(); + mAvailablePos.add(first); + } + + private MetroBlock findAvailablePos(View view) + { + MetroBlock p = new MetroBlock(); + if (mAvailablePos.size() == 0) + { + p.left = getPaddingLeft(); + p.top = getPaddingTop(); + p.width = getMeasuredWidth(); + return p; + } + int min = mAvailablePos.get(0).top; + MetroBlock minHeightPos = mAvailablePos.get(0); + for (MetroBlock _p : mAvailablePos) + { + if (_p.top < min) + { + min = _p.top; + minHeightPos = _p; + } + } + return minHeightPos; + } + + + @Override + public MetroLayout.LayoutParams generateLayoutParams(AttributeSet attrs) + { + return new LayoutParams(getContext(), attrs); + } + + public static class LayoutParams extends ViewGroup.MarginLayoutParams + implements AutoLayoutHelper.AutoLayoutParams + { + private AutoLayoutInfo mAutoLayoutInfo; + + public LayoutParams(Context c, AttributeSet attrs) + { + super(c, attrs); + mAutoLayoutInfo = AutoLayoutHelper.getAutoLayoutInfo(c, attrs); + } + + public LayoutParams(int width, int height) + { + super(width, height); + } + + public LayoutParams(ViewGroup.LayoutParams source) + { + super(source); + } + + public LayoutParams(MarginLayoutParams source) + { + super(source); + } + + public LayoutParams(LayoutParams source) + { + this((ViewGroup.LayoutParams) source); + mAutoLayoutInfo = source.mAutoLayoutInfo; + } + + @Override + public AutoLayoutInfo getAutoLayoutInfo() + { + return mAutoLayoutInfo; + } + + + } + +} diff --git a/YFDXJ/autolayout/src/main/res/values/attrs.xml b/YFDXJ/autolayout/src/main/res/values/attrs.xml new file mode 100644 index 0000000..c1d75c7 --- /dev/null +++ b/YFDXJ/autolayout/src/main/res/values/attrs.xml @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/YFDXJ/autolayout/src/main/res/values/ids.xml b/YFDXJ/autolayout/src/main/res/values/ids.xml new file mode 100644 index 0000000..fd14abe --- /dev/null +++ b/YFDXJ/autolayout/src/main/res/values/ids.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/YFDXJ/autolayout/src/main/res/values/strings.xml b/YFDXJ/autolayout/src/main/res/values/strings.xml new file mode 100644 index 0000000..d9ea580 --- /dev/null +++ b/YFDXJ/autolayout/src/main/res/values/strings.xml @@ -0,0 +1,3 @@ + + autolayout + diff --git a/YFDXJ/build.gradle b/YFDXJ/build.gradle old mode 100755 new mode 100644 index 24babc8..ff81561 --- a/YFDXJ/build.gradle +++ b/YFDXJ/build.gradle @@ -1,6 +1,6 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. plugins { - id 'com.android.application' version '8.1.1' apply false + id 'com.android.application' version '8.6.0' apply false id 'org.jetbrains.kotlin.android' version '1.8.0' apply false } diff --git a/YFDXJ/gradle.properties b/YFDXJ/gradle.properties old mode 100755 new mode 100644 index 16a11ee..58df1b4 --- a/YFDXJ/gradle.properties +++ b/YFDXJ/gradle.properties @@ -6,12 +6,14 @@ # http://www.gradle.org/docs/current/userguide/build_environment.html # Specifies the JVM arguments used for the daemon process. # The setting is particularly useful for tweaking memory settings. -android.defaults.buildfeatures.buildconfig=true +#android.defaults.buildfeatures.buildconfig=true +#android.nonFinalResIds=false +#android.nonTransitiveRClass=false + android.enableJetifier=true -android.nonFinalResIds=false -android.nonTransitiveRClass=false android.useAndroidX=true -org.gradle.jvmargs=-Xmx1536m +org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 + # When configured, Gradle will run in incubating parallel mode. # This option should only be used with decoupled projects. More details, visit # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects diff --git a/YFDXJ/gradle/wrapper/gradle-wrapper.jar b/YFDXJ/gradle/wrapper/gradle-wrapper.jar old mode 100755 new mode 100644 diff --git a/YFDXJ/gradle/wrapper/gradle-wrapper.properties b/YFDXJ/gradle/wrapper/gradle-wrapper.properties old mode 100755 new mode 100644 index 7bc137f..3d055a1 --- a/YFDXJ/gradle/wrapper/gradle-wrapper.properties +++ b/YFDXJ/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.0-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-all.zip diff --git a/YFDXJ/gradlew b/YFDXJ/gradlew old mode 100755 new mode 100644 diff --git a/YFDXJ/gradlew.bat b/YFDXJ/gradlew.bat old mode 100755 new mode 100644 diff --git a/YFDXJ/litepal/build.gradle b/YFDXJ/litepal/build.gradle new file mode 100644 index 0000000..d4de868 --- /dev/null +++ b/YFDXJ/litepal/build.gradle @@ -0,0 +1,11 @@ +apply plugin: 'com.android.library' + +android { + compileSdk 34 + defaultConfig { + minSdk 24 + targetSdk 34 + testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" + } + namespace 'org.litepal' +} \ No newline at end of file diff --git a/YFDXJ/litepal/src/androidTest/java/org/litepal/ApplicationTest.java b/YFDXJ/litepal/src/androidTest/java/org/litepal/ApplicationTest.java new file mode 100644 index 0000000..2b59c95 --- /dev/null +++ b/YFDXJ/litepal/src/androidTest/java/org/litepal/ApplicationTest.java @@ -0,0 +1,13 @@ +package org.litepal; + +import android.app.Application; +import android.test.ApplicationTestCase; + +/** + * Testing Fundamentals + */ +public class ApplicationTest extends ApplicationTestCase { + public ApplicationTest() { + super(Application.class); + } +} \ No newline at end of file diff --git a/YFDXJ/litepal/src/main/AndroidManifest.xml b/YFDXJ/litepal/src/main/AndroidManifest.xml new file mode 100644 index 0000000..972c3a8 --- /dev/null +++ b/YFDXJ/litepal/src/main/AndroidManifest.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/YFDXJ/litepal/src/main/java/org/litepal/LitePal.java b/YFDXJ/litepal/src/main/java/org/litepal/LitePal.java new file mode 100644 index 0000000..e06e10a --- /dev/null +++ b/YFDXJ/litepal/src/main/java/org/litepal/LitePal.java @@ -0,0 +1,170 @@ +/* + * Copyright (C) Tony Green, LitePal Framework Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.litepal; + +import android.content.Context; +import android.database.sqlite.SQLiteDatabase; +import android.os.Handler; +import android.os.Looper; +import android.text.TextUtils; + +import org.litepal.parser.LitePalAttr; +import org.litepal.parser.LitePalConfig; +import org.litepal.parser.LitePalParser; +import org.litepal.tablemanager.Connector; +import org.litepal.util.BaseUtility; +import org.litepal.util.Const; +import org.litepal.util.SharedUtil; + +import java.io.File; + +/** + * LitePal is an Android library that allows developers to use SQLite database extremely easy. + * You can initialized it by calling {@link #initialize(Context)} method to make LitePal ready to + * work. Also you can switch the using database by calling {@link #use(LitePalDB)} and {@link #useDefault()} + * methods. + * + * @author Tony Green + * @since 1.4 + */ +public class LitePal { + + private static Handler handler = new Handler(Looper.getMainLooper()); + + /** + * Initialize to make LitePal ready to work. If you didn't configure LitePalApplication + * in the AndroidManifest.xml, make sure you call this method as soon as possible. In + * Application's onCreate() method will be fine. + * + * @param context + * Application context. + */ + public static void initialize(Context context) { + LitePalApplication.sContext = context; + } + + /** + * Get a writable SQLiteDatabase. + * + * @return A writable SQLiteDatabase instance + */ + public static SQLiteDatabase getDatabase() { + return Connector.getDatabase(); + } + + /** + * Get the main thread handler. You don't need this method. It's used by framework only. + * @return Main thread handler. + */ + public static Handler getHandler() { + return handler; + } + + /** + * Switch the using database to the one specified by parameter. + * @param litePalDB + * The database to switch to. + */ + public static void use(LitePalDB litePalDB) { + LitePalAttr litePalAttr = LitePalAttr.getInstance(); + litePalAttr.setDbName(litePalDB.getDbName()); + litePalAttr.setVersion(litePalDB.getVersion()); + litePalAttr.setStorage(litePalDB.isExternalStorage() ? "external" : "internal"); + litePalAttr.setClassNames(litePalDB.getClassNames()); + // set the extra key name only when use database other than default or litepal.xml not exists + if (!isDefaultDatabase(litePalDB.getDbName())) { + litePalAttr.setExtraKeyName(litePalDB.getDbName()); + litePalAttr.setCases("lower"); + } + Connector.clearLitePalOpenHelperInstance(); + } + + /** + * Switch the using database to default with configuration by litepal.xml. + */ + public static void useDefault() { + LitePalAttr.clearInstance(); + Connector.clearLitePalOpenHelperInstance(); + } + + /** + * Delete the specified database. + * @param dbName + * Name of database to delete. + * @return True if delete success, false otherwise. + */ + public static boolean deleteDatabase(String dbName) { + if (!TextUtils.isEmpty(dbName)) { + if (!dbName.endsWith(Const.Config.DB_NAME_SUFFIX)) { + dbName = dbName + Const.Config.DB_NAME_SUFFIX; + } + File dbFile = LitePalApplication.getContext().getDatabasePath(dbName); + if (dbFile.exists()) { + boolean result = dbFile.delete(); + if (result) { + removeVersionInSharedPreferences(dbName); + Connector.clearLitePalOpenHelperInstance(); + } + return result; + } + String path = LitePalApplication.getContext().getExternalFilesDir("") + "/databases/"; + dbFile = new File(path + dbName); + boolean result = dbFile.delete(); + if (result) { + removeVersionInSharedPreferences(dbName); + Connector.clearLitePalOpenHelperInstance(); + } + return result; + } + return false; + } + + /** + * Remove the database version in SharedPreferences file. + * @param dbName + */ + private static void removeVersionInSharedPreferences(String dbName) { + if (isDefaultDatabase(dbName)) { + SharedUtil.removeVersion(null); + } else { + SharedUtil.removeVersion(dbName); + } + } + + /** + * Check the dbName is default database or not. If it's same as dbName in litepal.xml, then it is + * default database. + * @param dbName + * Name of database to check. + * @return True if it's default database, false otherwise. + */ + private static boolean isDefaultDatabase(String dbName) { + if (BaseUtility.isLitePalXMLExists()) { + if (!dbName.endsWith(Const.Config.DB_NAME_SUFFIX)) { + dbName = dbName + Const.Config.DB_NAME_SUFFIX; + } + LitePalConfig config = LitePalParser.parseLitePalConfiguration(); + String defaultDbName = config.getDbName(); + if (!defaultDbName.endsWith(Const.Config.DB_NAME_SUFFIX)) { + defaultDbName = defaultDbName + Const.Config.DB_NAME_SUFFIX; + } + return dbName.equalsIgnoreCase(defaultDbName); + } + return false; + } + +} diff --git a/YFDXJ/litepal/src/main/java/org/litepal/LitePalApplication.java b/YFDXJ/litepal/src/main/java/org/litepal/LitePalApplication.java new file mode 100644 index 0000000..a4df0a7 --- /dev/null +++ b/YFDXJ/litepal/src/main/java/org/litepal/LitePalApplication.java @@ -0,0 +1,79 @@ +/* + * Copyright (C) Tony Green, LitePal Framework Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.litepal; + +import org.litepal.exceptions.GlobalException; + +import android.app.Application; +import android.content.Context; + +/** + * Base class of LitePal to make things easier when developers need to use + * context. When you need context, just use + * LitePalApplication.getContext(). To make this function work, you need + * to configure your AndroidManifest.xml. Specifying + * "org.litepal.LitePalApplication" as the application name in your + * <application> tag to enable LitePal get the context. Of course if you + * need to write your own Application class, LitePal can still live with that. + * But just remember make your own Application class inherited from + * LitePalApplication instead of inheriting from Application directly. This can + * make all things work without side effects.
+ * Besides if you don't want use the above way, you can also call the LitePal.initialize(Context) + * method to do the same job. Just remember call this method as early as possible, in Application's onCreate() + * method will be fine. + * + * @author Tony Green + * @since 1.0 + */ +public class LitePalApplication extends Application { + + /** + * Global application context. + */ + static Context sContext; + + /** + * Construct of LitePalApplication. Initialize application context. + */ + public LitePalApplication() { + sContext = this; + } + + /** + * Deprecated. Use {@link LitePal#initialize(Context)} instead. + * @param context + * Application context. + */ + @Deprecated + public static void initialize(Context context) { + sContext = context; + } + + /** + * Get the global application context. + * + * @return Application context. + * @throws org.litepal.exceptions.GlobalException + */ + public static Context getContext() { + if (sContext == null) { + throw new GlobalException(GlobalException.APPLICATION_CONTEXT_IS_NULL); + } + return sContext; + } + +} diff --git a/YFDXJ/litepal/src/main/java/org/litepal/LitePalBase.java b/YFDXJ/litepal/src/main/java/org/litepal/LitePalBase.java new file mode 100644 index 0000000..d8cc46e --- /dev/null +++ b/YFDXJ/litepal/src/main/java/org/litepal/LitePalBase.java @@ -0,0 +1,693 @@ +/* + * Copyright (C) Tony Green, LitePal Framework Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.litepal; + +import org.litepal.annotation.Column; +import org.litepal.crud.DataSupport; +import org.litepal.crud.model.AssociationsInfo; +import org.litepal.exceptions.DatabaseGenerateException; +import org.litepal.parser.LitePalAttr; +import org.litepal.tablemanager.model.AssociationsModel; +import org.litepal.tablemanager.model.ColumnModel; +import org.litepal.tablemanager.model.GenericModel; +import org.litepal.tablemanager.model.TableModel; +import org.litepal.tablemanager.typechange.BlobOrm; +import org.litepal.tablemanager.typechange.BooleanOrm; +import org.litepal.tablemanager.typechange.DateOrm; +import org.litepal.tablemanager.typechange.DecimalOrm; +import org.litepal.tablemanager.typechange.NumericOrm; +import org.litepal.tablemanager.typechange.OrmChange; +import org.litepal.tablemanager.typechange.TextOrm; +import org.litepal.util.BaseUtility; +import org.litepal.util.Const; +import org.litepal.util.DBUtility; + +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * Base class of all the LitePal components. If each component need to + * interactive with other components or they have some same logic with duplicate + * codes, LitePalBase may be the solution. + * + * @author Tony Green + * @since 1.1 + */ +public abstract class LitePalBase { + + public static final String TAG = "LitePalBase"; + + /** + * Action to get associations. + */ + private static final int GET_ASSOCIATIONS_ACTION = 1; + + /** + * Action to get association info. + */ + private static final int GET_ASSOCIATION_INFO_ACTION = 2; + + /** + * All the supporting mapping types currently in the array. + */ + private OrmChange[] typeChangeRules = { new NumericOrm(), new TextOrm(), new BooleanOrm(), + new DecimalOrm(), new DateOrm(), new BlobOrm()}; + + /** + * This is map of class name to fields list. Indicates that each class has which supported fields. + */ + private Map> classFieldsMap = new HashMap>(); + + /** + * This is map of class name to generic fields list. Indicates that each class has which supported generic fields. + */ + private Map> classGenericFieldsMap = new HashMap>(); + + /** + * The collection contains all association models. + */ + private Collection mAssociationModels; + + /** + * The collection contains all association info. + */ + private Collection mAssociationInfos; + + /** + * The collection contains all generic models. + */ + private Collection mGenericModels; + + /** + * This method is used to get the table model by the class name passed + * in. The principle to generate table model is that each field in the class + * with non-static modifier and has a type among int/Integer, long/Long, + * short/Short, float/Float, double/Double, char/Character, boolean/Boolean + * or String, would generate a column with same name as corresponding field. + * If users don't want some of the fields map a column, declare an ignore + * annotation with {@link Column#ignore()}. + * + * @param className + * The full name of the class to map in database. + * @return A table model with table name, class name and the map of column + * name and column type. + */ + protected TableModel getTableModel(String className) { + String tableName = DBUtility.getTableNameByClassName(className); + TableModel tableModel = new TableModel(); + tableModel.setTableName(tableName); + tableModel.setClassName(className); + List supportedFields = getSupportedFields(className); + for (Field field : supportedFields) { + ColumnModel columnModel = convertFieldToColumnModel(field); + tableModel.addColumnModel(columnModel); + } + return tableModel; + } + + /** + * This method is used to get association models depends on the given class + * name list. + * + * @param classNames + * The names of the classes that want to get their associations. + * @return Collection of association models. + */ + protected Collection getAssociations(List classNames) { + if (mAssociationModels == null) { + mAssociationModels = new HashSet(); + } + if (mGenericModels == null) { + mGenericModels = new HashSet(); + } + mAssociationModels.clear(); + mGenericModels.clear(); + for (String className : classNames) { + analyzeClassFields(className, GET_ASSOCIATIONS_ACTION); + } + return mAssociationModels; + } + + /** + * Get all generic models for create generic tables. + * @return All generic models. + */ + protected Collection getGenericModels() { + return mGenericModels; + } + + /** + * Get the association info model by the class name. + * + * @param className + * The class name to introspection. + * @return Collection of association info. + */ + protected Collection getAssociationInfo(String className) { + if (mAssociationInfos == null) { + mAssociationInfos = new HashSet(); + } + mAssociationInfos.clear(); + analyzeClassFields(className, GET_ASSOCIATION_INFO_ACTION); + return mAssociationInfos; + } + + /** + * Find all the fields in the class. But not each field is supported to add + * a column to the table. Only the basic data types and String are + * supported. This method will intercept all the types which are not + * supported and return a new list of supported fields. + * + * @param className + * The full name of the class. + * @return A list of supported fields. + */ + protected List getSupportedFields(String className) { + List fieldList = classFieldsMap.get(className); + if (fieldList == null) { + List supportedFields = new ArrayList(); + Class clazz; + try { + clazz = Class.forName(className); + } catch (ClassNotFoundException e) { + throw new DatabaseGenerateException(DatabaseGenerateException.CLASS_NOT_FOUND + className); + } + recursiveSupportedFields(clazz, supportedFields); + classFieldsMap.put(className, supportedFields); + return supportedFields; + } + return fieldList; + } + + /** + * Find all supported generic fields in the class. Supporting rule is in {@link BaseUtility#isGenericTypeSupported(String)}. + * @param className + * The full name of the class. + * @return A list of supported generic fields. + */ + protected List getSupportedGenericFields(String className) { + List genericFieldList = classGenericFieldsMap.get(className); + if (genericFieldList == null) { + List supportedGenericFields = new ArrayList(); + Class clazz; + try { + clazz = Class.forName(className); + } catch (ClassNotFoundException e) { + throw new DatabaseGenerateException(DatabaseGenerateException.CLASS_NOT_FOUND + className); + } + recursiveSupportedGenericFields(clazz, supportedGenericFields); + classGenericFieldsMap.put(className, supportedGenericFields); + return supportedGenericFields; + } + return genericFieldList; + } + + /** + * If the field type implements from List or Set, regard it as a collection. + * + * @param fieldType + * The field type. + * @return True if the field type is collection, false otherwise. + */ + protected boolean isCollection(Class fieldType) { + return isList(fieldType) || isSet(fieldType); + } + + /** + * If the field type implements from List, regard it as a list. + * + * @param fieldType + * The field type. + * @return True if the field type is List, false otherwise. + */ + protected boolean isList(Class fieldType) { + return List.class.isAssignableFrom(fieldType); + } + + /** + * If the field type implements from Set, regard it as a set. + * + * @param fieldType + * The field type. + * @return True if the field type is Set, false otherwise. + */ + protected boolean isSet(Class fieldType) { + return Set.class.isAssignableFrom(fieldType); + } + + /** + * Judge the passed in column is an id column or not. The column named id or + * _id will be considered as id column. + * + * @param columnName + * The name of column. + * @return Return true if it's id column, otherwise return false. + */ + protected boolean isIdColumn(String columnName) { + return "_id".equalsIgnoreCase(columnName) || "id".equalsIgnoreCase(columnName); + } + + /** + * If two tables are associated, one table have a foreign key column. The + * foreign key column name will be the associated table name with _id + * appended. + * + * @param associatedTableName + * The associated table name. + * @return The foreign key column name. + */ + protected String getForeignKeyColumnName(String associatedTableName) { + return BaseUtility.changeCase(associatedTableName + "_id"); + } + + /** + * Get the column type for creating table by field type. + * @param fieldType + * Type of field. + * @return The column type for creating table. + */ + protected String getColumnType(String fieldType) { + String columnType; + for (OrmChange ormChange : typeChangeRules) { + columnType = ormChange.object2Relation(fieldType); + if (columnType != null) { + return columnType; + } + } + return null; + } + + /** + * Get the generic type class of List or Set. If there's no generic type of + * List or Set return null. + * + * @param field + * A generic type field. + * @return The generic type of List or Set. + */ + protected Class getGenericTypeClass(Field field) { + Type genericType = field.getGenericType(); + if (genericType != null) { + if (genericType instanceof ParameterizedType) { + ParameterizedType parameterizedType = (ParameterizedType) genericType; + return (Class) parameterizedType.getActualTypeArguments()[0]; + } + } + return null; + } + + private void recursiveSupportedFields(Class clazz, List supportedFields) { + if (clazz == DataSupport.class || clazz == Object.class) { + return; + } + Field[] fields = clazz.getDeclaredFields(); + if (fields != null && fields.length > 0) { + for (Field field : fields) { + Column annotation = field.getAnnotation(Column.class); + if (annotation != null && annotation.ignore()) { + continue; + } + int modifiers = field.getModifiers(); + if (!Modifier.isStatic(modifiers)) { + Class fieldTypeClass = field.getType(); + String fieldType = fieldTypeClass.getName(); + if (BaseUtility.isFieldTypeSupported(fieldType)) { + supportedFields.add(field); + } + } + } + } + recursiveSupportedFields(clazz.getSuperclass(), supportedFields); + } + + private void recursiveSupportedGenericFields(Class clazz, List supportedGenericFields) { + if (clazz == DataSupport.class || clazz == Object.class) { + return; + } + Field[] fields = clazz.getDeclaredFields(); + if (fields != null && fields.length > 0) { + for (Field field : fields) { + Column annotation = field.getAnnotation(Column.class); + if (annotation != null && annotation.ignore()) { + continue; + } + int modifiers = field.getModifiers(); + if (!Modifier.isStatic(modifiers) && isCollection(field.getType())) { + String genericTypeName = getGenericTypeName(field); + if (BaseUtility.isGenericTypeSupported(genericTypeName)) { + supportedGenericFields.add(field); + } + } + } + } + recursiveSupportedGenericFields(clazz.getSuperclass(), supportedGenericFields); + } + + /** + * Introspection of the passed in class. Analyze the fields of current class + * and find out the associations of it. + * + * @param className + * The class name to introspection. + * @param action + * Between {@link org.litepal.LitePalBase#GET_ASSOCIATIONS_ACTION} and + * {@link org.litepal.LitePalBase#GET_ASSOCIATION_INFO_ACTION} + */ + private void analyzeClassFields(String className, int action) { + try { + Class dynamicClass = Class.forName(className); + Field[] fields = dynamicClass.getDeclaredFields(); + for (Field field : fields) { + if (isPrivateAndNonPrimitive(field)) { + oneToAnyConditions(className, field, action); + manyToAnyConditions(className, field, action); + } + } + } catch (ClassNotFoundException ex) { + ex.printStackTrace(); + throw new DatabaseGenerateException(DatabaseGenerateException.CLASS_NOT_FOUND + className); + } + } + + /** + * Judge the field is a private non primitive field or not. + * + * @param field + * The field to judge. + * @return True if the field is private and non primitive, + * false otherwise. + */ + private boolean isPrivateAndNonPrimitive(Field field) { + return Modifier.isPrivate(field.getModifiers()) && !field.getType().isPrimitive(); + } + + /** + * Deals with one to any association conditions. e.g. Song and Album. An + * album have many songs, and a song belongs to one album. So if there's an + * Album model defined in Song with private modifier, and in Album there's a + * List or Set with generic type of Song and declared as private modifier, + * they are one2many association. If there's no List or Set defined in + * Album, they will become one2one associations. If there's also a Song + * model defined in Album with private modifier, maybe the album just have + * one song, they are one2one association too. + * + * When it's many2one association, it's easy to just simply add a foreign id + * column to the many side model's table. But when it comes to many2many + * association, it can not be done without intermediate join table in + * database. LitePal assumes that this join table's name is the + * concatenation of the two target table names in alphabetical order. + * + * @param className + * Source class name. + * @param field + * A field of source class. + * @param action + * Between {@link org.litepal.LitePalBase#GET_ASSOCIATIONS_ACTION} and + * {@link org.litepal.LitePalBase#GET_ASSOCIATION_INFO_ACTION} + * + * @throws ClassNotFoundException + */ + private void oneToAnyConditions(String className, Field field, int action) throws ClassNotFoundException { + Class fieldTypeClass = field.getType(); + // If the mapping list contains the class name + // defined in one class. + if (LitePalAttr.getInstance().getClassNames().contains(fieldTypeClass.getName())) { + Class reverseDynamicClass = Class.forName(fieldTypeClass.getName()); + Field[] reverseFields = reverseDynamicClass.getDeclaredFields(); + // Look up if there's a reverse association + // definition in the reverse class. + boolean reverseAssociations = false; + // Begin to check the fields of the defined + // class. + for (int i = 0; i < reverseFields.length; i++) { + Field reverseField = reverseFields[i]; + if (!Modifier.isStatic(reverseField.getModifiers())) { + Class reverseFieldTypeClass = reverseField.getType(); + // If there's the from class name in the + // defined class, they are one2one bidirectional + // associations. + if (className.equals(reverseFieldTypeClass.getName())) { + if (action == GET_ASSOCIATIONS_ACTION) { + addIntoAssociationModelCollection(className, fieldTypeClass.getName(), + fieldTypeClass.getName(), Const.Model.ONE_TO_ONE); + } else if (action == GET_ASSOCIATION_INFO_ACTION) { + addIntoAssociationInfoCollection(className, fieldTypeClass.getName(), + fieldTypeClass.getName(), field, reverseField, Const.Model.ONE_TO_ONE); + } + reverseAssociations = true; + } + // If there's the from class Set or List in + // the defined class, they are many2one bidirectional + // associations. + else if (isCollection(reverseFieldTypeClass)) { + String genericTypeName = getGenericTypeName(reverseField); + if (className.equals(genericTypeName)) { + if (action == GET_ASSOCIATIONS_ACTION) { + addIntoAssociationModelCollection(className, fieldTypeClass.getName(), + className, Const.Model.MANY_TO_ONE); + } else if (action == GET_ASSOCIATION_INFO_ACTION) { + addIntoAssociationInfoCollection(className, fieldTypeClass.getName(), + className, field, reverseField, Const.Model.MANY_TO_ONE); + } + reverseAssociations = true; + } + } + } + } + // If there's no from class in the defined class, they are + // one2one unidirectional associations. + if (!reverseAssociations) { + if (action == GET_ASSOCIATIONS_ACTION) { + addIntoAssociationModelCollection(className, fieldTypeClass.getName(), + fieldTypeClass.getName(), Const.Model.ONE_TO_ONE); + } else if (action == GET_ASSOCIATION_INFO_ACTION) { + addIntoAssociationInfoCollection(className, fieldTypeClass.getName(), + fieldTypeClass.getName(), field, null, Const.Model.ONE_TO_ONE); + } + } + } + } + + /** + * Deals with one to any association conditions. e.g. Song and Album. An + * album have many songs, and a song belongs to one album. So if there's an + * Album model defined in Song with private modifier, and in Album there's a + * List or Set with generic type of Song and declared as private modifier, + * they are one2many association. If there's no List or Set defined in + * Album, they will become one2one associations. If there's also a Song + * model defined in Album with private modifier, maybe the album just have + * one song, they are one2one association too. + * + * When it's many2one association, it's easy to just simply add a foreign id + * column to the many side model's table. But when it comes to many2many + * association, it can not be done without intermediate join table in + * database. LitePal assumes that this join table's name is the + * concatenation of the two target table names in alphabetical order. + * + * @param className + * Source class name. + * @param field + * A field of source class. + * @param action + * Between {@link org.litepal.LitePalBase#GET_ASSOCIATIONS_ACTION} and + * {@link org.litepal.LitePalBase#GET_ASSOCIATION_INFO_ACTION} + * + * @throws ClassNotFoundException + */ + private void manyToAnyConditions(String className, Field field, int action) throws ClassNotFoundException { + if (isCollection(field.getType())) { + String genericTypeName = getGenericTypeName(field); + // If the mapping list contains the genericTypeName, begin to check + // this genericTypeName class. + if (LitePalAttr.getInstance().getClassNames().contains(genericTypeName)) { + Class reverseDynamicClass = Class.forName(genericTypeName); + Field[] reverseFields = reverseDynamicClass.getDeclaredFields(); + // Look up if there's a reverse association + // definition in the reverse class. + boolean reverseAssociations = false; + for (int i = 0; i < reverseFields.length; i++) { + Field reverseField = reverseFields[i]; + // Only map private fields + if (!Modifier.isStatic(reverseField.getModifiers())) { + Class reverseFieldTypeClass = reverseField.getType(); + // If there's a from class name defined in the reverse + // class, they are many2one bidirectional + // associations. + if (className.equals(reverseFieldTypeClass.getName())) { + if (action == GET_ASSOCIATIONS_ACTION) { + addIntoAssociationModelCollection(className, genericTypeName, + genericTypeName, Const.Model.MANY_TO_ONE); + } else if (action == GET_ASSOCIATION_INFO_ACTION) { + addIntoAssociationInfoCollection(className, genericTypeName, genericTypeName, + field, reverseField, Const.Model.MANY_TO_ONE); + } + reverseAssociations = true; + } + // If there's a List or Set contains from class name + // defined in the reverse class, they are many2many + // association. + else if (isCollection(reverseFieldTypeClass)) { + String reverseGenericTypeName = getGenericTypeName(reverseField); + if (className.equals(reverseGenericTypeName)) { + if (action == GET_ASSOCIATIONS_ACTION) { + addIntoAssociationModelCollection(className, genericTypeName, null, + Const.Model.MANY_TO_MANY); + } else if (action == GET_ASSOCIATION_INFO_ACTION) { + addIntoAssociationInfoCollection(className, genericTypeName, null, field, + reverseField, Const.Model.MANY_TO_MANY); + } + reverseAssociations = true; + } + } + + } + } + // If there's no from class in the defined class, they + // are many2one unidirectional associations. + if (!reverseAssociations) { + if (action == GET_ASSOCIATIONS_ACTION) { + addIntoAssociationModelCollection(className, genericTypeName, + genericTypeName, Const.Model.MANY_TO_ONE); + } else if (action == GET_ASSOCIATION_INFO_ACTION) { + addIntoAssociationInfoCollection(className, genericTypeName, genericTypeName, + field, null, Const.Model.MANY_TO_ONE); + } + } + } else if(BaseUtility.isGenericTypeSupported(genericTypeName) && action == GET_ASSOCIATIONS_ACTION) { + Column annotation = field.getAnnotation(Column.class); + if (annotation != null && annotation.ignore()) { + return; + } + GenericModel genericModel = new GenericModel(); + genericModel.setTableName(DBUtility.getGenericTableName(className, field.getName())); + genericModel.setValueColumnName(DBUtility.convertToValidColumnName(field.getName())); + genericModel.setValueColumnType(getColumnType(genericTypeName)); + genericModel.setValueIdColumnName(DBUtility.getGenericValueIdColumnName(className)); + mGenericModels.add(genericModel); + } + } + } + + /** + * Package a {@link org.litepal.tablemanager.model.AssociationsModel}, and add it into + * {@link #mAssociationModels} Collection. + * + * @param className + * The class name for {@link org.litepal.tablemanager.model.AssociationsModel}. + * @param associatedClassName + * The associated class name for {@link org.litepal.tablemanager.model.AssociationsModel}. + * @param classHoldsForeignKey + * The class which holds foreign key. + * @param associationType + * The association type for {@link org.litepal.tablemanager.model.AssociationsModel}. + */ + private void addIntoAssociationModelCollection(String className, String associatedClassName, + String classHoldsForeignKey, int associationType) { + AssociationsModel associationModel = new AssociationsModel(); + associationModel.setTableName(DBUtility.getTableNameByClassName(className)); + associationModel.setAssociatedTableName(DBUtility.getTableNameByClassName(associatedClassName)); + associationModel.setTableHoldsForeignKey(DBUtility.getTableNameByClassName(classHoldsForeignKey)); + associationModel.setAssociationType(associationType); + mAssociationModels.add(associationModel); + } + + /** + * Package a {@link org.litepal.crud.model.AssociationsInfo}, and add it into + * {@link #mAssociationInfos} Collection. + * + * @param selfClassName + * The class name of self model. + * @param associatedClassName + * The class name of the class which associated with self class. + * @param classHoldsForeignKey + * The class which holds foreign key. + * @param associateOtherModelFromSelf + * The field of self class to declare has association with other + * class. + * @param associateSelfFromOtherModel + * The field of the associated class to declare has association + * with self class. + * @param associationType + * The association type. + */ + private void addIntoAssociationInfoCollection(String selfClassName, String associatedClassName, + String classHoldsForeignKey, Field associateOtherModelFromSelf, + Field associateSelfFromOtherModel, int associationType) { + AssociationsInfo associationInfo = new AssociationsInfo(); + associationInfo.setSelfClassName(selfClassName); + associationInfo.setAssociatedClassName(associatedClassName); + associationInfo.setClassHoldsForeignKey(classHoldsForeignKey); + associationInfo.setAssociateOtherModelFromSelf(associateOtherModelFromSelf); + associationInfo.setAssociateSelfFromOtherModel(associateSelfFromOtherModel); + associationInfo.setAssociationType(associationType); + mAssociationInfos.add(associationInfo); + } + + /** + * Get the generic type name of List or Set. If there's no generic type of + * List or Set return null. + * + * @param field + * A generic type field. + * @return The name of generic type of List of Set. + */ + protected String getGenericTypeName(Field field) { + Class genericTypeClass = getGenericTypeClass(field); + if (genericTypeClass != null) { + return genericTypeClass.getName(); + } + return null; + } + + /** + * Convert a field instance into A ColumnModel instance. ColumnModel can provide information + * when creating table. + * @param field + * A supported field to map into column. + * @return ColumnModel instance contains column information. + */ + private ColumnModel convertFieldToColumnModel(Field field) { + String fieldType = field.getType().getName(); + String columnType = getColumnType(fieldType); + boolean nullable = true; + boolean unique = false; + String defaultValue = ""; + Column annotation = field.getAnnotation(Column.class); + if (annotation != null) { + nullable = annotation.nullable(); + unique = annotation.unique(); + defaultValue = annotation.defaultValue(); + } + ColumnModel columnModel = new ColumnModel(); + columnModel.setColumnName(DBUtility.convertToValidColumnName(field.getName())); + columnModel.setColumnType(columnType); + columnModel.setNullable(nullable); + columnModel.setUnique(unique); + columnModel.setDefaultValue(defaultValue); + return columnModel; + } + +} diff --git a/YFDXJ/litepal/src/main/java/org/litepal/LitePalDB.java b/YFDXJ/litepal/src/main/java/org/litepal/LitePalDB.java new file mode 100644 index 0000000..97aaf25 --- /dev/null +++ b/YFDXJ/litepal/src/main/java/org/litepal/LitePalDB.java @@ -0,0 +1,129 @@ +/* + * Copyright (C) Tony Green, LitePal Framework Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.litepal; + +import org.litepal.parser.LitePalAttr; +import org.litepal.parser.LitePalConfig; +import org.litepal.parser.LitePalParser; + +import java.util.ArrayList; +import java.util.List; + +/** + * Configuration of LitePal database. It's similar to litepal.xml configuration, but allows to + * configure database details at runtime. This is very important when comes to support multiple + * databases functionality. + * + * @author Tony Green + * @since 1.4 + */ +public class LitePalDB { + + /** + * The version of database. + */ + private int version; + + /** + * The name of database. + */ + private String dbName; + + /** + * Indicates that the database file stores in external storage or not. + */ + private boolean isExternalStorage = false; + + /** + * All the model classes that want to map in the database. Each class should + * be given the full name including package name. + */ + private List classNames; + + /** + * Construct a LitePalDB instance from the default configuration by litepal.xml. But database + * name must be different than the default. + * @param dbName + * Name of database. + * @return A LitePalDB instance which used the default configuration in litepal.xml but with a specified database name. + */ + public static LitePalDB fromDefault(String dbName) { + LitePalConfig config = LitePalParser.parseLitePalConfiguration(); + LitePalDB litePalDB = new LitePalDB(dbName, config.getVersion()); + litePalDB.setExternalStorage("external".equals(config.getStorage())); + litePalDB.setClassNames(config.getClassNames()); + return litePalDB; + } + + /** + * Construct a LitePalDB instance. Database name and version are necessary fields. + * @param dbName + * Name of database. + * @param version + * Version of database. + */ + public LitePalDB(String dbName, int version) { + this.dbName = dbName; + this.version = version; + } + + public int getVersion() { + return version; + } + + public String getDbName() { + return dbName; + } + + public boolean isExternalStorage() { + return isExternalStorage; + } + + public void setExternalStorage(boolean isExternalStorage) { + this.isExternalStorage = isExternalStorage; + } + + /** + * Get the class name list. Always add table_schema as a value. + * + * @return The class name list. + */ + public List getClassNames() { + if (classNames == null) { + classNames = new ArrayList(); + classNames.add("org.litepal.model.Table_Schema"); + } else if (classNames.isEmpty()) { + classNames.add("org.litepal.model.Table_Schema"); + } + return classNames; + } + + /** + * Add a class name into the current mapping model list. + * + * @param className + * Full package class name. + */ + public void addClassName(String className) { + getClassNames().add(className); + } + + void setClassNames(List className) { + this.classNames = className; + } + +} \ No newline at end of file diff --git a/YFDXJ/litepal/src/main/java/org/litepal/annotation/Column.java b/YFDXJ/litepal/src/main/java/org/litepal/annotation/Column.java new file mode 100644 index 0000000..81179b0 --- /dev/null +++ b/YFDXJ/litepal/src/main/java/org/litepal/annotation/Column.java @@ -0,0 +1,54 @@ +/* + * Copyright (C) Tony Green, LitePal Framework Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.litepal.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Used for adding constraints to a column. Note that this annotation won't affect id column. + * + * @author Tony Green + * @since 1.3 + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.FIELD) +public @interface Column { + + /** + * Set nullable constraint for the column. + */ + boolean nullable() default true; + + /** + * Set unique constraint for the column. + */ + boolean unique() default false; + + /** + * Set default value with String type for the column regardless of what column type is. + */ + String defaultValue() default ""; + + /** + * Ignore to map this field into a column. + */ + boolean ignore() default false; + +} diff --git a/YFDXJ/litepal/src/main/java/org/litepal/crud/AssociationsAnalyzer.java b/YFDXJ/litepal/src/main/java/org/litepal/crud/AssociationsAnalyzer.java new file mode 100644 index 0000000..3b0f730 --- /dev/null +++ b/YFDXJ/litepal/src/main/java/org/litepal/crud/AssociationsAnalyzer.java @@ -0,0 +1,193 @@ +/* + * Copyright (C) Tony Green, LitePal Framework Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.litepal.crud; + +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashSet; + +import org.litepal.LitePalBase; +import org.litepal.crud.model.AssociationsInfo; +import org.litepal.exceptions.DataSupportException; +import org.litepal.util.DBUtility; + +/** + * Base class of associations analyzer. + * + * @author Tony Green + * @since 1.1 + */ +abstract class AssociationsAnalyzer extends DataHandler { + + /** + * Get the associated models collection of associated model. Used for + * reverse searching associations. + * + * @param associatedModel + * The associated model of baseObj. + * @param associationInfo + * To get reverse associated models collection. + * @return The associated models collection of associated model by analyzing + * associationInfo. + * @throws SecurityException + * @throws IllegalArgumentException + * @throws NoSuchMethodException + * @throws IllegalAccessException + * @throws java.lang.reflect.InvocationTargetException + */ + @SuppressWarnings("unchecked") + protected Collection getReverseAssociatedModels(DataSupport associatedModel, + AssociationsInfo associationInfo) throws SecurityException, IllegalArgumentException, + NoSuchMethodException, IllegalAccessException, InvocationTargetException { + return (Collection) getFieldValue(associatedModel, + associationInfo.getAssociateSelfFromOtherModel()); + } + + /** + * Set the associated models collection of associated model. Break quote of + * source collection. + * + * @param associatedModel + * The associated model of baseObj. + * @param associationInfo + * To get reverse associated models collection. + * @param associatedModelCollection + * The new associated models collection with same data as source + * collection but different quote. + * @throws SecurityException + * @throws IllegalArgumentException + * @throws NoSuchMethodException + * @throws IllegalAccessException + * @throws java.lang.reflect.InvocationTargetException + */ + protected void setReverseAssociatedModels(DataSupport associatedModel, + AssociationsInfo associationInfo, Collection associatedModelCollection) + throws SecurityException, IllegalArgumentException, NoSuchMethodException, + IllegalAccessException, InvocationTargetException { + setFieldValue(associatedModel, associationInfo.getAssociateSelfFromOtherModel(), + associatedModelCollection); + } + + /** + * Check the associated model collection. If the associated model collection + * is null, try to initialize the associated model collection by the given + * associated field. If the associated field is subclass of List, make an + * instance of ArrayList for associated model collection. If the associated + * field is subclass of Set, make an instance of HashSet for associated + * model collection. If the associated model collection is not null, doing + * nothing. + * + * @param associatedModelCollection + * The associated model collection to check null and initialize. + * @param associatedField + * The field to decide which type to initialize for associated + * model collection. + * @throws DataSupportException + */ + protected Collection checkAssociatedModelCollection( + Collection associatedModelCollection, Field associatedField) { + Collection collection = null; + if (isList(associatedField.getType())) { + collection = new ArrayList(); + } else if (isSet(associatedField.getType())) { + collection = new HashSet(); + } else { + throw new DataSupportException(DataSupportException.WRONG_FIELD_TYPE_FOR_ASSOCIATIONS); + } + if (associatedModelCollection != null) { + collection.addAll(associatedModelCollection); + } + return collection; + } + + /** + * Build the bidirectional association by setting the baseObj instance to + * the associated model. + * + * @param baseObj + * The instance of self model. + * @param associatedModel + * The associated model. + * @param associationInfo + * The association info to get the association. + * @throws SecurityException + * @throws IllegalArgumentException + * @throws NoSuchMethodException + * @throws IllegalAccessException + * @throws java.lang.reflect.InvocationTargetException + */ + protected void buildBidirectionalAssociations(DataSupport baseObj, DataSupport associatedModel, + AssociationsInfo associationInfo) throws SecurityException, IllegalArgumentException, + NoSuchMethodException, IllegalAccessException, InvocationTargetException { + setFieldValue(associatedModel, associationInfo.getAssociateSelfFromOtherModel(), + baseObj); + } + + /** + * If the associated model is saved, add its' name and id to baseObj by + * calling {@link org.litepal.crud.DataSupport#addAssociatedModelWithFK(String, long)}. Or if + * the baseObj is saved, add its' name and id to associated model by calling + * {@link org.litepal.crud.DataSupport#addAssociatedModelWithoutFK(String, long)}. + * + * @param baseObj + * The baseObj currently want to persist. + * @param associatedModel + * The associated model. + */ + protected void dealsAssociationsOnTheSideWithoutFK(DataSupport baseObj, + DataSupport associatedModel) { + if (associatedModel != null) { + if (associatedModel.isSaved()) { + baseObj.addAssociatedModelWithFK(associatedModel.getTableName(), + associatedModel.getBaseObjId()); + } else { + if (baseObj.isSaved()) { + associatedModel.addAssociatedModelWithoutFK(baseObj.getTableName(), + baseObj.getBaseObjId()); + } + } + } + } + + /** + * If the associated model of self model is null, the FK value in database + * should be cleared if it exists when updating. + * + * @param baseObj + * The baseObj currently want to persist or update. + * @param associationInfo + * The associated info analyzed by + * {@link LitePalBase#getAssociationInfo(String)}. + */ + protected void mightClearFKValue(DataSupport baseObj, AssociationsInfo associationInfo) { + baseObj.addFKNameToClearSelf(getForeignKeyName(associationInfo)); + } + + /** + * Get foreign key name by {@link org.litepal.crud.model.AssociationsInfo}. + * + * @param associationInfo + * To get foreign key name from. + * @return The foreign key name. + */ + private String getForeignKeyName(AssociationsInfo associationInfo) { + return getForeignKeyColumnName(DBUtility.getTableNameByClassName(associationInfo + .getAssociatedClassName())); + } +} diff --git a/YFDXJ/litepal/src/main/java/org/litepal/crud/ClusterQuery.java b/YFDXJ/litepal/src/main/java/org/litepal/crud/ClusterQuery.java new file mode 100644 index 0000000..4d77ee2 --- /dev/null +++ b/YFDXJ/litepal/src/main/java/org/litepal/crud/ClusterQuery.java @@ -0,0 +1,914 @@ +/* + * Copyright (C) Tony Green, LitePal Framework Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.litepal.crud; + +import java.util.List; + +import org.litepal.LitePal; +import org.litepal.crud.async.AverageExecutor; +import org.litepal.crud.async.CountExecutor; +import org.litepal.crud.async.FindExecutor; +import org.litepal.crud.async.FindMultiExecutor; +import org.litepal.tablemanager.Connector; +import org.litepal.util.BaseUtility; +import org.litepal.util.DBUtility; + +/** + * Allows developers to query tables with cluster style. + * + * @author Tony Green + * @since 1.1 + */ +public class ClusterQuery { + + /** + * Representing the selected columns in SQL. + */ + String[] mColumns; + + /** + * Representing the where clause in SQL. + */ + String[] mConditions; + + /** + * Representing the order by clause in SQL. + */ + String mOrderBy; + + /** + * Representing the limit clause in SQL. + */ + String mLimit; + + /** + * Representing the offset in SQL. + */ + String mOffset; + + /** + * Do not allow to create instance by developers. + */ + ClusterQuery() { + } + + /** + * Declaring to query which columns in table. + * + *

+	 * DataSupport.select("name", "age").find(Person.class);
+	 * 
+ * + * This will find all rows with name and age columns in Person table. + * + * @param columns + * A String array of which columns to return. Passing null will + * return all columns. + * + * @return A ClusterQuery instance. + */ + public ClusterQuery select(String... columns) { + mColumns = columns; + return this; + } + + /** + * Declaring to query which rows in table. + * + *
+	 * DataSupport.where("name = ? or age > ?", "Tom", "14").find(Person.class);
+	 * 
+ * + * This will find rows which name is Tom or age greater than 14 in Person + * table. + * + * @param conditions + * A filter declaring which rows to return, formatted as an SQL + * WHERE clause. Passing null will return all rows. + * @return A ClusterQuery instance. + */ + public ClusterQuery where(String... conditions) { + mConditions = conditions; + return this; + } + + /** + * Declaring how to order the rows queried from table. + * + *
+	 * DataSupport.order("name desc").find(Person.class);
+	 * 
+ * + * This will find all rows in Person table sorted by name with inverted + * order. + * + * @param column + * How to order the rows, formatted as an SQL ORDER BY clause. + * Passing null will use the default sort order, which may be + * unordered. + * @return A ClusterQuery instance. + */ + public ClusterQuery order(String column) { + mOrderBy = column; + return this; + } + + /** + * Limits the number of rows returned by the query. + * + *
+	 * DataSupport.limit(2).find(Person.class);
+	 * 
+ * + * This will find the top 2 rows in Person table. + * + * @param value + * Limits the number of rows returned by the query, formatted as + * LIMIT clause. + * @return A ClusterQuery instance. + */ + public ClusterQuery limit(int value) { + mLimit = String.valueOf(value); + return this; + } + + /** + * Declaring the offset of rows returned by the query. This method must be + * used with {@link #limit(int)}, or nothing will return. + * + *
+	 * DataSupport.limit(1).offset(2).find(Person.class);
+	 * 
+ * + * This will find the third row in Person table. + * + * @param value + * The offset amount of rows returned by the query. + * @return A ClusterQuery instance. + */ + public ClusterQuery offset(int value) { + mOffset = String.valueOf(value); + return this; + } + + /** + * Finds multiple records by the cluster parameters. You can use the below + * way to finish a complicated query: + * + *
+	 * DataSupport.select("name").where("age > ?", "14").order("age").limit(1).offset(2)
+	 * 		.find(Person.class);
+	 * 
+ * + * You can also do the same job with SQLiteDatabase like this: + * + *
+	 * getSQLiteDatabase().query("Person", "name", "age > ?", new String[] { "14" }, null, null, "age",
+	 * 		"2,1");
+	 * 
+ * + * Obviously, the first way is much more semantic.
+ * Note that the associated models won't be loaded by default considering + * the efficiency, but you can do that by using + * {@link org.litepal.crud.ClusterQuery#find(Class, boolean)}. + * + * @param modelClass + * Which table to query and the object type to return as a list. + * @return An object list with founded data from database, or an empty list. + */ + public List find(Class modelClass) { + return find(modelClass, false); + } + + /** + * Basically same as {@link #find(Class)} but pending to a new thread for executing. + * + * @param modelClass + * Which table to query and the object type to return as a list. + * @return A FindMultiExecutor instance. + */ + public FindMultiExecutor findAsync(final Class modelClass) { + return findAsync(modelClass, false); + } + + /** + * It is mostly same as {@link org.litepal.crud.ClusterQuery#find(Class)} but an isEager + * parameter. If set true the associated models will be loaded as well. + *
+ * Note that isEager will only work for one deep level relation, considering the query efficiency. + * You have to implement on your own if you need to load multiple deepness of relation at once. + * + * @param modelClass + * Which table to query and the object type to return as a list. + * @param isEager + * True to load the associated models, false not. + * @return An object list with founded data from database, or an empty list. + */ + public synchronized List find(Class modelClass, boolean isEager) { + QueryHandler queryHandler = new QueryHandler(Connector.getDatabase()); + String limit; + if (mOffset == null) { + limit = mLimit; + } else { + if (mLimit == null) { + mLimit = "0"; + } + limit = mOffset + "," + mLimit; + } + return queryHandler.onFind(modelClass, mColumns, mConditions, mOrderBy, limit, isEager); + } + + /** + * Basically same as {@link #find(Class, boolean)} but pending to a new thread for executing. + * + * @param modelClass + * Which table to query and the object type to return as a list. + * @param isEager + * True to load the associated models, false not. + * @return A FindMultiExecutor instance. + */ + public FindMultiExecutor findAsync(final Class modelClass, final boolean isEager) { + final FindMultiExecutor executor = new FindMultiExecutor(); + Runnable runnable = new Runnable() { + @Override + public void run() { + synchronized (DataSupport.class) { + final List t = find(modelClass, isEager); + if (executor.getListener() != null) { + LitePal.getHandler().post(new Runnable() { + @Override + public void run() { + executor.getListener().onFinish(t); + } + }); + } + } + } + }; + executor.submit(runnable); + return executor; + } + + /** + * Finds the first record by the cluster parameters. You can use the below + * way to finish a complicated query: + * + *
+     * DataSupport.select("name").where("age > ?", "14").order("age").limit(1).offset(2)
+     * 		.findFirst(Person.class);
+     * 
+ * + * Note that the associated models won't be loaded by default considering + * the efficiency, but you can do that by using + * {@link org.litepal.crud.ClusterQuery#findFirst(Class, boolean)}. + * + * @param modelClass + * Which table to query and the object type to return. + * @return An object with founded data from database, or null. + */ + public T findFirst(Class modelClass) { + return findFirst(modelClass, false); + } + + /** + * Basically same as {@link #findFirst(Class)} but pending to a new thread for executing. + * + * @param modelClass + * Which table to query and the object type to return. + * @return A FindExecutor instance. + */ + public FindExecutor findFirstAsync(Class modelClass) { + return findFirstAsync(modelClass, false); + } + + /** + * It is mostly same as {@link org.litepal.crud.ClusterQuery#findFirst(Class)} but an isEager + * parameter. If set true the associated models will be loaded as well. + *
+ * Note that isEager will only work for one deep level relation, considering the query efficiency. + * You have to implement on your own if you need to load multiple deepness of relation at once. + * + * @param modelClass + * Which table to query and the object type to return. + * @param isEager + * True to load the associated models, false not. + * @return An object with founded data from database, or null. + */ + public T findFirst(Class modelClass, boolean isEager) { + List list = find(modelClass, isEager); + if (list.size() > 0) { + return list.get(0); + } + return null; + } + + /** + * Basically same as {@link #findFirst(Class, boolean)} but pending to a new thread for executing. + * + * @param modelClass + * Which table to query and the object type to return. + * @param isEager + * True to load the associated models, false not. + * @return A FindExecutor instance. + */ + public FindExecutor findFirstAsync(final Class modelClass, final boolean isEager) { + final FindExecutor executor = new FindExecutor(); + Runnable runnable = new Runnable() { + @Override + public void run() { + synchronized (DataSupport.class) { + final T t = findFirst(modelClass, isEager); + if (executor.getListener() != null) { + LitePal.getHandler().post(new Runnable() { + @Override + public void run() { + executor.getListener().onFinish(t); + } + }); + } + } + } + }; + executor.submit(runnable); + return executor; + } + + /** + * Finds the last record by the cluster parameters. You can use the below + * way to finish a complicated query: + * + *
+     * DataSupport.select("name").where("age > ?", "14").order("age").limit(1).offset(2)
+     * 		.findLast(Person.class);
+     * 
+ * + * Note that the associated models won't be loaded by default considering + * the efficiency, but you can do that by using + * {@link org.litepal.crud.ClusterQuery#findLast(Class, boolean)}. + * + * @param modelClass + * Which table to query and the object type to return. + * @return An object with founded data from database, or null. + */ + public T findLast(Class modelClass) { + return findLast(modelClass, false); + } + + /** + * Basically same as {@link #findLast(Class)} but pending to a new thread for executing. + * + * @param modelClass + * Which table to query and the object type to return. + * @return A FindExecutor instance. + */ + public FindExecutor findLastAsync(Class modelClass) { + return findLastAsync(modelClass, false); + } + + /** + * It is mostly same as {@link org.litepal.crud.ClusterQuery#findLast(Class)} but an isEager + * parameter. If set true the associated models will be loaded as well. + *
+ * Note that isEager will only work for one deep level relation, considering the query efficiency. + * You have to implement on your own if you need to load multiple deepness of relation at once. + * + * @param modelClass + * Which table to query and the object type to return. + * @param isEager + * True to load the associated models, false not. + * @return An object with founded data from database, or null. + */ + public T findLast(Class modelClass, boolean isEager) { + List list = find(modelClass, isEager); + int size = list.size(); + if (size > 0) { + return list.get(size - 1); + } + return null; + } + + /** + * Basically same as {@link #findLast(Class, boolean)} but pending to a new thread for executing. + * + * @param modelClass + * Which table to query and the object type to return. + * @param isEager + * True to load the associated models, false not. + * @return A FindExecutor instance. + */ + public FindExecutor findLastAsync(final Class modelClass, final boolean isEager) { + final FindExecutor executor = new FindExecutor(); + Runnable runnable = new Runnable() { + @Override + public void run() { + synchronized (DataSupport.class) { + final T t = findLast(modelClass, isEager); + if (executor.getListener() != null) { + LitePal.getHandler().post(new Runnable() { + @Override + public void run() { + executor.getListener().onFinish(t); + } + }); + } + } + } + }; + executor.submit(runnable); + return executor; + } + + /** + * Count the records. + * + *
+	 * DataSupport.count(Person.class);
+	 * 
+ * + * This will count all rows in person table.
+ * You can also specify a where clause when counting. + * + *
+	 * DataSupport.where("age > ?", "15").count(Person.class);
+	 * 
+ * + * @param modelClass + * Which table to query from by class. + * @return Count of the specified table. + */ + public synchronized int count(Class modelClass) { + return count(BaseUtility.changeCase(modelClass.getSimpleName())); + } + + /** + * Basically same as {@link #count(Class)} but pending to a new thread for executing. + * + * @param modelClass + * Which table to query from by class. + * @return A CountExecutor instance. + */ + public CountExecutor countAsync(Class modelClass) { + return countAsync(BaseUtility.changeCase(DBUtility.getTableNameByClassName(modelClass.getName()))); + } + + /** + * Count the records. + * + *
+	 * DataSupport.count("person");
+	 * 
+ * + * This will count all rows in person table.
+ * You can also specify a where clause when counting. + * + *
+	 * DataSupport.where("age > ?", "15").count("person");
+	 * 
+ * + * @param tableName + * Which table to query from. + * @return Count of the specified table. + */ + public synchronized int count(String tableName) { + QueryHandler queryHandler = new QueryHandler(Connector.getDatabase()); + return queryHandler.onCount(tableName, mConditions); + } + + /** + * Basically same as {@link #count(String)} but pending to a new thread for executing. + * + * @param tableName + * Which table to query from. + * @return A CountExecutor instance. + */ + public CountExecutor countAsync(final String tableName) { + final CountExecutor executor = new CountExecutor(); + Runnable runnable = new Runnable() { + @Override + public void run() { + synchronized (DataSupport.class) { + final int count = count(tableName); + if (executor.getListener() != null) { + LitePal.getHandler().post(new Runnable() { + @Override + public void run() { + executor.getListener().onFinish(count); + } + }); + } + } + } + }; + executor.submit(runnable); + return executor; + } + + /** + * Calculates the average value on a given column. + * + *
+	 * DataSupport.average(Person.class, "age");
+	 * 
+ * + * You can also specify a where clause when calculating. + * + *
+	 * DataSupport.where("age > ?", "15").average(Person.class, "age");
+	 * 
+ * + * @param modelClass + * Which table to query from by class. + * @param column + * The based on column to calculate. + * @return The average value on a given column. + */ + public synchronized double average(Class modelClass, String column) { + return average(BaseUtility.changeCase(modelClass.getSimpleName()), column); + } + + /** + * Basically same as {@link #average(Class, String)} but pending to a new thread for executing. + * + * @param modelClass + * Which table to query from by class. + * @param column + * The based on column to calculate. + * @return A AverageExecutor instance. + */ + public AverageExecutor averageAsync(final Class modelClass, final String column) { + return averageAsync(BaseUtility.changeCase(DBUtility.getTableNameByClassName(modelClass.getName())), column); + } + + /** + * Calculates the average value on a given column. + * + *
+	 * DataSupport.average("person", "age");
+	 * 
+ * + * You can also specify a where clause when calculating. + * + *
+	 * DataSupport.where("age > ?", "15").average("person", "age");
+	 * 
+ * + * @param tableName + * Which table to query from. + * @param column + * The based on column to calculate. + * @return The average value on a given column. + */ + public synchronized double average(String tableName, String column) { + QueryHandler queryHandler = new QueryHandler(Connector.getDatabase()); + return queryHandler.onAverage(tableName, column, mConditions); + } + + /** + * Basically same as {@link #average(String, String)} but pending to a new thread for executing. + * + * @param tableName + * Which table to query from. + * @param column + * The based on column to calculate. + * @return A AverageExecutor instance. + */ + public AverageExecutor averageAsync(final String tableName, final String column) { + final AverageExecutor executor = new AverageExecutor(); + Runnable runnable = new Runnable() { + @Override + public void run() { + synchronized (DataSupport.class) { + final double average = average(tableName, column); + if (executor.getListener() != null) { + LitePal.getHandler().post(new Runnable() { + @Override + public void run() { + executor.getListener().onFinish(average); + } + }); + } + } + } + }; + executor.submit(runnable); + return executor; + } + + /** + * Calculates the maximum value on a given column. The value is returned + * with the same data type of the column. + * + *
+	 * DataSupport.max(Person.class, "age", int.class);
+	 * 
+ * + * You can also specify a where clause when calculating. + * + *
+	 * DataSupport.where("age > ?", "15").max(Person.class, "age", Integer.TYPE);
+	 * 
+ * + * @param modelClass + * Which table to query from by class. + * @param columnName + * The based on column to calculate. + * @param columnType + * The type of the based on column. + * @return The maximum value on a given column. + */ + public synchronized T max(Class modelClass, String columnName, Class columnType) { + return max(BaseUtility.changeCase(modelClass.getSimpleName()), columnName, columnType); + } + + /** + * Basically same as {@link #max(Class, String, Class)} but pending to a new thread for executing. + * + * @param modelClass + * Which table to query from by class. + * @param columnName + * The based on column to calculate. + * @param columnType + * The type of the based on column. + * @return A FindExecutor instance. + */ + public FindExecutor maxAsync(final Class modelClass, final String columnName, final Class columnType) { + return maxAsync(BaseUtility.changeCase(DBUtility.getTableNameByClassName(modelClass.getName())), columnName, columnType); + } + + /** + * Calculates the maximum value on a given column. The value is returned + * with the same data type of the column. + * + *
+	 * DataSupport.max("person", "age", int.class);
+	 * 
+ * + * You can also specify a where clause when calculating. + * + *
+	 * DataSupport.where("age > ?", "15").max("person", "age", Integer.TYPE);
+	 * 
+ * + * @param tableName + * Which table to query from. + * @param columnName + * The based on column to calculate. + * @param columnType + * The type of the based on column. + * @return The maximum value on a given column. + */ + public synchronized T max(String tableName, String columnName, Class columnType) { + QueryHandler queryHandler = new QueryHandler(Connector.getDatabase()); + return queryHandler.onMax(tableName, columnName, mConditions, columnType); + } + + /** + * Basically same as {@link #max(String, String, Class)} but pending to a new thread for executing. + * + * @param tableName + * Which table to query from. + * @param columnName + * The based on column to calculate. + * @param columnType + * The type of the based on column. + * @return A FindExecutor instance. + */ + public FindExecutor maxAsync(final String tableName, final String columnName, final Class columnType) { + final FindExecutor executor = new FindExecutor(); + Runnable runnable = new Runnable() { + @Override + public void run() { + synchronized (DataSupport.class) { + final T t = max(tableName, columnName, columnType); + if (executor.getListener() != null) { + LitePal.getHandler().post(new Runnable() { + @Override + public void run() { + executor.getListener().onFinish(t); + } + }); + } + } + } + }; + executor.submit(runnable); + return executor; + } + + /** + * Calculates the minimum value on a given column. The value is returned + * with the same data type of the column. + * + *
+	 * DataSupport.min(Person.class, "age", int.class);
+	 * 
+ * + * You can also specify a where clause when calculating. + * + *
+	 * DataSupport.where("age > ?", "15").min(Person.class, "age", Integer.TYPE);
+	 * 
+ * + * @param modelClass + * Which table to query from by class. + * @param columnName + * The based on column to calculate. + * @param columnType + * The type of the based on column. + * @return The minimum value on a given column. + */ + public synchronized T min(Class modelClass, String columnName, Class columnType) { + return min(BaseUtility.changeCase(modelClass.getSimpleName()), columnName, columnType); + } + + /** + * Basically same as {@link #min(Class, String, Class)} but pending to a new thread for executing. + * + * @param modelClass + * Which table to query from by class. + * @param columnName + * The based on column to calculate. + * @param columnType + * The type of the based on column. + * @return A FindExecutor instance. + */ + public FindExecutor minAsync(final Class modelClass, final String columnName, final Class columnType) { + return minAsync(BaseUtility.changeCase(DBUtility.getTableNameByClassName(modelClass.getName())), columnName, columnType); + } + + /** + * Calculates the minimum value on a given column. The value is returned + * with the same data type of the column. + * + *
+	 * DataSupport.min("person", "age", int.class);
+	 * 
+ * + * You can also specify a where clause when calculating. + * + *
+	 * DataSupport.where("age > ?", "15").min("person", "age", Integer.TYPE);
+	 * 
+ * + * @param tableName + * Which table to query from. + * @param columnName + * The based on column to calculate. + * @param columnType + * The type of the based on column. + * @return The minimum value on a given column. + */ + public synchronized T min(String tableName, String columnName, Class columnType) { + QueryHandler queryHandler = new QueryHandler(Connector.getDatabase()); + return queryHandler.onMin(tableName, columnName, mConditions, columnType); + } + + /** + * Basically same as {@link #min(String, String, Class)} but pending to a new thread for executing. + * + * @param tableName + * Which table to query from. + * @param columnName + * The based on column to calculate. + * @param columnType + * The type of the based on column. + * @return A FindExecutor instance. + */ + public FindExecutor minAsync(final String tableName, final String columnName, final Class columnType) { + final FindExecutor executor = new FindExecutor(); + Runnable runnable = new Runnable() { + @Override + public void run() { + synchronized (DataSupport.class) { + final T t = min(tableName, columnName, columnType); + if (executor.getListener() != null) { + LitePal.getHandler().post(new Runnable() { + @Override + public void run() { + executor.getListener().onFinish(t); + } + }); + } + } + } + }; + executor.submit(runnable); + return executor; + } + + /** + * Calculates the sum of values on a given column. The value is returned + * with the same data type of the column. + * + *
+	 * DataSupport.sum(Person.class, "age", int.class);
+	 * 
+ * + * You can also specify a where clause when calculating. + * + *
+	 * DataSupport.where("age > ?", "15").sum(Person.class, "age", Integer.TYPE);
+	 * 
+ * + * @param modelClass + * Which table to query from by class. + * @param columnName + * The based on column to calculate. + * @param columnType + * The type of the based on column. + * @return The sum value on a given column. + */ + public synchronized T sum(Class modelClass, String columnName, Class columnType) { + return sum(BaseUtility.changeCase(modelClass.getSimpleName()), columnName, columnType); + } + + /** + * Basically same as {@link #sum(Class, String, Class)} but pending to a new thread for executing. + * + * @param modelClass + * Which table to query from by class. + * @param columnName + * The based on column to calculate. + * @param columnType + * The type of the based on column. + * @return A FindExecutor instance. + */ + public FindExecutor sumAsync(final Class modelClass, final String columnName, final Class columnType) { + return sumAsync(BaseUtility.changeCase(DBUtility.getTableNameByClassName(modelClass.getName())), columnName, columnType); + } + + /** + * Calculates the sum of values on a given column. The value is returned + * with the same data type of the column. + * + *
+	 * DataSupport.sum("person", "age", int.class);
+	 * 
+ * + * You can also specify a where clause when calculating. + * + *
+	 * DataSupport.where("age > ?", "15").sum("person", "age", Integer.TYPE);
+	 * 
+ * + * @param tableName + * Which table to query from. + * @param columnName + * The based on column to calculate. + * @param columnType + * The type of the based on column. + * @return The sum value on a given column. + */ + public synchronized T sum(String tableName, String columnName, Class columnType) { + QueryHandler queryHandler = new QueryHandler(Connector.getDatabase()); + return queryHandler.onSum(tableName, columnName, mConditions, columnType); + } + + /** + * Basically same as {@link #sum(String, String, Class)} but pending to a new thread for executing. + * + * @param tableName + * Which table to query from. + * @param columnName + * The based on column to calculate. + * @param columnType + * The type of the based on column. + * @return A FindExecutor instance. + */ + public FindExecutor sumAsync(final String tableName, final String columnName, final Class columnType) { + final FindExecutor executor = new FindExecutor(); + Runnable runnable = new Runnable() { + @Override + public void run() { + synchronized (DataSupport.class) { + final T t = sum(tableName, columnName, columnType); + if (executor.getListener() != null) { + LitePal.getHandler().post(new Runnable() { + @Override + public void run() { + executor.getListener().onFinish(t); + } + }); + } + } + } + }; + executor.submit(runnable); + return executor; + } + +} \ No newline at end of file diff --git a/YFDXJ/litepal/src/main/java/org/litepal/crud/DataHandler.java b/YFDXJ/litepal/src/main/java/org/litepal/crud/DataHandler.java new file mode 100644 index 0000000..5f19816 --- /dev/null +++ b/YFDXJ/litepal/src/main/java/org/litepal/crud/DataHandler.java @@ -0,0 +1,1370 @@ +/* + * Copyright (C) Tony Green, LitePal Framework Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.litepal.crud; + +import android.content.ContentValues; +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; +import android.util.SparseArray; + +import org.litepal.LitePalBase; +import org.litepal.crud.model.AssociationsInfo; +import org.litepal.exceptions.DataSupportException; +import org.litepal.exceptions.DatabaseGenerateException; +import org.litepal.tablemanager.model.GenericModel; +import org.litepal.util.BaseUtility; +import org.litepal.util.Const; +import org.litepal.util.DBUtility; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Date; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; + +import static org.litepal.util.BaseUtility.changeCase; + +/** + * This is the base class for CRUD component. All the common actions which can + * be shared with each function in CURD component will be put here. + * + * @author Tony Green + * @since 1.1 + */ +abstract class DataHandler extends LitePalBase { + public static final String TAG = "DataHandler"; + + /** + * Instance of SQLiteDatabase, use to do the CRUD job. + */ + SQLiteDatabase mDatabase; + + /** + * Store empty model instance. In case to create each time when checking + * field is with default value or not. + */ + private DataSupport tempEmptyModel; + + /** + * Holds the AssociationsInfo which foreign keys in the current model. + */ + private List fkInCurrentModel; + + /** + * Holds the AssociationsInfo which foreign keys in other models. + */ + private List fkInOtherModel; + + /** + * Query the table of the given model, returning a model list over the + * result set. + * + * @param modelClass + * The model to compile the query against. + * @param columns + * A list of which columns to return. Passing null will return + * all columns, which is discouraged to prevent reading data from + * storage that isn't going to be used. + * @param selection + * A filter declaring which rows to return, formatted as an SQL + * WHERE clause (excluding the WHERE itself). Passing null will + * return all rows for the given table. + * @param selectionArgs + * You may include ?s in selection, which will be replaced by the + * values from selectionArgs, in order that they appear in the + * selection. The values will be bound as Strings. + * @param groupBy + * A filter declaring how to group rows, formatted as an SQL + * GROUP BY clause (excluding the GROUP BY itself). Passing null + * will cause the rows to not be grouped. + * @param having + * A filter declare which row groups to include in the cursor, if + * row grouping is being used, formatted as an SQL HAVING clause + * (excluding the HAVING itself). Passing null will cause all row + * groups to be included, and is required when row grouping is + * not being used. + * @param orderBy + * How to order the rows, formatted as an SQL ORDER BY clause + * (excluding the ORDER BY itself). Passing null will use the + * default sort order, which may be unordered. + * @param limit + * Limits the number of rows returned by the query, formatted as + * LIMIT clause. Passing null denotes no LIMIT clause. + * @param foreignKeyAssociations + * Associated classes which have foreign keys in the current + * model's table. + * @return A model list. The list may be empty. + */ + @SuppressWarnings("unchecked") + protected List query(Class modelClass, String[] columns, String selection, + String[] selectionArgs, String groupBy, String having, String orderBy, String limit, + List foreignKeyAssociations) { + List dataList = new ArrayList(); + Cursor cursor = null; + try { + List supportedFields = getSupportedFields(modelClass.getName()); + List supportedGenericFields = getSupportedGenericFields(modelClass.getName()); + String[] customizedColumns = DBUtility.convertSelectClauseToValidNames(getCustomizedColumns(columns, supportedGenericFields, foreignKeyAssociations)); + String tableName = getTableName(modelClass); + cursor = mDatabase.query(tableName, customizedColumns, selection, selectionArgs, + groupBy, having, orderBy, limit); + if (cursor.moveToFirst()) { + SparseArray queryInfoCacheSparseArray = new SparseArray(); + Map genericModelMap = new HashMap(); + do { + T modelInstance = (T) createInstanceFromClass(modelClass); + giveBaseObjIdValue((DataSupport) modelInstance, + cursor.getLong(cursor.getColumnIndexOrThrow("id"))); + setValueToModel(modelInstance, supportedFields, foreignKeyAssociations, cursor, queryInfoCacheSparseArray); + setGenericValueToModel((DataSupport) modelInstance, supportedGenericFields, genericModelMap); + if (foreignKeyAssociations != null) { + setAssociatedModel((DataSupport) modelInstance); + } + dataList.add(modelInstance); + } while (cursor.moveToNext()); + queryInfoCacheSparseArray.clear(); + genericModelMap.clear(); + } + return dataList; + } catch (Exception e) { + throw new DataSupportException(e.getMessage(), e); + } finally { + if (cursor != null) { + cursor.close(); + } + } + } + + /** + * Handles the math query of the given table. + * + * @param tableName + * Which table to query from. + * @param columns + * A list of which columns to return. Passing null will return + * all columns, which is discouraged to prevent reading data from + * storage that isn't going to be used. + * @param conditions + * A filter declaring which rows to return, formatted as an SQL + * WHERE clause. Passing null will return all rows. + * @param type + * The type of the based on column. + * @return The result calculating by SQL. + */ + @SuppressWarnings("unchecked") + protected T mathQuery(String tableName, String[] columns, String[] conditions, Class type) { + BaseUtility.checkConditionsCorrect(conditions); + Cursor cursor = null; + T result = null; + try { + cursor = mDatabase.query(tableName, columns, getWhereClause(conditions), + getWhereArgs(conditions), null, null, null); + if (cursor.moveToFirst()) { + Class cursorClass = cursor.getClass(); + Method method = cursorClass.getMethod(genGetColumnMethod(type), int.class); + result = (T) method.invoke(cursor, 0); + } + } catch (Exception e) { + throw new DataSupportException(e.getMessage(), e); + } finally { + if (cursor != null) { + cursor.close(); + } + } + return result; + } + + /** + * Assign the generated id value to {@link DataSupport#baseObjId}. This + * value will be used as identify of this model for system use. + * + * @param baseObj + * The class of base object. + * @param id + * The value of id. + */ + protected void giveBaseObjIdValue(DataSupport baseObj, long id) throws SecurityException, + NoSuchFieldException, IllegalArgumentException, IllegalAccessException { + if (id > 0) { + DynamicExecutor.set(baseObj, "baseObjId", id, DataSupport.class); + } + } + + /** + * Iterate all the fields passed in. Each field calls + * {@link #putFieldsValueDependsOnSaveOrUpdate(DataSupport, java.lang.reflect.Field, android.content.ContentValues)} + * if it's not id field. + * + * @param baseObj + * Current model to persist or update. + * @param supportedFields + * List of all supported fields. + * @param values + * To store data of current model for persisting or updating. + * @throws java.lang.reflect.InvocationTargetException + * @throws IllegalAccessException + * @throws NoSuchMethodException + * @throws IllegalArgumentException + * @throws SecurityException + */ + protected void putFieldsValue(DataSupport baseObj, List supportedFields, + ContentValues values) throws SecurityException, IllegalArgumentException, NoSuchMethodException, IllegalAccessException, InvocationTargetException { + for (Field field : supportedFields) { + if (!isIdColumn(field.getName())) { + putFieldsValueDependsOnSaveOrUpdate(baseObj, field, values); + } + } + } + + /** + * This method deals with the putting values job into ContentValues. The + * ContentValues has put method to set data. But we do not know we + * should use which put method cause the field type isn't clear. So + * the reflection API is necessary here to put values into ContentValues + * with dynamically getting field type to put value. + * + * @param baseObj + * The class of base object. + * @param field + * Field to put into ContentValues. + * @param values + * To store data of current model for persisting or updating. + * @throws SecurityException + * @throws NoSuchMethodException + * @throws IllegalArgumentException + * @throws IllegalAccessException + * @throws java.lang.reflect.InvocationTargetException + */ + protected void putContentValuesForSave(DataSupport baseObj, Field field, ContentValues values) + throws SecurityException, IllegalArgumentException, NoSuchMethodException, + IllegalAccessException, InvocationTargetException { + Object fieldValue = DynamicExecutor.getField(baseObj, field.getName(), baseObj.getClass()); + if (fieldValue != null) { + // put content value only when value is not null. this allows to use defaultValue declared in annotation. + if ("java.util.Date".equals(field.getType().getName())) { + Date date = (Date) fieldValue; + fieldValue = date.getTime(); + } + Object[] parameters = new Object[] { changeCase(DBUtility.convertToValidColumnName(field.getName())), fieldValue }; + Class[] parameterTypes = getParameterTypes(field, fieldValue, parameters); + DynamicExecutor.send(values, "put", parameters, values.getClass(), parameterTypes); + } + } + + /** + * This method deals with the putting values job into ContentValues. The + * ContentValues has put method to set data. But we do not know we + * should use which put method cause the field type isn't clear. So + * the reflection API is necessary here to put values into ContentValues + * with dynamically getting field type to put value. + * + * @param baseObj + * The class of base object. + * @param field + * Field to put into ContentValues. + * @param values + * To store data of current model for persisting or updating. + * @throws SecurityException + * @throws NoSuchMethodException + * @throws IllegalArgumentException + * @throws IllegalAccessException + * @throws java.lang.reflect.InvocationTargetException + */ + protected void putContentValuesForUpdate(DataSupport baseObj, Field field, ContentValues values) + throws SecurityException, IllegalArgumentException, NoSuchMethodException, + IllegalAccessException, InvocationTargetException { + Object fieldValue = getFieldValue(baseObj, field); + if ("java.util.Date".equals(field.getType().getName()) && fieldValue != null) { + Date date = (Date) fieldValue; + fieldValue = date.getTime(); + } + Object[] parameters = new Object[] { changeCase(DBUtility.convertToValidColumnName(field.getName())), fieldValue }; + Class[] parameterTypes = getParameterTypes(field, fieldValue, parameters); + DynamicExecutor.send(values, "put", parameters, values.getClass(), parameterTypes); + } + + /** + * Get the field value for model. + * + * @param dataSupport + * The model to get method from. + * @param field + * Use to generate getter method name. + * @return The value returned by getter method. + * @throws SecurityException + * @throws NoSuchMethodException + * @throws IllegalArgumentException + * @throws IllegalAccessException + * @throws java.lang.reflect.InvocationTargetException + */ + protected Object getFieldValue(DataSupport dataSupport, Field field) + throws SecurityException, NoSuchMethodException, IllegalArgumentException, + IllegalAccessException, InvocationTargetException { + if (shouldGetOrSet(dataSupport, field)) { + return DynamicExecutor.getField(dataSupport, field.getName(), dataSupport.getClass()); + } + return null; + } + + /** + * Set the field value for model. + * + * @param dataSupport + * The model to set method to. + * @param field + * Use to generate setter method name. + * @param parameter + * The parameter to invoke setter method. + * @throws SecurityException + * @throws NoSuchMethodException + * @throws IllegalArgumentException + * @throws IllegalAccessException + * @throws java.lang.reflect.InvocationTargetException + */ + protected void setFieldValue(DataSupport dataSupport, Field field, Object parameter) + throws SecurityException, NoSuchMethodException, IllegalArgumentException, + IllegalAccessException, InvocationTargetException { + if (shouldGetOrSet(dataSupport, field)) { + DynamicExecutor.setField(dataSupport, field.getName(), parameter, dataSupport.getClass()); + } + } + + /** + * Find all the associated models of currently model. Then add all the + * associated models into baseObj. + * + * @param baseObj + * The class of base object. + */ + protected void analyzeAssociatedModels(DataSupport baseObj, Collection associationInfos) { + try { + for (AssociationsInfo associationInfo : associationInfos) { + if (associationInfo.getAssociationType() == Const.Model.MANY_TO_ONE) { + new Many2OneAnalyzer().analyze(baseObj, associationInfo); + } else if (associationInfo.getAssociationType() == Const.Model.ONE_TO_ONE) { + new One2OneAnalyzer().analyze(baseObj, associationInfo); + } else if (associationInfo.getAssociationType() == Const.Model.MANY_TO_MANY) { + new Many2ManyAnalyzer().analyze(baseObj, associationInfo); + } + } + } catch (Exception e) { + throw new DataSupportException(e.getMessage(), e); + } + } + + /** + * Get the associated model. + * + * @param baseObj + * The instance of self model. + * @param associationInfo + * To get the associated model. + * @return The associated model of self model by analyzing associationInfo. + * + * @throws SecurityException + * @throws IllegalArgumentException + * @throws NoSuchMethodException + * @throws IllegalAccessException + * @throws java.lang.reflect.InvocationTargetException + */ + protected DataSupport getAssociatedModel(DataSupport baseObj, AssociationsInfo associationInfo) + throws SecurityException, IllegalArgumentException, NoSuchMethodException, + IllegalAccessException, InvocationTargetException { + return (DataSupport) getFieldValue(baseObj, + associationInfo.getAssociateOtherModelFromSelf()); + } + + /** + * Get the associated models collection. When it comes to many2one or + * many2many association. A model may have lots of associated models. + * + * @param baseObj + * The instance of self model. + * @param associationInfo + * To get the associated models collection. + * @return The associated models collection of self model by analyzing + * associationInfo. + * @throws SecurityException + * @throws IllegalArgumentException + * @throws NoSuchMethodException + * @throws IllegalAccessException + * @throws java.lang.reflect.InvocationTargetException + */ + @SuppressWarnings("unchecked") + protected Collection getAssociatedModels(DataSupport baseObj, + AssociationsInfo associationInfo) throws SecurityException, IllegalArgumentException, + NoSuchMethodException, IllegalAccessException, InvocationTargetException { + return (Collection) getFieldValue(baseObj, + associationInfo.getAssociateOtherModelFromSelf()); + } + + /** + * Create an empty instance of baseObj if it hasn't created one yet. If + * there's already an empty model existed in {@link #tempEmptyModel}, no + * need to create a new one. + * + * @param baseObj + * Current model to update. + * @return An empty instance of baseObj. + */ + protected DataSupport getEmptyModel(DataSupport baseObj) { + if (tempEmptyModel != null) { + return tempEmptyModel; + } + String className = null; + try { + className = baseObj.getClassName(); + Class modelClass = Class.forName(className); + tempEmptyModel = (DataSupport) modelClass.newInstance(); + return tempEmptyModel; + } catch (ClassNotFoundException e) { + throw new DatabaseGenerateException(DatabaseGenerateException.CLASS_NOT_FOUND + + className); + } catch (InstantiationException e) { + throw new DataSupportException(className + DataSupportException.INSTANTIATION_EXCEPTION, e); + } catch (Exception e) { + throw new DataSupportException(e.getMessage(), e); + } + } + + /** + * Get the WHERE clause to apply when updating or deleting multiple rows. + * + * @param conditions + * A string array representing the WHERE part of an SQL + * statement. + * @return The WHERE clause to apply when updating or deleting multiple + * rows. + */ + protected String getWhereClause(String... conditions) { + if (isAffectAllLines((Object) conditions)) { + return null; + } + if (conditions != null && conditions.length > 0) { + return conditions[0]; + } + return null; + } + + /** + * Get the WHERE arguments to fill into where clause when updating or + * deleting multiple rows. + * + * @param conditions + * A string array representing the WHERE part of an SQL + * statement. + * @return The WHERE arguments to fill into where clause when updating or + * deleting multiple rows. + */ + protected String[] getWhereArgs(String... conditions) { + if (isAffectAllLines((Object) conditions)) { + return null; + } + if (conditions != null && conditions.length > 1) { + String[] whereArgs = new String[conditions.length - 1]; + System.arraycopy(conditions, 1, whereArgs, 0, conditions.length - 1); + return whereArgs; + } + return null; + } + + /** + * Check the passing conditions represent to affect all lines or not.
+ * Do not pass anything to the conditions parameter means affect all lines. + * + * @param conditions + * An array representing the WHERE part of an SQL statement. + * @return Affect all lines or not. + */ + protected boolean isAffectAllLines(Object... conditions) { + if (conditions != null && conditions.length == 0) { + return true; + } + return false; + } + + /** + * Get the where clause by the passed in id collection to apply multiple + * rows. + * + * @param ids + * The id collection. + * @return The where clause to execute. + */ + protected String getWhereOfIdsWithOr(Collection ids) { + StringBuilder whereClause = new StringBuilder(); + boolean needOr = false; + for (long id : ids) { + if (needOr) { + whereClause.append(" or "); + } + needOr = true; + whereClause.append("id = "); + whereClause.append(id); + } + return changeCase(whereClause.toString()); + } + + /** + * Get the where clause by the passed in id array to apply multiple rows. + * + * @param ids + * The id collection. + * @return The where clause to execute. + */ + protected String getWhereOfIdsWithOr(long... ids) { + StringBuilder whereClause = new StringBuilder(); + boolean needOr = false; + for (long id : ids) { + if (needOr) { + whereClause.append(" or "); + } + needOr = true; + whereClause.append("id = "); + whereClause.append(id); + } + return changeCase(whereClause.toString()); + } + + /** + * When executing {@link #getFieldValue(DataSupport, Field)} or + * {@link #setFieldValue(DataSupport, Field, Object)}, the + * dataSupport and field passed in should be protected from null value. + * + * @param dataSupport + * The object to execute set or get method. + * @param field + * The field of generating set and get methods. + * @return True if dataSupport and field are not null, false otherwise. + */ + protected boolean shouldGetOrSet(DataSupport dataSupport, Field field) { + return dataSupport != null && field != null; + } + + /** + * Get the name of intermediate join table. + * + * @param baseObj + * Current model. + * @param associatedTableName + * The name of associated table. + * @return The name of intermediate join table. + */ + protected String getIntermediateTableName(DataSupport baseObj, String associatedTableName) { + return changeCase(DBUtility.getIntermediateTableName(baseObj.getTableName(), + associatedTableName)); + } + + /** + * Get the simple name of modelClass. Then change the case by the setting + * rule in litepal.xml as table name. + * + * @param modelClass + * Class of model to get table name from. + * @return The table name of model. + */ + protected String getTableName(Class modelClass) { + return BaseUtility.changeCase(DBUtility.getTableNameByClassName(modelClass.getName())); + } + + /** + * Creates an instance from the passed in class. It will always create an + * instance no matter how the constructor defines in the class file. A best + * suit constructor will be find by calling + * {@link #findBestSuitConstructor(Class)} method. + * + * @param modelClass + * The class to create instance. + * @return An instance by the passed in class. + */ + protected Object createInstanceFromClass(Class modelClass) { + try { + Constructor constructor = findBestSuitConstructor(modelClass); + return constructor.newInstance(getConstructorParams(modelClass, constructor)); + } catch (Exception e) { + throw new DataSupportException(e.getMessage(), e); + } + } + + /** + * Finds the best suit constructor for creating an instance of a class. The + * principle is that the constructor with least parameters and has no self + * type parameter will be the best suit one to create instance. + * + * @param modelClass + * To get constructors from. + * @return The best suit constructor. + */ + protected Constructor findBestSuitConstructor(Class modelClass) { + Constructor[] constructors = modelClass.getDeclaredConstructors(); + SparseArray> map = new SparseArray>(); + int minKey = Integer.MAX_VALUE; + for (Constructor constructor : constructors) { + int key = constructor.getParameterTypes().length; + Class[] types = constructor.getParameterTypes(); + for (Class parameterType : types) { + if (parameterType == modelClass) { + key = key + 10000; // plus the key for not using this constructor + } else if (parameterType.getName().equals("com.android.tools.fd.runtime.InstantReloadException")) { + key = key + 10000; // plus the key for not using this constructor + } + } + if (map.get(key) == null) { + map.put(key, constructor); + } + if (key < minKey) { + minKey = key; + } + } + Constructor bestSuitConstructor = map.get(minKey); + if (bestSuitConstructor != null) { + bestSuitConstructor.setAccessible(true); + } + return bestSuitConstructor; + } + + /** + * Depends on the passed in constructor, creating a parameters array with + * initialized values for the constructor. + * + * @param modelClass + * The original class the this constructor belongs to. + * @param constructor + * The constructor to get parameters for it. + * + * @return A parameters array with initialized values. + */ + protected Object[] getConstructorParams(Class modelClass, Constructor constructor) { + Class[] paramTypes = constructor.getParameterTypes(); + Object[] params = new Object[paramTypes.length]; + for (int i = 0; i < paramTypes.length; i++) { + params[i] = getInitParamValue(modelClass, paramTypes[i]); + } + return params; + } + + /** + * Get value from database by cursor, then set the value into modelInstance. + * + * @param modelInstance + * The model to set into. + * @param supportedFields + * Corresponding to each column in database. + * @param foreignKeyAssociations + * Associated classes which have foreign keys in the current + * model's table. + * @param cursor + * Use to get value from database. + * @param sparseArray + * Use SparseArray to cache the query information at first loop. Then the rest loop + * can get query information directly to speed up. + * @throws SecurityException + * @throws IllegalArgumentException + * @throws NoSuchMethodException + * @throws IllegalAccessException + * @throws java.lang.reflect.InvocationTargetException + */ + protected void setValueToModel(Object modelInstance, List supportedFields, + List foreignKeyAssociations, Cursor cursor, SparseArray sparseArray) throws SecurityException, + IllegalArgumentException, NoSuchMethodException, IllegalAccessException, + InvocationTargetException { + int cacheSize = sparseArray.size(); + if (cacheSize > 0) { + for (int i = 0; i < cacheSize; i++) { + int columnIndex = sparseArray.keyAt(i); + QueryInfoCache cache = sparseArray.get(columnIndex); + setToModelByReflection(modelInstance, cache.field, columnIndex, cache.getMethodName, cursor); + } + } else { + for (Field field : supportedFields) { + String getMethodName = genGetColumnMethod(field); + String columnName = isIdColumn(field.getName()) ? "id" : DBUtility.convertToValidColumnName(field.getName()); + int columnIndex = cursor.getColumnIndex(BaseUtility.changeCase(columnName)); + if (columnIndex != -1) { + setToModelByReflection(modelInstance, field, columnIndex, getMethodName, cursor); + QueryInfoCache cache = new QueryInfoCache(); + cache.getMethodName = getMethodName; + cache.field = field; + sparseArray.put(columnIndex, cache); + } + } + } + + if (foreignKeyAssociations != null) { + for (AssociationsInfo associationInfo : foreignKeyAssociations) { + String foreignKeyColumn = getForeignKeyColumnName(DBUtility + .getTableNameByClassName(associationInfo.getAssociatedClassName())); + int columnIndex = cursor.getColumnIndex(foreignKeyColumn); + if (columnIndex != -1) { + long associatedClassId = cursor.getLong(columnIndex); + try { + DataSupport associatedObj = (DataSupport) DataSupport.find( + Class.forName(associationInfo.getAssociatedClassName()), + associatedClassId); + if (associatedObj != null) { + setFieldValue((DataSupport) modelInstance, + associationInfo.getAssociateOtherModelFromSelf(), associatedObj); + } + } catch (ClassNotFoundException e) { + e.printStackTrace(); + } + } + } + } + } + + /** + * Get generic value from generic tables, then set the value into the baseObj. + * @param baseObj + * The model to set into. + * @param supportedGenericFields + * List of all supported generic fields. + * @param genericModelMap + * Use HashMap to cache the query information at first loop. Then the rest loop can + * get query information directly to speed up. + * @throws NoSuchMethodException + * @throws IllegalAccessException + * @throws InvocationTargetException + */ + protected void setGenericValueToModel(DataSupport baseObj, List supportedGenericFields, + Map genericModelMap) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException { + for (Field field : supportedGenericFields) { + String tableName, genericValueColumnName, genericValueIdColumnName, getMethodName; + Cursor cursor = null; + GenericModel genericModel = genericModelMap.get(field); + if (genericModel == null) { + tableName = DBUtility.getGenericTableName(baseObj.getClassName(), field.getName()); + genericValueColumnName = DBUtility.convertToValidColumnName(field.getName()); + genericValueIdColumnName = DBUtility.getGenericValueIdColumnName(baseObj.getClassName()); + getMethodName = genGetColumnMethod(field); + GenericModel model = new GenericModel(); + model.setTableName(tableName); + model.setValueColumnName(genericValueColumnName); + model.setValueIdColumnName(genericValueIdColumnName); + model.setGetMethodName(getMethodName); + genericModelMap.put(field, model); + } else { + tableName = genericModel.getTableName(); + genericValueColumnName = genericModel.getValueColumnName(); + genericValueIdColumnName = genericModel.getValueIdColumnName(); + getMethodName = genericModel.getGetMethodName(); + } + try { + cursor = mDatabase.query(tableName, null, genericValueIdColumnName + " = ?", + new String[]{ String.valueOf(baseObj.getBaseObjId()) }, null, null, null); + if (cursor.moveToFirst()) { + do { + int columnIndex = cursor.getColumnIndex(BaseUtility.changeCase(genericValueColumnName)); + if (columnIndex != -1) { + setToModelByReflection(baseObj, field, columnIndex, getMethodName, cursor); + } + } while (cursor.moveToNext()); + } + } finally { + if (cursor != null) { + cursor.close(); + } + } + } + } + + /** + * Get the foreign key associations of the specified class. + * + * @param className + * The full class name. + * @param isEager + * True to load the associated models, false not. + * @return The foreign key associations of the specified class + */ + protected List getForeignKeyAssociations(String className, boolean isEager) { + if (isEager) { + analyzeAssociations(className); + return fkInCurrentModel; + } + return null; + } + + /** + * Get the types of parameters for {@link android.content.ContentValues#put}. Need two + * parameters. First is String type for key. Second is depend on field for + * value. + * + * @param field + * The field to get parameter type. + * @param fieldValue + * Value of the field. Only used to convert to String when the + * field is char. + * @param parameters + * If the field is char, convert the value to String at index 1. + * @return The types of parameters for {@link android.content.ContentValues#put}. + */ + protected Class[] getParameterTypes(Field field, Object fieldValue, Object[] parameters) { + Class[] parameterTypes; + if (isCharType(field)) { + parameters[1] = String.valueOf(fieldValue); + parameterTypes = new Class[] { String.class, String.class }; + } else { + if (field.getType().isPrimitive()) { + parameterTypes = new Class[] { String.class, getObjectType(field.getType()) }; + } else if ("java.util.Date".equals(field.getType().getName())) { + parameterTypes = new Class[] { String.class, Long.class }; + } else { + parameterTypes = new Class[] { String.class, field.getType() }; + } + } + return parameterTypes; + } + + /** + * Each primitive type has a corresponding object type. For example int and + * Integer, boolean and Boolean. This method gives a way to turn primitive + * type into object type. + * + * @param primitiveType + * The class of primitive type. + * @return If the passed in parameter is primitive type, return a + * corresponding object type. Otherwise return null. + */ + private Class getObjectType(Class primitiveType) { + if (primitiveType != null) { + if (primitiveType.isPrimitive()) { + String basicTypeName = primitiveType.getName(); + if ("int".equals(basicTypeName)) { + return Integer.class; + } else if ("short".equals(basicTypeName)) { + return Short.class; + } else if ("long".equals(basicTypeName)) { + return Long.class; + } else if ("float".equals(basicTypeName)) { + return Float.class; + } else if ("double".equals(basicTypeName)) { + return Double.class; + } else if ("boolean".equals(basicTypeName)) { + return Boolean.class; + } else if ("char".equals(basicTypeName)) { + return Character.class; + } + } + } + return null; + } + + /** + * Gives the passed in parameter an initialized value. If the parameter is + * basic data type or the corresponding object data type, return the default + * data. Or return null. + * + * @param modelClass + * The original class the this constructor belongs to. + * @param paramType + * Parameter to get initialized value. + * @return Default data of basic data type or null. + */ + private Object getInitParamValue(Class modelClass, Class paramType) { + String paramTypeName = paramType.getName(); + if ("boolean".equals(paramTypeName) || "java.lang.Boolean".equals(paramTypeName)) { + return false; + } + if ("float".equals(paramTypeName) || "java.lang.Float".equals(paramTypeName)) { + return 0f; + } + if ("double".equals(paramTypeName) || "java.lang.Double".equals(paramTypeName)) { + return 0.0; + } + if ("int".equals(paramTypeName) || "java.lang.Integer".equals(paramTypeName)) { + return 0; + } + if ("long".equals(paramTypeName) || "java.lang.Long".equals(paramTypeName)) { + return 0L; + } + if ("short".equals(paramTypeName) || "java.lang.Short".equals(paramTypeName)) { + return 0; + } + if ("char".equals(paramTypeName) || "java.lang.Character".equals(paramTypeName)) { + return ' '; + } + if ("[B".equals(paramTypeName) || "[Ljava.lang.Byte;".equals(paramTypeName)) { + return new byte[0]; + } + if ("java.lang.String".equals(paramTypeName)) { + return ""; + } + if (modelClass == paramType) { + return null; + } + return createInstanceFromClass(paramType); + } + + /** + * Judge if the field is char or Character type. + * + * @param field + * Field to judge type. + * @return Return true if it's char or Character. Otherwise return false. + */ + private boolean isCharType(Field field) { + String type = field.getType().getName(); + return type.equals("char") || type.endsWith("Character"); + } + + /** + * Judge a field is a primitive boolean type or not. Cause it's a little + * special when use IDE to generate getter and setter method. The primitive + * boolean type won't be like getXxx, it's something like + * isXxx. + * + * @param field + * Use field to get field type. + * @return If it's primitive boolean type return true, else return false. + */ + private boolean isPrimitiveBooleanType(Field field) { + Class fieldType = field.getType(); + if ("boolean".equals(fieldType.getName())) { + return true; + } + return false; + } + + /** + * Put the value of field into ContentValues if current action is saving. + * Check the value of field is default value or not if current action is + * updating. If it's not default value, put it into ContentValues. Otherwise + * ignore it. + * + * @param baseObj + * Current model to persist or update. + * @param field + * With value to put into ContentValues. + * @param values + * To store data of current model for persisting or updating. + * @throws SecurityException + * @throws IllegalArgumentException + * @throws NoSuchMethodException + * @throws IllegalAccessException + * @throws java.lang.reflect.InvocationTargetException + */ + private void putFieldsValueDependsOnSaveOrUpdate(DataSupport baseObj, Field field, + ContentValues values) throws SecurityException, IllegalArgumentException, + NoSuchMethodException, IllegalAccessException, InvocationTargetException { + if (isUpdating()) { + if (!isFieldWithDefaultValue(baseObj, field)) { + putContentValuesForUpdate(baseObj, field, values); + } + } else if (isSaving()) { + putContentValuesForSave(baseObj, field, values); + } + } + + /** + * Current action is updating or not. Note that update the record by saving + * the already saved record again belongs to save action. + * + * @return If current action is updating return true. Otherwise return + * false. + */ + private boolean isUpdating() { + return UpdateHandler.class.getName().equals(getClass().getName()); + } + + /** + * Current action is saving or not. Note that update the record by saving + * the already saved record again belongs to save action. + * + * @return If current action is saving return true. Otherwise return false. + */ + private boolean isSaving() { + return SaveHandler.class.getName().equals(getClass().getName()); + } + + /** + * Analyze the passed in field. Check if this field is with default value. + * The baseObj need a default constructor or {@link DataSupportException} + * will be thrown. + * + * @param baseObj + * Current model to update. + * @param field + * To check if with default value. + * @return If the field is with default value, return true. Otherwise return + * false. + * @throws IllegalAccessException + * @throws SecurityException + * @throws IllegalArgumentException + * @throws NoSuchMethodException + * @throws java.lang.reflect.InvocationTargetException + * @throws DatabaseGenerateException + * @throws DataSupportException + */ + private boolean isFieldWithDefaultValue(DataSupport baseObj, Field field) + throws IllegalAccessException, SecurityException, IllegalArgumentException, + NoSuchMethodException, InvocationTargetException { + DataSupport emptyModel = getEmptyModel(baseObj); + Object realReturn = getFieldValue(baseObj, field); + Object defaultReturn = getFieldValue(emptyModel, field); + if (realReturn != null && defaultReturn != null) { + String realFieldValue = realReturn.toString(); + String defaultFieldValue = defaultReturn.toString(); + return realFieldValue.equals(defaultFieldValue); + } + return realReturn == defaultReturn; + } + + /** + * Generate the getter method name by field, following the Android Studio rule. + * + * @param field + * The field to generate getter method from. + * @return The generated getter method name. + */ + private String makeGetterMethodName(Field field) { + String getterMethodPrefix; + String fieldName = field.getName(); + if (isPrimitiveBooleanType(field)) { + if (fieldName.matches("^is[A-Z]{1}.*$")) { + fieldName = fieldName.substring(2); + } + getterMethodPrefix = "is"; + } else { + getterMethodPrefix = "get"; + } + if (fieldName.matches("^[a-z]{1}[A-Z]{1}.*")) { + return getterMethodPrefix + fieldName; + } else { + return getterMethodPrefix + BaseUtility.capitalize(fieldName); + } + } + + /** + * Generate the setter method name by field, following the Android Studio rule. + * + * @param field + * The field to generate setter method from. + * @return The generated setter method name. + */ + private String makeSetterMethodName(Field field) { + String setterMethodName; + String setterMethodPrefix = "set"; + if (isPrimitiveBooleanType(field) && field.getName().matches("^is[A-Z]{1}.*$")) { + setterMethodName = setterMethodPrefix + field.getName().substring(2); + } else if (field.getName().matches("^[a-z]{1}[A-Z]{1}.*")) { + setterMethodName = setterMethodPrefix + field.getName(); + } else { + setterMethodName = setterMethodPrefix + BaseUtility.capitalize(field.getName()); + } + return setterMethodName; + } + + /** + * Generates the getType method for cursor based on field. There're couple of + * unusual conditions. If field type is boolean, generate getInt method. If + * field type is char, generate getString method. If field type is Date, generate + * getLong method. If filed type is Integer, generate getInt method. If field type + * is bytes, generate getBlob method. + * + * @param field + * To generate getType method for cursor. + * @return The getType method for cursor. + */ + private String genGetColumnMethod(Field field) { + Class fieldType; + if (isCollection(field.getType())) { + fieldType = getGenericTypeClass(field); + } else { + fieldType = field.getType(); + } + return genGetColumnMethod(fieldType); + } + + /** + * Generates the getType method for cursor based on field. There're couple of + * unusual conditions. If field type is boolean, generate getInt method. If + * field type is char, generate getString method. If field type is Date, generate + * getLong method. If filed type is Integer, generate getInt method. If field type + * is bytes, generate getBlob method. + * + * @param fieldType + * To generate getType method for cursor. + * @return The getType method for cursor. + */ + private String genGetColumnMethod(Class fieldType) { + String typeName; + if (fieldType.isPrimitive()) { + typeName = BaseUtility.capitalize(fieldType.getName()); + } else { + typeName = fieldType.getSimpleName(); + } + String methodName = "get" + typeName; + if ("getBoolean".equals(methodName)) { + methodName = "getInt"; + } else if ("getChar".equals(methodName) || "getCharacter".equals(methodName)) { + methodName = "getString"; + } else if ("getDate".equals(methodName)) { + methodName = "getLong"; + } else if ("getInteger".equals(methodName)) { + methodName = "getInt"; + } else if ("getbyte[]".equalsIgnoreCase(methodName)) { + methodName = "getBlob"; + } + return methodName; + } + + /** + * Customize the passed in columns. If the columns contains an id column + * already, just return it. If contains an _id column, rename it to id. If + * not, an add id column then return. If it contains generic columns them + * from query and use them in supported generic fields. + * + * @param columns + * The original columns that passed in. + * @param foreignKeyAssociations + * Associated classes which have foreign keys in the current + * model's table. + * @return Customized columns with id column always. + */ + private String[] getCustomizedColumns(String[] columns, List supportedGenericFields, List foreignKeyAssociations) { + if (columns != null && columns.length > 0) { + boolean columnsContainsId = false; + List convertList = Arrays.asList(columns); + List columnList = new ArrayList(convertList); + List supportedGenericFieldNames = new ArrayList(); + List columnToRemove = new ArrayList(); + List genericColumnsForQuery = new ArrayList(); + List tempSupportedGenericFields = new ArrayList(); + + for (Field supportedGenericField : supportedGenericFields) { + supportedGenericFieldNames.add(supportedGenericField.getName()); + } + + for (int i = 0; i < columnList.size(); i++) { + String columnName = columnList.get(i); + // find out all generic columns. + if (BaseUtility.containsIgnoreCases(supportedGenericFieldNames, columnName)) { + columnToRemove.add(i); + } else if (isIdColumn(columnName)) { + columnsContainsId = true; + if ("_id".equalsIgnoreCase(columnName)) { + columnList.set(i, BaseUtility.changeCase("id")); + } + } + } + + // remove generic columns cause they can't be used for query + for (int i = columnToRemove.size() - 1; i >= 0 ; i--) { + int index = columnToRemove.get(i); + String genericColumn = columnList.remove(index); + genericColumnsForQuery.add(genericColumn); + } + + for (Field supportedGenericField : supportedGenericFields) { + String fieldName = supportedGenericField.getName(); + if (BaseUtility.containsIgnoreCases(genericColumnsForQuery, fieldName)) { + tempSupportedGenericFields.add(supportedGenericField); + } + } + + supportedGenericFields.clear(); + supportedGenericFields.addAll(tempSupportedGenericFields); + + if (foreignKeyAssociations != null && foreignKeyAssociations.size() > 0) { + for (int i = 0; i < foreignKeyAssociations.size(); i++) { + String associatedTable = DBUtility + .getTableNameByClassName(foreignKeyAssociations.get(i) + .getAssociatedClassName()); + columnList.add(getForeignKeyColumnName(associatedTable)); + } + } + if (!columnsContainsId) { + columnList.add(BaseUtility.changeCase("id")); + } + return columnList.toArray(new String[columnList.size()]); + } + return null; + } + + /** + * Analyze the associations for the specified class. + * + * @param className + * The full class name. + */ + private void analyzeAssociations(String className) { + Collection associationInfos = getAssociationInfo(className); + if (fkInCurrentModel == null) { + fkInCurrentModel = new ArrayList(); + } else { + fkInCurrentModel.clear(); + } + if (fkInOtherModel == null) { + fkInOtherModel = new ArrayList(); + } else { + fkInOtherModel.clear(); + } + for (AssociationsInfo associationInfo : associationInfos) { + if (associationInfo.getAssociationType() == Const.Model.MANY_TO_ONE + || associationInfo.getAssociationType() == Const.Model.ONE_TO_ONE) { + if (associationInfo.getClassHoldsForeignKey().equals(className)) { + fkInCurrentModel.add(associationInfo); + } else { + fkInOtherModel.add(associationInfo); + } + } else if (associationInfo.getAssociationType() == Const.Model.MANY_TO_MANY) { + fkInOtherModel.add(associationInfo); + } + } + } + + /** + * Finds the associated models of baseObj, then set them into baseObj. + * + * @param baseObj + * The class of base object. + */ + @SuppressWarnings({ "rawtypes", "unchecked" }) + private void setAssociatedModel(DataSupport baseObj) { + if (fkInOtherModel == null) { + return; + } + for (AssociationsInfo info : fkInOtherModel) { + Cursor cursor = null; + String associatedClassName = info.getAssociatedClassName(); + boolean isM2M = info.getAssociationType() == Const.Model.MANY_TO_MANY; + try { + List supportedFields = getSupportedFields(associatedClassName); + List supportedGenericFields = getSupportedGenericFields(associatedClassName); + if (isM2M) { + String tableName = baseObj.getTableName(); + String associatedTableName = DBUtility + .getTableNameByClassName(associatedClassName); + String intermediateTableName = DBUtility.getIntermediateTableName(tableName, + associatedTableName); + StringBuilder sql = new StringBuilder(); + sql.append("select * from ").append(associatedTableName) + .append(" a inner join ").append(intermediateTableName) + .append(" b on a.id = b.").append(associatedTableName + "_id") + .append(" where b.").append(tableName).append("_id = ?"); + cursor = DataSupport.findBySQL(BaseUtility.changeCase(sql.toString()), + String.valueOf(baseObj.getBaseObjId())); + } else { + String foreignKeyColumn = getForeignKeyColumnName(DBUtility + .getTableNameByClassName(info.getSelfClassName())); + String associatedTableName = DBUtility + .getTableNameByClassName(associatedClassName); + cursor = mDatabase.query(BaseUtility.changeCase(associatedTableName), null, + foreignKeyColumn + "=?", + new String[] { String.valueOf(baseObj.getBaseObjId()) }, null, null, + null, null); + } + if (cursor != null && cursor.moveToFirst()) { + SparseArray queryInfoCacheSparseArray = new SparseArray(); + Map genericModelMap = new HashMap(); + do { + DataSupport modelInstance = (DataSupport) createInstanceFromClass(Class.forName(associatedClassName)); + giveBaseObjIdValue(modelInstance, + cursor.getLong(cursor.getColumnIndexOrThrow("id"))); + setValueToModel(modelInstance, supportedFields, null, cursor, queryInfoCacheSparseArray); + setGenericValueToModel(modelInstance, supportedGenericFields, genericModelMap); + if (info.getAssociationType() == Const.Model.MANY_TO_ONE || isM2M) { + Field field = info.getAssociateOtherModelFromSelf(); + Collection collection = (Collection) getFieldValue(baseObj, field); + if (collection == null) { + if (isList(field.getType())) { + collection = new ArrayList(); + } else { + collection = new HashSet(); + } + DynamicExecutor.setField(baseObj, field.getName(), collection, baseObj.getClass()); + } + collection.add(modelInstance); + } else if (info.getAssociationType() == Const.Model.ONE_TO_ONE) { + setFieldValue(baseObj, + info.getAssociateOtherModelFromSelf(), modelInstance); + } + } while (cursor.moveToNext()); + queryInfoCacheSparseArray.clear(); + genericModelMap.clear(); + } + } catch (Exception e) { + throw new DataSupportException(e.getMessage(), e); + } finally { + if (cursor != null) { + cursor.close(); + } + } + } + } + + @SuppressWarnings("unchecked") + private void setToModelByReflection(Object modelInstance, Field field, int columnIndex, String getMethodName, Cursor cursor) + throws NoSuchMethodException, InvocationTargetException, IllegalAccessException { + Class cursorClass = cursor.getClass(); + Method method = cursorClass.getMethod(getMethodName, int.class); + Object value = method.invoke(cursor, columnIndex); + if (field.getType() == boolean.class || field.getType() == Boolean.class) { + if ("0".equals(String.valueOf(value))) { + value = false; + } else if ("1".equals(String.valueOf(value))) { + value = true; + } + } else if (field.getType() == char.class || field.getType() == Character.class) { + value = ((String) value).charAt(0); + } else if (field.getType() == Date.class) { + long date = (Long) value; + if (date <= 0) { + value = null; + } else { + value = new Date(date); + } + } + if (isCollection(field.getType())) { + Collection collection = (Collection) DynamicExecutor.getField(modelInstance, field.getName(), modelInstance.getClass()); + if (collection == null) { + if (isList(field.getType())) { + collection = new ArrayList(); + } else { + collection = new HashSet(); + } + DynamicExecutor.setField(modelInstance, field.getName(), collection, modelInstance.getClass()); + } + collection.add(value); + } else { + DynamicExecutor.setField(modelInstance, field.getName(), value, + modelInstance.getClass()); + } + } + + /** + * Cache core info for query operation to improve query performance. + * + * @since 1.3.1 + */ + class QueryInfoCache { + + String getMethodName; + + Field field; + + } + +} \ No newline at end of file diff --git a/YFDXJ/litepal/src/main/java/org/litepal/crud/DataSupport.java b/YFDXJ/litepal/src/main/java/org/litepal/crud/DataSupport.java new file mode 100644 index 0000000..a0d3655 --- /dev/null +++ b/YFDXJ/litepal/src/main/java/org/litepal/crud/DataSupport.java @@ -0,0 +1,2304 @@ +/* + * Copyright (C) Tony Green, LitePal Framework Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.litepal.crud; + +import android.content.ContentValues; +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; + +import org.litepal.LitePal; +import org.litepal.crud.async.AverageExecutor; +import org.litepal.crud.async.CountExecutor; +import org.litepal.crud.async.FindExecutor; +import org.litepal.crud.async.FindMultiExecutor; +import org.litepal.crud.async.SaveExecutor; +import org.litepal.crud.async.UpdateOrDeleteExecutor; +import org.litepal.exceptions.DataSupportException; +import org.litepal.tablemanager.Connector; +import org.litepal.util.BaseUtility; +import org.litepal.util.DBUtility; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * DataSupport connects classes to SQLite database tables to establish an almost + * zero-configuration persistence layer for applications. In the context of an + * application, these classes are commonly referred to as models. Models can + * also be connected to other models.
+ * DataSupport relies heavily on naming in that it uses class and association + * names to establish mappings between respective database tables and foreign + * key columns.
+ * Automated mapping between classes and tables, attributes and columns. + * + *
+ * public class Person extends DataSupport {
+ * 	private int id;
+ * 	private String name;
+ * 	private int age;
+ * }
+ * 
+ * The Person class is automatically mapped to the table named "person",
+ * which might look like this:
+ * 
+ * CREATE TABLE person (
+ * 	id integer primary key autoincrement,
+ * 	age integer, 
+ * 	name text
+ * );
+ * 
+ * + * @author Tony Green + * @since 1.1 + */ +public class DataSupport { + + /** + * The identify of each model. LitePal will generate the value + * automatically. Do not try to assign or modify it. + */ + private long baseObjId; + + /** + * A map contains all the associated models' id with M2O or O2O + * associations. Each corresponding table of these models contains a foreign + * key column. + */ + private Map> associatedModelsMapWithFK; + + /** + * A map contains all the associated models' id with M2O or O2O association. + * Each corresponding table of these models doesn't contain foreign key + * column. Instead self model has a foreign key column in the corresponding + * table. + */ + private Map associatedModelsMapWithoutFK; + + /** + * A map contains all the associated models' id with M2M association. + */ + private Map> associatedModelsMapForJoinTable; + + /** + * When updating a model and the associations breaks between current model + * and others, if current model holds a foreign key, it need to be cleared. + * This list holds all the foreign key names that need to clear. + */ + private List listToClearSelfFK; + + /** + * When updating a model and the associations breaks between current model + * and others, clear all the associated models' foreign key value if it + * exists. This list holds all the associated table names that need to + * clear. + */ + private List listToClearAssociatedFK; + + /** + * A list holds all the field names which need to be updated into default + * value of model. + */ + private List fieldsToSetToDefault; + + /** + * Declaring to query which columns in table. + * + *
+	 * DataSupport.select("name", "age").find(Person.class);
+	 * 
+ * + * This will find all rows with name and age columns in Person table. + * + * @param columns + * A String array of which columns to return. Passing null will + * return all columns. + * + * @return A ClusterQuery instance. + */ + public static synchronized ClusterQuery select(String... columns) { + ClusterQuery cQuery = new ClusterQuery(); + cQuery.mColumns = columns; + return cQuery; + } + + /** + * Declaring to query which rows in table. + * + *
+	 * DataSupport.where("name = ? or age > ?", "Tom", "14").find(Person.class);
+	 * 
+ * + * This will find rows which name is Tom or age greater than 14 in Person + * table. + * + * @param conditions + * A filter declaring which rows to return, formatted as an SQL + * WHERE clause. Passing null will return all rows. + * @return A ClusterQuery instance. + */ + public static synchronized ClusterQuery where(String... conditions) { + ClusterQuery cQuery = new ClusterQuery(); + cQuery.mConditions = conditions; + return cQuery; + } + + /** + * Declaring how to order the rows queried from table. + * + *
+	 * DataSupport.order("name desc").find(Person.class);
+	 * 
+ * + * This will find all rows in Person table sorted by name with inverted + * order. + * + * @param column + * How to order the rows, formatted as an SQL ORDER BY clause. + * Passing null will use the default sort order, which may be + * unordered. + * @return A ClusterQuery instance. + */ + public static synchronized ClusterQuery order(String column) { + ClusterQuery cQuery = new ClusterQuery(); + cQuery.mOrderBy = column; + return cQuery; + } + + /** + * Limits the number of rows returned by the query. + * + *
+	 * DataSupport.limit(2).find(Person.class);
+	 * 
+ * + * This will find the top 2 rows in Person table. + * + * @param value + * Limits the number of rows returned by the query, formatted as + * LIMIT clause. + * @return A ClusterQuery instance. + */ + public static synchronized ClusterQuery limit(int value) { + ClusterQuery cQuery = new ClusterQuery(); + cQuery.mLimit = String.valueOf(value); + return cQuery; + } + + /** + * Declaring the offset of rows returned by the query. This method must be + * used with {@link #limit(int)}, or nothing will return. + * + *
+	 * DataSupport.limit(1).offset(2).find(Person.class);
+	 * 
+ * + * This will find the third row in Person table. + * + * @param value + * The offset amount of rows returned by the query. + * @return A ClusterQuery instance. + */ + public static synchronized ClusterQuery offset(int value) { + ClusterQuery cQuery = new ClusterQuery(); + cQuery.mOffset = String.valueOf(value); + return cQuery; + } + + /** + * Count the records. + * + *
+	 * DataSupport.count(Person.class);
+	 * 
+ * + * This will count all rows in person table.
+ * You can also specify a where clause when counting. + * + *
+	 * DataSupport.where("age > ?", "15").count(Person.class);
+	 * 
+ * + * @param modelClass + * Which table to query from by class. + * @return Count of the specified table. + */ + public static synchronized int count(Class modelClass) { + return count(BaseUtility.changeCase(DBUtility.getTableNameByClassName(modelClass.getName()))); + } + + /** + * Basically same as {@link #count(Class)} but pending to a new thread for executing. + * + * @param modelClass + * Which table to query from by class. + * @return A CountExecutor instance. + */ + public static CountExecutor countAsync(final Class modelClass) { + return countAsync(BaseUtility.changeCase(DBUtility.getTableNameByClassName(modelClass.getName()))); + } + + /** + * Count the records. + * + *
+	 * DataSupport.count("person");
+	 * 
+ * + * This will count all rows in person table.
+ * You can also specify a where clause when counting. + * + *
+	 * DataSupport.where("age > ?", "15").count("person");
+	 * 
+ * + * @param tableName + * Which table to query from. + * @return Count of the specified table. + */ + public static synchronized int count(String tableName) { + ClusterQuery cQuery = new ClusterQuery(); + return cQuery.count(tableName); + } + + /** + * Basically same as {@link #count(String)} but pending to a new thread for executing. + * + * @param tableName + * Which table to query from. + * @return A CountExecutor instance. + */ + public static CountExecutor countAsync(final String tableName) { + final CountExecutor executor = new CountExecutor(); + Runnable runnable = new Runnable() { + @Override + public void run() { + synchronized (DataSupport.class) { + final int count = count(tableName); + if (executor.getListener() != null) { + LitePal.getHandler().post(new Runnable() { + @Override + public void run() { + executor.getListener().onFinish(count); + } + }); + } + } + } + }; + executor.submit(runnable); + return executor; + } + + /** + * Calculates the average value on a given column. + * + *
+	 * DataSupport.average(Person.class, "age");
+	 * 
+ * + * You can also specify a where clause when calculating. + * + *
+	 * DataSupport.where("age > ?", "15").average(Person.class, "age");
+	 * 
+ * + * @param modelClass + * Which table to query from by class. + * @param column + * The based on column to calculate. + * @return The average value on a given column. + */ + public static synchronized double average(Class modelClass, String column) { + return average(BaseUtility.changeCase(DBUtility.getTableNameByClassName(modelClass.getName())), column); + } + + /** + * Basically same as {@link #average(Class, String)} but pending to a new thread for executing. + * + * @param modelClass + * Which table to query from by class. + * @param column + * The based on column to calculate. + * @return A AverageExecutor instance. + */ + public static AverageExecutor averageAsync(final Class modelClass, final String column) { + return averageAsync(BaseUtility.changeCase(DBUtility.getTableNameByClassName(modelClass.getName())), column); + } + + /** + * Calculates the average value on a given column. + * + *
+	 * DataSupport.average("person", "age");
+	 * 
+ * + * You can also specify a where clause when calculating. + * + *
+	 * DataSupport.where("age > ?", "15").average("person", "age");
+	 * 
+ * + * @param tableName + * Which table to query from. + * @param column + * The based on column to calculate. + * @return The average value on a given column. + */ + public static synchronized double average(String tableName, String column) { + ClusterQuery cQuery = new ClusterQuery(); + return cQuery.average(tableName, column); + } + + /** + * Basically same as {@link #average(String, String)} but pending to a new thread for executing. + * + * @param tableName + * Which table to query from. + * @param column + * The based on column to calculate. + * @return A AverageExecutor instance. + */ + public static AverageExecutor averageAsync(final String tableName, final String column) { + final AverageExecutor executor = new AverageExecutor(); + Runnable runnable = new Runnable() { + @Override + public void run() { + synchronized (DataSupport.class) { + final double average = average(tableName, column); + if (executor.getListener() != null) { + LitePal.getHandler().post(new Runnable() { + @Override + public void run() { + executor.getListener().onFinish(average); + } + }); + } + } + } + }; + executor.submit(runnable); + return executor; + } + + /** + * Calculates the maximum value on a given column. The value is returned + * with the same data type of the column. + * + *
+	 * DataSupport.max(Person.class, "age", int.class);
+	 * 
+ * + * You can also specify a where clause when calculating. + * + *
+	 * DataSupport.where("age > ?", "15").max(Person.class, "age", Integer.TYPE);
+	 * 
+ * + * @param modelClass + * Which table to query from by class. + * @param columnName + * The based on column to calculate. + * @param columnType + * The type of the based on column. + * @return The maximum value on a given column. + */ + public static synchronized T max(Class modelClass, String columnName, Class columnType) { + return max(BaseUtility.changeCase(DBUtility.getTableNameByClassName(modelClass.getName())), columnName, columnType); + } + + /** + * Basically same as {@link #max(Class, String, Class)} but pending to a new thread for executing. + * + * @param modelClass + * Which table to query from by class. + * @param columnName + * The based on column to calculate. + * @param columnType + * The type of the based on column. + * @return A FindExecutor instance. + */ + public static FindExecutor maxAsync(final Class modelClass, final String columnName, final Class columnType) { + return maxAsync(BaseUtility.changeCase(DBUtility.getTableNameByClassName(modelClass.getName())), columnName, columnType); + } + + /** + * Calculates the maximum value on a given column. The value is returned + * with the same data type of the column. + * + *
+	 * DataSupport.max("person", "age", int.class);
+	 * 
+ * + * You can also specify a where clause when calculating. + * + *
+	 * DataSupport.where("age > ?", "15").max("person", "age", Integer.TYPE);
+	 * 
+ * + * @param tableName + * Which table to query from. + * @param columnName + * The based on column to calculate. + * @param columnType + * The type of the based on column. + * @return The maximum value on a given column. + */ + public static synchronized T max(String tableName, String columnName, Class columnType) { + ClusterQuery cQuery = new ClusterQuery(); + return cQuery.max(tableName, columnName, columnType); + } + + /** + * Basically same as {@link #max(String, String, Class)} but pending to a new thread for executing. + * + * @param tableName + * Which table to query from. + * @param columnName + * The based on column to calculate. + * @param columnType + * The type of the based on column. + * @return A FindExecutor instance. + */ + public static FindExecutor maxAsync(final String tableName, final String columnName, final Class columnType) { + final FindExecutor executor = new FindExecutor(); + Runnable runnable = new Runnable() { + @Override + public void run() { + synchronized (DataSupport.class) { + final T t = max(tableName, columnName, columnType); + if (executor.getListener() != null) { + LitePal.getHandler().post(new Runnable() { + @Override + public void run() { + executor.getListener().onFinish(t); + } + }); + } + } + } + }; + executor.submit(runnable); + return executor; + } + + /** + * Calculates the minimum value on a given column. The value is returned + * with the same data type of the column. + * + *
+	 * DataSupport.min(Person.class, "age", int.class);
+	 * 
+ * + * You can also specify a where clause when calculating. + * + *
+	 * DataSupport.where("age > ?", "15").min(Person.class, "age", Integer.TYPE);
+	 * 
+ * + * @param modelClass + * Which table to query from by class. + * @param columnName + * The based on column to calculate. + * @param columnType + * The type of the based on column. + * @return The minimum value on a given column. + */ + public static synchronized T min(Class modelClass, String columnName, Class columnType) { + return min(BaseUtility.changeCase(DBUtility.getTableNameByClassName(modelClass.getName())), columnName, columnType); + } + + /** + * Basically same as {@link #min(Class, String, Class)} but pending to a new thread for executing. + * + * @param modelClass + * Which table to query from by class. + * @param columnName + * The based on column to calculate. + * @param columnType + * The type of the based on column. + * @return A FindExecutor instance. + */ + public static FindExecutor minAsync(final Class modelClass, final String columnName, final Class columnType) { + return minAsync(BaseUtility.changeCase(DBUtility.getTableNameByClassName(modelClass.getName())), columnName, columnType); + } + + /** + * Calculates the minimum value on a given column. The value is returned + * with the same data type of the column. + * + *
+	 * DataSupport.min("person", "age", int.class);
+	 * 
+ * + * You can also specify a where clause when calculating. + * + *
+	 * DataSupport.where("age > ?", "15").min("person", "age", Integer.TYPE);
+	 * 
+ * + * @param tableName + * Which table to query from. + * @param columnName + * The based on column to calculate. + * @param columnType + * The type of the based on column. + * @return The minimum value on a given column. + */ + public static synchronized T min(String tableName, String columnName, Class columnType) { + ClusterQuery cQuery = new ClusterQuery(); + return cQuery.min(tableName, columnName, columnType); + } + + /** + * Basically same as {@link #min(String, String, Class)} but pending to a new thread for executing. + * + * @param tableName + * Which table to query from. + * @param columnName + * The based on column to calculate. + * @param columnType + * The type of the based on column. + * @return A FindExecutor instance. + */ + public static FindExecutor minAsync(final String tableName, final String columnName, final Class columnType) { + final FindExecutor executor = new FindExecutor(); + Runnable runnable = new Runnable() { + @Override + public void run() { + synchronized (DataSupport.class) { + final T t = min(tableName, columnName, columnType); + if (executor.getListener() != null) { + LitePal.getHandler().post(new Runnable() { + @Override + public void run() { + executor.getListener().onFinish(t); + } + }); + } + } + } + }; + executor.submit(runnable); + return executor; + } + + /** + * Calculates the sum of values on a given column. The value is returned + * with the same data type of the column. + * + *
+	 * DataSupport.sum(Person.class, "age", int.class);
+	 * 
+ * + * You can also specify a where clause when calculating. + * + *
+	 * DataSupport.where("age > ?", "15").sum(Person.class, "age", Integer.TYPE);
+	 * 
+ * + * @param modelClass + * Which table to query from by class. + * @param columnName + * The based on column to calculate. + * @param columnType + * The type of the based on column. + * @return The sum value on a given column. + */ + public static synchronized T sum(Class modelClass, String columnName, Class columnType) { + return sum(BaseUtility.changeCase(DBUtility.getTableNameByClassName(modelClass.getName())), columnName, columnType); + } + + /** + * Basically same as {@link #sum(Class, String, Class)} but pending to a new thread for executing. + * + * @param modelClass + * Which table to query from by class. + * @param columnName + * The based on column to calculate. + * @param columnType + * The type of the based on column. + * @return A FindExecutor instance. + */ + public static FindExecutor sumAsync(final Class modelClass, final String columnName, final Class columnType) { + return sumAsync(BaseUtility.changeCase(DBUtility.getTableNameByClassName(modelClass.getName())), columnName, columnType); + } + + /** + * Calculates the sum of values on a given column. The value is returned + * with the same data type of the column. + * + *
+	 * DataSupport.sum("person", "age", int.class);
+	 * 
+ * + * You can also specify a where clause when calculating. + * + *
+	 * DataSupport.where("age > ?", "15").sum("person", "age", Integer.TYPE);
+	 * 
+ * + * @param tableName + * Which table to query from. + * @param columnName + * The based on column to calculate. + * @param columnType + * The type of the based on column. + * @return The sum value on a given column. + */ + public static synchronized T sum(String tableName, String columnName, Class columnType) { + ClusterQuery cQuery = new ClusterQuery(); + return cQuery.sum(tableName, columnName, columnType); + } + + /** + * Basically same as {@link #sum(String, String, Class)} but pending to a new thread for executing. + * + * @param tableName + * Which table to query from. + * @param columnName + * The based on column to calculate. + * @param columnType + * The type of the based on column. + * @return A FindExecutor instance. + */ + public static FindExecutor sumAsync(final String tableName, final String columnName, final Class columnType) { + final FindExecutor executor = new FindExecutor(); + Runnable runnable = new Runnable() { + @Override + public void run() { + synchronized (DataSupport.class) { + final T t = sum(tableName, columnName, columnType); + if (executor.getListener() != null) { + LitePal.getHandler().post(new Runnable() { + @Override + public void run() { + executor.getListener().onFinish(t); + } + }); + } + } + } + }; + executor.submit(runnable); + return executor; + } + + /** + * Finds the record by a specific id. + * + *
+	 * Person p = DataSupport.find(Person.class, 1);
+	 * 
+ * + * The modelClass determines which table to query and the object type to + * return. If no record can be found, then return null.
+ * + * Note that the associated models won't be loaded by default considering + * the efficiency, but you can do that by using + * {@link org.litepal.crud.DataSupport#find(Class, long, boolean)}. + * + * @param modelClass + * Which table to query and the object type to return. + * @param id + * Which record to query. + * @return An object with found data from database, or null. + */ + public static synchronized T find(Class modelClass, long id) { + return find(modelClass, id, false); + } + + /** + * Basically same as {@link #find(Class, long)} but pending to a new thread for executing. + * + * @param modelClass + * Which table to query and the object type to return. + * @param id + * Which record to query. + * @return A FindExecutor instance. + */ + public static FindExecutor findAsync(Class modelClass, long id) { + return findAsync(modelClass, id, false); + } + + /** + * It is mostly same as {@link org.litepal.crud.DataSupport#find(Class, long)} but an isEager + * parameter. If set true the associated models will be loaded as well. + *
+ * Note that isEager will only work for one deep level relation, considering the query efficiency. + * You have to implement on your own if you need to load multiple deepness of relation at once. + * + * @param modelClass + * Which table to query and the object type to return. + * @param id + * Which record to query. + * @param isEager + * True to load the associated models, false not. + * @return An object with found data from database, or null. + */ + public static synchronized T find(Class modelClass, long id, boolean isEager) { + QueryHandler queryHandler = new QueryHandler(Connector.getDatabase()); + return queryHandler.onFind(modelClass, id, isEager); + } + + /** + * Basically same as {@link #find(Class, long, boolean)} but pending to a new thread for executing. + * + * @param modelClass + * Which table to query and the object type to return. + * @param id + * Which record to query. + * @param isEager + * True to load the associated models, false not. + * @return A FindExecutor instance. + */ + public static FindExecutor findAsync(final Class modelClass, final long id, final boolean isEager) { + final FindExecutor executor = new FindExecutor(); + Runnable runnable = new Runnable() { + @Override + public void run() { + synchronized (DataSupport.class) { + final T t = find(modelClass, id, isEager); + if (executor.getListener() != null) { + LitePal.getHandler().post(new Runnable() { + @Override + public void run() { + executor.getListener().onFinish(t); + } + }); + } + } + } + }; + executor.submit(runnable); + return executor; + } + + /** + * Finds the first record of a single table. + * + *
+	 * Person p = DataSupport.findFirst(Person.class);
+	 * 
+ * + * Note that the associated models won't be loaded by default considering + * the efficiency, but you can do that by using + * {@link org.litepal.crud.DataSupport#findFirst(Class, boolean)}. + * + * @param modelClass + * Which table to query and the object type to return. + * @return An object with data of first row, or null. + */ + public static synchronized T findFirst(Class modelClass) { + return findFirst(modelClass, false); + } + + /** + * Basically same as {@link #findFirst(Class)} but pending to a new thread for executing. + * + * @param modelClass + * Which table to query and the object type to return. + * @return A FindExecutor instance. + */ + public static FindExecutor findFirstAsync(Class modelClass) { + return findFirstAsync(modelClass, false); + } + + /** + * It is mostly same as {@link org.litepal.crud.DataSupport#findFirst(Class)} but an isEager + * parameter. If set true the associated models will be loaded as well. + *
+ * Note that isEager will only work for one deep level relation, considering the query efficiency. + * You have to implement on your own if you need to load multiple deepness of relation at once. + * + * @param modelClass + * Which table to query and the object type to return. + * @param isEager + * True to load the associated models, false not. + * @return An object with data of first row, or null. + */ + public static synchronized T findFirst(Class modelClass, boolean isEager) { + QueryHandler queryHandler = new QueryHandler(Connector.getDatabase()); + return queryHandler.onFindFirst(modelClass, isEager); + } + + /** + * Basically same as {@link #findFirstAsync(Class, boolean)} but pending to a new thread for executing. + * + * @param modelClass + * Which table to query and the object type to return. + * @param isEager + * True to load the associated models, false not. + * @return A FindExecutor instance. + */ + public static FindExecutor findFirstAsync(final Class modelClass, final boolean isEager) { + final FindExecutor executor = new FindExecutor(); + Runnable runnable = new Runnable() { + @Override + public void run() { + synchronized (DataSupport.class) { + final T t = findFirst(modelClass, isEager); + if (executor.getListener() != null) { + LitePal.getHandler().post(new Runnable() { + @Override + public void run() { + executor.getListener().onFinish(t); + } + }); + } + } + } + }; + executor.submit(runnable); + return executor; + } + + /** + * Finds the last record of a single table. + * + *
+	 * Person p = DataSupport.findLast(Person.class);
+	 * 
+ * + * Note that the associated models won't be loaded by default considering + * the efficiency, but you can do that by using + * {@link org.litepal.crud.DataSupport#findLast(Class, boolean)}. + * + * @param modelClass + * Which table to query and the object type to return. + * @return An object with data of last row, or null. + */ + public static synchronized T findLast(Class modelClass) { + return findLast(modelClass, false); + } + + /** + * Basically same as {@link #findLast(Class)} but pending to a new thread for executing. + * + * @param modelClass + * Which table to query and the object type to return. + * @return A FindExecutor instance. + */ + public static FindExecutor findLastAsync(Class modelClass) { + return findLastAsync(modelClass, false); + } + + /** + * It is mostly same as {@link org.litepal.crud.DataSupport#findLast(Class)} but an isEager + * parameter. If set true the associated models will be loaded as well. + *
+ * Note that isEager will only work for one deep level relation, considering the query efficiency. + * You have to implement on your own if you need to load multiple deepness of relation at once. + * + * @param modelClass + * Which table to query and the object type to return. + * @param isEager + * True to load the associated models, false not. + * @return An object with data of last row, or null. + */ + public static synchronized T findLast(Class modelClass, boolean isEager) { + QueryHandler queryHandler = new QueryHandler(Connector.getDatabase()); + return queryHandler.onFindLast(modelClass, isEager); + } + + /** + * Basically same as {@link #findLast(Class, boolean)} but pending to a new thread for executing. + * + * @param modelClass + * Which table to query and the object type to return. + * @param isEager + * True to load the associated models, false not. + * @return A FindExecutor instance. + */ + public static FindExecutor findLastAsync(final Class modelClass, final boolean isEager) { + final FindExecutor executor = new FindExecutor(); + Runnable runnable = new Runnable() { + @Override + public void run() { + synchronized (DataSupport.class) { + final T t = findLast(modelClass, isEager); + if (executor.getListener() != null) { + LitePal.getHandler().post(new Runnable() { + @Override + public void run() { + executor.getListener().onFinish(t); + } + }); + } + } + } + }; + executor.submit(runnable); + return executor; + } + + /** + * Finds multiple records by an id array. + * + *
+	 * List<Person> people = DataSupport.findAll(Person.class, 1, 2, 3);
+	 * 
+	 * long[] bookIds = { 10, 18 };
+	 * List<Book> books = DataSupport.findAll(Book.class, bookIds);
+	 * 
+ * + * Of course you can find all records by passing nothing to the ids + * parameter. + * + *
+	 * List<Book> allBooks = DataSupport.findAll(Book.class);
+	 * 
+ * + * Note that the associated models won't be loaded by default considering + * the efficiency, but you can do that by using + * {@link org.litepal.crud.DataSupport#findAll(Class, boolean, long...)}. + * + * The modelClass determines which table to query and the object type to + * return. + * + * @param modelClass + * Which table to query and the object type to return as a list. + * @param ids + * Which records to query. Or do not pass it to find all records. + * @return An object list with found data from database, or an empty list. + */ + public static synchronized List findAll(Class modelClass, long... ids) { + return findAll(modelClass, false, ids); + } + + /** + * Basically same as {@link #findAll(Class, long...)} but pending to a new thread for executing. + * + * @param modelClass + * Which table to query and the object type to return as a list. + * @param ids + * Which records to query. Or do not pass it to find all records. + * @return A FindMultiExecutor instance. + */ + public static FindMultiExecutor findAllAsync(Class modelClass, long... ids) { + return findAllAsync(modelClass, false, ids); + } + + /** + * It is mostly same as {@link org.litepal.crud.DataSupport#findAll(Class, long...)} but an + * isEager parameter. If set true the associated models will be loaded as well. + *
+ * Note that isEager will only work for one deep level relation, considering the query efficiency. + * You have to implement on your own if you need to load multiple deepness of relation at once. + * + * @param modelClass + * Which table to query and the object type to return as a list. + * @param isEager + * True to load the associated models, false not. + * @param ids + * Which records to query. Or do not pass it to find all records. + * @return An object list with found data from database, or an empty list. + */ + public static synchronized List findAll(Class modelClass, boolean isEager, + long... ids) { + QueryHandler queryHandler = new QueryHandler(Connector.getDatabase()); + return queryHandler.onFindAll(modelClass, isEager, ids); + } + + /** + * Basically same as {@link #findAll(Class, boolean, long...)} but pending to a new thread for executing. + * + * @param modelClass + * Which table to query and the object type to return as a list. + * @param isEager + * True to load the associated models, false not. + * @param ids + * Which records to query. Or do not pass it to find all records. + * @return A FindMultiExecutor instance. + */ + public static FindMultiExecutor findAllAsync(final Class modelClass, final boolean isEager, final long... ids) { + final FindMultiExecutor executor = new FindMultiExecutor(); + Runnable runnable = new Runnable() { + @Override + public void run() { + synchronized (DataSupport.class) { + final List t = findAll(modelClass, isEager, ids); + if (executor.getListener() != null) { + LitePal.getHandler().post(new Runnable() { + @Override + public void run() { + executor.getListener().onFinish(t); + } + }); + } + } + } + }; + executor.submit(runnable); + return executor; + } + + /** + * Runs the provided SQL and returns a Cursor over the result set. You may + * include ? in where clause in the query, which will be replaced by the + * second to the last parameters, such as: + * + *
+	 * Cursor cursor = DataSupport.findBySQL("select * from person where name=? and age=?", "Tom", "14");
+	 * 
+ * + * @param sql + * First parameter is the SQL clause to apply. Second to the last + * parameters will replace the place holders. + * @return A Cursor object, which is positioned before the first entry. Note + * that Cursors are not synchronized, see the documentation for more + * details. + */ + public static synchronized Cursor findBySQL(String... sql) { + BaseUtility.checkConditionsCorrect(sql); + if (sql == null) { + return null; + } + if (sql.length <= 0) { + return null; + } + String[] selectionArgs; + if (sql.length == 1) { + selectionArgs = null; + } else { + selectionArgs = new String[sql.length - 1]; + System.arraycopy(sql, 1, selectionArgs, 0, sql.length - 1); + } + return Connector.getDatabase().rawQuery(sql[0], selectionArgs); + } + + /** + * Deletes the record in the database by id.
+ * The data in other tables which is referenced with the record will be + * removed too. + * + *
+	 * DataSupport.delete(Person.class, 1);
+	 * 
+ * + * This means that the record 1 in person table will be removed. + * + * @param modelClass + * Which table to delete from by class. + * @param id + * Which record to delete. + * @return The number of rows affected. Including cascade delete rows. + */ + public static synchronized int delete(Class modelClass, long id) { + int rowsAffected = 0; + SQLiteDatabase db = Connector.getDatabase(); + db.beginTransaction(); + try { + DeleteHandler deleteHandler = new DeleteHandler(db); + rowsAffected = deleteHandler.onDelete(modelClass, id); + db.setTransactionSuccessful(); + return rowsAffected; + } finally { + db.endTransaction(); + } + } + + /** + * Basically same as {@link #delete(Class, long)} but pending to a new thread for executing. + * + * @param modelClass + * Which table to delete from by class. + * @param id + * Which record to delete. + * @return A UpdateOrDeleteExecutor instance. + */ + public static UpdateOrDeleteExecutor deleteAsync(final Class modelClass, final long id) { + final UpdateOrDeleteExecutor executor = new UpdateOrDeleteExecutor(); + Runnable runnable = new Runnable() { + @Override + public void run() { + synchronized (DataSupport.class) { + final int rowsAffected = delete(modelClass, id); + if (executor.getListener() != null) { + LitePal.getHandler().post(new Runnable() { + @Override + public void run() { + executor.getListener().onFinish(rowsAffected); + } + }); + } + } + } + }; + executor.submit(runnable); + return executor; + } + + /** + * Deletes all records with details given if they match a set of conditions + * supplied. This method constructs a single SQL DELETE statement and sends + * it to the database. + * + *
+	 * DataSupport.deleteAll(Person.class, "name = ? and age = ?", "Tom", "14");
+	 * 
+ * + * This means that all the records which name is Tom and age is 14 will be + * removed.
+ * + * @param modelClass + * Which table to delete from by class. + * @param conditions + * A string array representing the WHERE part of an SQL + * statement. First parameter is the WHERE clause to apply when + * deleting. The way of specifying place holders is to insert one + * or more question marks in the SQL. The first question mark is + * replaced by the second element of the array, the next question + * mark by the third, and so on. Passing empty string will update + * all rows. + * @return The number of rows affected. + */ + public static synchronized int deleteAll(Class modelClass, String... conditions) { + DeleteHandler deleteHandler = new DeleteHandler(Connector.getDatabase()); + return deleteHandler.onDeleteAll(modelClass, conditions); + } + + /** + * Basically same as {@link #deleteAll(Class, String...)} but pending to a new thread for executing. + * + * @param modelClass + * Which table to delete from by class. + * @param conditions + * A string array representing the WHERE part of an SQL + * statement. First parameter is the WHERE clause to apply when + * deleting. The way of specifying place holders is to insert one + * or more question marks in the SQL. The first question mark is + * replaced by the second element of the array, the next question + * mark by the third, and so on. Passing empty string will update + * all rows. + * @return A UpdateOrDeleteExecutor instance. + */ + public static UpdateOrDeleteExecutor deleteAllAsync(final Class modelClass, final String... conditions) { + final UpdateOrDeleteExecutor executor = new UpdateOrDeleteExecutor(); + Runnable runnable = new Runnable() { + @Override + public void run() { + synchronized (DataSupport.class) { + final int rowsAffected = deleteAll(modelClass, conditions); + if (executor.getListener() != null) { + LitePal.getHandler().post(new Runnable() { + @Override + public void run() { + executor.getListener().onFinish(rowsAffected); + } + }); + } + } + } + }; + executor.submit(runnable); + return executor; + } + + /** + * Deletes all records with details given if they match a set of conditions + * supplied. This method constructs a single SQL DELETE statement and sends + * it to the database. + * + *
+	 * DataSupport.deleteAll("person", "name = ? and age = ?", "Tom", "14");
+	 * 
+ * + * This means that all the records which name is Tom and age is 14 will be + * removed.
+ * + * Note that this method won't delete the referenced data in other tables. + * You should remove those values by your own. + * + * @param tableName + * Which table to delete from. + * @param conditions + * A string array representing the WHERE part of an SQL + * statement. First parameter is the WHERE clause to apply when + * deleting. The way of specifying place holders is to insert one + * or more question marks in the SQL. The first question mark is + * replaced by the second element of the array, the next question + * mark by the third, and so on. Passing empty string will update + * all rows. + * @return The number of rows affected. + */ + public static synchronized int deleteAll(String tableName, String... conditions) { + DeleteHandler deleteHandler = new DeleteHandler(Connector.getDatabase()); + return deleteHandler.onDeleteAll(tableName, conditions); + } + + /** + * Basically same as {@link #deleteAll(String, String...)} but pending to a new thread for executing. + * + * @param tableName + * Which table to delete from. + * @param conditions + * A string array representing the WHERE part of an SQL + * statement. First parameter is the WHERE clause to apply when + * deleting. The way of specifying place holders is to insert one + * or more question marks in the SQL. The first question mark is + * replaced by the second element of the array, the next question + * mark by the third, and so on. Passing empty string will update + * all rows. + * @return A UpdateOrDeleteExecutor instance. + */ + public static UpdateOrDeleteExecutor deleteAllAsync(final String tableName, final String... conditions) { + final UpdateOrDeleteExecutor executor = new UpdateOrDeleteExecutor(); + Runnable runnable = new Runnable() { + @Override + public void run() { + synchronized (DataSupport.class) { + final int rowsAffected = deleteAll(tableName, conditions); + if (executor.getListener() != null) { + LitePal.getHandler().post(new Runnable() { + @Override + public void run() { + executor.getListener().onFinish(rowsAffected); + } + }); + } + } + } + }; + executor.submit(runnable); + return executor; + } + + /** + * Updates the corresponding record by id with ContentValues. Returns the + * number of affected rows. + * + *
+	 * ContentValues cv = new ContentValues();
+	 * cv.put("name", "Jim");
+	 * DataSupport.update(Person.class, cv, 1);
+	 * 
+ * + * This means that the name of record 1 will be updated into Jim.
+ * + * @param modelClass + * Which table to update by class. + * @param values + * A map from column names to new column values. null is a valid + * value that will be translated to NULL. + * @param id + * Which record to update. + * @return The number of rows affected. + */ + public static synchronized int update(Class modelClass, ContentValues values, long id) { + UpdateHandler updateHandler = new UpdateHandler(Connector.getDatabase()); + return updateHandler.onUpdate(modelClass, id, values); + } + + /** + * Basically same as {@link #update(Class, ContentValues, long)} but pending to a new thread for executing. + * + * @param modelClass + * Which table to update by class. + * @param values + * A map from column names to new column values. null is a valid + * value that will be translated to NULL. + * @param id + * Which record to update. + * @return A UpdateOrDeleteExecutor instance. + */ + public static UpdateOrDeleteExecutor updateAsync(final Class modelClass, final ContentValues values, final long id) { + final UpdateOrDeleteExecutor executor = new UpdateOrDeleteExecutor(); + Runnable runnable = new Runnable() { + @Override + public void run() { + synchronized (DataSupport.class) { + final int rowsAffected = update(modelClass, values, id); + if (executor.getListener() != null) { + LitePal.getHandler().post(new Runnable() { + @Override + public void run() { + executor.getListener().onFinish(rowsAffected); + } + }); + } + } + } + }; + executor.submit(runnable); + return executor; + } + + /** + * Updates all records with details given if they match a set of conditions + * supplied. This method constructs a single SQL UPDATE statement and sends + * it to the database. + * + *
+	 * ContentValues cv = new ContentValues();
+	 * cv.put("name", "Jim");
+	 * DataSupport.update(Person.class, cv, "name = ?", "Tom");
+	 * 
+ * + * This means that all the records which name is Tom will be updated into + * Jim. + * + * @param modelClass + * Which table to update by class. + * @param values + * A map from column names to new column values. null is a valid + * value that will be translated to NULL. + * @param conditions + * A string array representing the WHERE part of an SQL + * statement. First parameter is the WHERE clause to apply when + * updating. The way of specifying place holders is to insert one + * or more question marks in the SQL. The first question mark is + * replaced by the second element of the array, the next question + * mark by the third, and so on. Passing empty string will update + * all rows. + * @return The number of rows affected. + */ + public static synchronized int updateAll(Class modelClass, ContentValues values, + String... conditions) { + return updateAll(BaseUtility.changeCase(DBUtility.getTableNameByClassName( + modelClass.getName())), values, conditions); + } + + /** + * Basically same as {@link #updateAll(Class, ContentValues, String...)} but pending to a new thread for executing. + * + * @param modelClass + * Which table to update by class. + * @param values + * A map from column names to new column values. null is a valid + * value that will be translated to NULL. + * @param conditions + * A string array representing the WHERE part of an SQL + * statement. First parameter is the WHERE clause to apply when + * updating. The way of specifying place holders is to insert one + * or more question marks in the SQL. The first question mark is + * replaced by the second element of the array, the next question + * mark by the third, and so on. Passing empty string will update + * all rows. + * @return A UpdateOrDeleteExecutor instance. + */ + public static UpdateOrDeleteExecutor updateAllAsync(Class modelClass, ContentValues values, String... conditions) { + return updateAllAsync(BaseUtility.changeCase(DBUtility.getTableNameByClassName( + modelClass.getName())), values, conditions); + } + + /** + * Updates all records with details given if they match a set of conditions + * supplied. This method constructs a single SQL UPDATE statement and sends + * it to the database. + * + *
+	 * ContentValues cv = new ContentValues();
+	 * cv.put("name", "Jim");
+	 * DataSupport.update("person", cv, "name = ?", "Tom");
+	 * 
+ * + * This means that all the records which name is Tom will be updated into + * Jim. + * + * @param tableName + * Which table to update. + * @param values + * A map from column names to new column values. null is a valid + * value that will be translated to NULL. + * @param conditions + * A string array representing the WHERE part of an SQL + * statement. First parameter is the WHERE clause to apply when + * updating. The way of specifying place holders is to insert one + * or more question marks in the SQL. The first question mark is + * replaced by the second element of the array, the next question + * mark by the third, and so on. Passing empty string will update + * all rows. + * @return The number of rows affected. + */ + public static synchronized int updateAll(String tableName, ContentValues values, + String... conditions) { + UpdateHandler updateHandler = new UpdateHandler(Connector.getDatabase()); + return updateHandler.onUpdateAll(tableName, values, conditions); + } + + /** + * Basically same as {@link #updateAll(String, ContentValues, String...)} but pending to a new thread for executing. + * + * @param tableName + * Which table to update. + * @param values + * A map from column names to new column values. null is a valid + * value that will be translated to NULL. + * @param conditions + * A string array representing the WHERE part of an SQL + * statement. First parameter is the WHERE clause to apply when + * updating. The way of specifying place holders is to insert one + * or more question marks in the SQL. The first question mark is + * replaced by the second element of the array, the next question + * mark by the third, and so on. Passing empty string will update + * all rows. + * @return A UpdateOrDeleteExecutor instance. + */ + public static UpdateOrDeleteExecutor updateAllAsync(final String tableName, final ContentValues values, final String... conditions) { + final UpdateOrDeleteExecutor executor = new UpdateOrDeleteExecutor(); + Runnable runnable = new Runnable() { + @Override + public void run() { + synchronized (DataSupport.class) { + final int rowsAffected = updateAll(tableName, values, conditions); + if (executor.getListener() != null) { + LitePal.getHandler().post(new Runnable() { + @Override + public void run() { + executor.getListener().onFinish(rowsAffected); + } + }); + } + } + } + }; + executor.submit(runnable); + return executor; + } + + /** + * Saves the collection into database.
+ * + *
+	 * DataSupport.saveAll(people);
+	 * 
+ * + * If the model in collection is a new record gets created in the database, + * otherwise the existing record gets updated.
+ * If saving process failed by any accident, the whole action will be + * cancelled and your database will be rolled back.
+ * This method acts the same result as the below way, but much more + * efficient. + * + *
+	 * for (Person person : people) {
+	 * 	person.save();
+	 * }
+	 * 
+ * + * So when your collection holds huge of models, + * {@link #saveAll(java.util.Collection)} is the better choice. + * + * @param collection + * Holds all models to save. + */ + public static synchronized void saveAll(Collection collection) { + SQLiteDatabase db = Connector.getDatabase(); + db.beginTransaction(); + try { + SaveHandler saveHandler = new SaveHandler(db); + saveHandler.onSaveAll(collection); + db.setTransactionSuccessful(); + } catch (Exception e) { + throw new DataSupportException(e.getMessage(), e); + } finally { + db.endTransaction(); + } + } + + /** + * Basically same as {@link #saveAll(Collection)} but pending to a new thread for executing. + * + * @param collection + * Holds all models to save. + * @return A SaveExecutor instance. + */ + public static SaveExecutor saveAllAsync(final Collection collection) { + final SaveExecutor executor = new SaveExecutor(); + Runnable runnable = new Runnable() { + @Override + public void run() { + synchronized (DataSupport.class) { + boolean success; + try { + saveAll(collection); + success = true; + } catch (Exception e) { + success = false; + } + final boolean result = success; + if (executor.getListener() != null) { + LitePal.getHandler().post(new Runnable() { + @Override + public void run() { + executor.getListener().onFinish(result); + } + }); + } + } + } + }; + executor.submit(runnable); + return executor; + } + + /** + * Provide a way to mark all models in collection as deleted. This means these models' save + * state is no longer exist anymore. If save them again, they will be treated as inserting new + * data instead of updating the exist one. + * @param collection + * Collection of models which want to mark as deleted and clear their save state. + */ + public static void markAsDeleted(Collection collection) { + for (T t : collection) { + t.clearSavedState(); + } + } + + /** + * Check if the specified conditions data already exists in the table. + * @param modelClass + * Which table to check by class. + * @param conditions + * A filter declaring which data to check. Exactly same use as + * {@link DataSupport#where(String...)}, except null conditions will result in false. + * @return Return true if the specified conditions data already exists in the table. + * False otherwise. Null conditions will result in false. + */ + public static boolean isExist(Class modelClass, String... conditions) { + if (conditions == null) { + return false; + } + return where(conditions).count(modelClass) > 0; + } + + /** + * Deletes the record in the database. The record must be saved already.
+ * The data in other tables which is referenced with the record will be + * removed too. + * + *
+	 * Person person;
+	 * ....
+	 * if (person.isSaved()) {
+	 * 		person.delete();
+	 * }
+	 * 
+ * + * @return The number of rows affected. Including cascade delete rows. + */ + public synchronized int delete() { + SQLiteDatabase db = Connector.getDatabase(); + db.beginTransaction(); + try { + DeleteHandler deleteHandler = new DeleteHandler(db); + int rowsAffected = deleteHandler.onDelete(this); + baseObjId = 0; + db.setTransactionSuccessful(); + return rowsAffected; + } finally { + db.endTransaction(); + } + } + + /** + * Basically same as {@link #delete()} but pending to a new thread for executing. + * + * @return A UpdateOrDeleteExecutor instance. + */ + public UpdateOrDeleteExecutor deleteAsync() { + final UpdateOrDeleteExecutor executor = new UpdateOrDeleteExecutor(); + Runnable runnable = new Runnable() { + @Override + public void run() { + synchronized (DataSupport.class) { + final int rowsAffected = delete(); + if (executor.getListener() != null) { + LitePal.getHandler().post(new Runnable() { + @Override + public void run() { + executor.getListener().onFinish(rowsAffected); + } + }); + } + } + } + }; + executor.submit(runnable); + return executor; + } + + /** + * Updates the corresponding record by id. Use setXxx to decide which + * columns to update. + * + *
+	 * Person person = new Person();
+	 * person.setName("Jim");
+	 * person.update(1);
+	 * 
+ * + * This means that the name of record 1 will be updated into Jim.
+ * + * Note: 1. If you set a default value to a field, the corresponding + * column won't be updated. Use {@link #setToDefault(String)} to update + * columns into default value. 2. This method couldn't update foreign key in + * database. So do not use setXxx to set associations between models. + * + * @param id + * Which record to update. + * @return The number of rows affected. + */ + public synchronized int update(long id) { + try { + UpdateHandler updateHandler = new UpdateHandler(Connector.getDatabase()); + int rowsAffected = updateHandler.onUpdate(this, id); + getFieldsToSetToDefault().clear(); + return rowsAffected; + } catch (Exception e) { + throw new DataSupportException(e.getMessage(), e); + } + } + + /** + * Basically same as {@link #update(long)} but pending to a new thread for executing. + * + * @param id + * Which record to update. + * @return A UpdateOrDeleteExecutor instance. + */ + public UpdateOrDeleteExecutor updateAsync(final long id) { + final UpdateOrDeleteExecutor executor = new UpdateOrDeleteExecutor(); + Runnable runnable = new Runnable() { + @Override + public void run() { + synchronized (DataSupport.class) { + final int rowsAffected = update(id); + if (executor.getListener() != null) { + LitePal.getHandler().post(new Runnable() { + @Override + public void run() { + executor.getListener().onFinish(rowsAffected); + } + }); + } + } + } + }; + executor.submit(runnable); + return executor; + } + + /** + * Updates all records with details given if they match a set of conditions + * supplied. This method constructs a single SQL UPDATE statement and sends + * it to the database. + * + *
+	 * Person person = new Person();
+	 * person.setName("Jim");
+	 * person.updateAll("name = ?", "Tom");
+	 * 
+ * + * This means that all the records which name is Tom will be updated into + * Jim.
+ * + * Note: 1. If you set a default value to a field, the corresponding + * column won't be updated. Use {@link #setToDefault(String)} to update + * columns into default value. 2. This method couldn't update foreign key in + * database. So do not use setXxx to set associations between models. + * + * @param conditions + * A string array representing the WHERE part of an SQL + * statement. First parameter is the WHERE clause to apply when + * updating. The way of specifying place holders is to insert one + * or more question marks in the SQL. The first question mark is + * replaced by the second element of the array, the next question + * mark by the third, and so on. Passing empty string will update + * all rows. + * @return The number of rows affected. + */ + public synchronized int updateAll(String... conditions) { + try { + UpdateHandler updateHandler = new UpdateHandler(Connector.getDatabase()); + int rowsAffected = updateHandler.onUpdateAll(this, conditions); + getFieldsToSetToDefault().clear(); + return rowsAffected; + } catch (Exception e) { + throw new DataSupportException(e.getMessage(), e); + } + } + + /** + * Basically same as {@link #updateAll(String...)} but pending to a new thread for executing. + * + * @param conditions + * A string array representing the WHERE part of an SQL + * statement. First parameter is the WHERE clause to apply when + * updating. The way of specifying place holders is to insert one + * or more question marks in the SQL. The first question mark is + * replaced by the second element of the array, the next question + * mark by the third, and so on. Passing empty string will update + * all rows. + * @return A UpdateOrDeleteExecutor instance. + */ + public UpdateOrDeleteExecutor updateAllAsync(final String... conditions) { + final UpdateOrDeleteExecutor executor = new UpdateOrDeleteExecutor(); + Runnable runnable = new Runnable() { + @Override + public void run() { + synchronized (DataSupport.class) { + final int rowsAffected = updateAll(conditions); + if (executor.getListener() != null) { + LitePal.getHandler().post(new Runnable() { + @Override + public void run() { + executor.getListener().onFinish(rowsAffected); + } + }); + } + } + } + }; + executor.submit(runnable); + return executor; + } + + /** + * Saves the model.
+ * + *
+	 * Person person = new Person();
+	 * person.setName("Tom");
+	 * person.setAge(22);
+	 * person.save();
+	 * 
+ * + * If the model is a new record gets created in the database, otherwise the + * existing record gets updated.
+ * If saving process failed by any accident, the whole action will be + * cancelled and your database will be rolled back.
+ * If the model has a field named id or _id and field type is int or long, + * the id value generated by database will assign to it after the model is + * saved.
+ * Note that if the associated models of this model is already saved. The + * associations between them will be built automatically in database after + * it saved. + * + * @return If the model is saved successfully, return true. Any exception + * happens, return false. + */ + public synchronized boolean save() { + try { + saveThrows(); + return true; + } catch (Exception e) { + e.printStackTrace(); + return false; + } + } + + /** + * Basically same as {@link #save()} but pending to a new thread for executing. + * + * @return A SaveExecutor instance. + */ + public SaveExecutor saveAsync() { + final SaveExecutor executor = new SaveExecutor(); + Runnable runnable = new Runnable() { + @Override + public void run() { + synchronized (DataSupport.class) { + final boolean success = save(); + if (executor.getListener() != null) { + LitePal.getHandler().post(new Runnable() { + @Override + public void run() { + executor.getListener().onFinish(success); + } + }); + } + } + } + }; + executor.submit(runnable); + return executor; + } + + /** + * Saves the model.
+ * + *
+	 * Person person = new Person();
+	 * person.setName("Tom");
+	 * person.setAge(22);
+	 * person.saveThrows();
+	 * 
+ * + * If the model is a new record gets created in the database, otherwise the + * existing record gets updated.
+ * If saving process failed by any accident, the whole action will be + * cancelled and your database will be rolled back and throws + * {@link DataSupportException}
+ * If the model has a field named id or _id and field type is int or long, + * the id value generated by database will assign to it after the model is + * saved.
+ * Note that if the associated models of this model is already saved. The + * associations between them will be built automatically in database after + * it saved. + * + * @throws DataSupportException + */ + public synchronized void saveThrows() { + SQLiteDatabase db = Connector.getDatabase(); + db.beginTransaction(); + try { + SaveHandler saveHandler = new SaveHandler(db); + saveHandler.onSave(this); + clearAssociatedData(); + db.setTransactionSuccessful(); + } catch (Exception e) { + throw new DataSupportException(e.getMessage(), e); + } finally { + db.endTransaction(); + } + } + + /** + * This method is deprecated and will be removed in the future releases. + * Use {@link #saveOrUpdate(String...)} instead. + */ + @Deprecated + public synchronized boolean saveIfNotExist(String... conditions) { + if (!isExist(getClass(), conditions)) { + return save(); + } + return false; + } + + /** + * Save the model if the conditions data not exist, or update the matching models if the conditions data exist.
+ * + *
+     * Person person = new Person();
+     * person.setName("Tom");
+     * person.setAge(22);
+     * person.saveOrUpdate("name = ?", "Tom");
+     * 
+ * + * If person table doesn't have a name with Tom, a new record gets created in the database, + * otherwise all records which names are Tom will be updated.
+ * If saving process failed by any accident, the whole action will be + * cancelled and your database will be rolled back.
+ * If the model has a field named id or _id and field type is int or long, + * the id value generated by database will assign to it after the model is + * saved.
+ * Note that if the associated models of this model is already saved. The + * associations between them will be built automatically in database after + * it saved. + * + * @param conditions + * A string array representing the WHERE part of an SQL + * statement. First parameter is the WHERE clause to apply when + * updating. The way of specifying place holders is to insert one + * or more question marks in the SQL. The first question mark is + * replaced by the second element of the array, the next question + * mark by the third, and so on. Passing empty string will update + * all rows. + * @return If the model saved or updated successfully, return true. Otherwise return false. + */ + @SuppressWarnings("unchecked") + public synchronized boolean saveOrUpdate(String... conditions) { + if (conditions == null) { + return save(); + } + List list = (List) where(conditions).find(getClass()); + if (list.isEmpty()) { + return save(); + } else { + SQLiteDatabase db = Connector.getDatabase(); + db.beginTransaction(); + try { + for (DataSupport dataSupport : list) { + baseObjId = dataSupport.getBaseObjId(); + SaveHandler saveHandler = new SaveHandler(db); + saveHandler.onSave(this); + clearAssociatedData(); + } + db.setTransactionSuccessful(); + return true; + } catch (Exception e) { + e.printStackTrace(); + return false; + } finally { + db.endTransaction(); + } + } + } + + /** + * Basically same as {@link #saveOrUpdate(String...)} but pending to a new thread for executing. + * + * @param conditions + * A string array representing the WHERE part of an SQL + * statement. First parameter is the WHERE clause to apply when + * updating. The way of specifying place holders is to insert one + * or more question marks in the SQL. The first question mark is + * replaced by the second element of the array, the next question + * mark by the third, and so on. Passing empty string will update + * all rows. + * @return A SaveExecutor instance. + */ + public SaveExecutor saveOrUpdateAsync(final String... conditions) { + final SaveExecutor executor = new SaveExecutor(); + Runnable runnable = new Runnable() { + @Override + public void run() { + synchronized (DataSupport.class) { + final boolean success = saveOrUpdate(conditions); + if (executor.getListener() != null) { + LitePal.getHandler().post(new Runnable() { + @Override + public void run() { + executor.getListener().onFinish(success); + } + }); + } + } + } + }; + executor.submit(runnable); + return executor; + } + + /** + * This method is deprecated and will be removed in the future releases. + * Use {@link #save()} instead. + */ + @Deprecated + public synchronized boolean saveFast() { + SQLiteDatabase db = Connector.getDatabase(); + db.beginTransaction(); + try { + SaveHandler saveHandler = new SaveHandler(db); + saveHandler.onSaveFast(this); + db.setTransactionSuccessful(); + return true; + } catch (Exception e) { + e.printStackTrace(); + return false; + } finally { + db.endTransaction(); + } + } + + /** + * Current model is saved or not. + * + * @return If saved return true, or return false. + */ + public boolean isSaved() { + return baseObjId > 0; + } + + /** + * It model is saved, clear the saved state and model becomes unsaved. Otherwise nothing will happen. + */ + public void clearSavedState() { + baseObjId = 0; + } + + /** + * When updating database with {@link org.litepal.crud.DataSupport#update(long)}, you must + * use this method to update a field into default value. Use setXxx with + * default value of the model won't update anything.
+ * + * @param fieldName + * The name of field to update into default value. + */ + public void setToDefault(String fieldName) { + getFieldsToSetToDefault().add(fieldName); + } + + /** + * Assigns value to baseObjId. This will override the original value. Never call this method + * unless you know exactly what you are doing. + * @param baseObjId + */ + public void assignBaseObjId(int baseObjId) { + this.baseObjId = baseObjId; + } + + /** + * Disable developers to create instance of DataSupport directly. They + * should inherit this class with subclasses and operate on them. + */ + protected DataSupport() { + } + + /** + * Get the baseObjId of this model if it's useful for developers. It's for + * system use usually. Do not try to assign or modify it. + * + * @return The base object id. + */ + protected long getBaseObjId() { + return baseObjId; + } + + /** + * Get the full class name of self. + * + * @return The full class name of self. + */ + protected String getClassName() { + return getClass().getName(); + } + + /** + * Get the corresponding table name of current model. + * + * @return The corresponding table name of current model. + */ + protected String getTableName() { + return BaseUtility.changeCase(DBUtility.getTableNameByClassName(getClassName())); + } + + /** + * Get the list which holds all field names to update them into default + * value of model in database. + * + * @return List holds all the field names which need to be updated into + * default value of model. + */ + List getFieldsToSetToDefault() { + if (fieldsToSetToDefault == null) { + fieldsToSetToDefault = new ArrayList(); + } + return fieldsToSetToDefault; + } + + /** + * Add the id of an associated model into self model's associatedIdsWithFK + * map. The associated model has a foreign key column in the corresponding + * table. + * + * @param associatedTableName + * The table name of associated model. + * @param associatedId + * The {@link #baseObjId} of associated model after it is saved. + */ + void addAssociatedModelWithFK(String associatedTableName, long associatedId) { + Set associatedIdsWithFKSet = getAssociatedModelsMapWithFK().get(associatedTableName); + if (associatedIdsWithFKSet == null) { + associatedIdsWithFKSet = new HashSet(); + associatedIdsWithFKSet.add(associatedId); + associatedModelsMapWithFK.put(associatedTableName, associatedIdsWithFKSet); + } else { + associatedIdsWithFKSet.add(associatedId); + } + } + + /** + * Get the associated model's map of self model. It can be used for + * associations actions of CRUD. The key is the name of associated model. + * The value is a List of id of associated models. + * + * @return An associated model's map to update all the foreign key columns + * of associated models' table with self model's id. + */ + Map> getAssociatedModelsMapWithFK() { + if (associatedModelsMapWithFK == null) { + associatedModelsMapWithFK = new HashMap>(); + } + return associatedModelsMapWithFK; + } + + /** + * Add the id of an associated model into self model's associatedIdsM2M map. + * + * @param associatedModelName + * The name of associated model. + * @param associatedId + * The id of associated model. + */ + void addAssociatedModelForJoinTable(String associatedModelName, long associatedId) { + Set associatedIdsM2MSet = getAssociatedModelsMapForJoinTable().get( + associatedModelName); + if (associatedIdsM2MSet == null) { + associatedIdsM2MSet = new HashSet(); + associatedIdsM2MSet.add(associatedId); + associatedModelsMapForJoinTable.put(associatedModelName, associatedIdsM2MSet); + } else { + associatedIdsM2MSet.add(associatedId); + } + } + + /** + * Add an empty Set into {@link #associatedModelsMapForJoinTable} with + * associated model name as key. Might be useful when comes to update + * intermediate join table. + * + * @param associatedModelName + * The name of associated model. + */ + void addEmptyModelForJoinTable(String associatedModelName) { + Set associatedIdsM2MSet = getAssociatedModelsMapForJoinTable().get( + associatedModelName); + if (associatedIdsM2MSet == null) { + associatedIdsM2MSet = new HashSet(); + associatedModelsMapForJoinTable.put(associatedModelName, associatedIdsM2MSet); + } + } + + /** + * Get the associated model's map for intermediate join table. It is used to + * save values into intermediate join table. The key is the name of + * associated model. The value is the id of associated model. + * + * @return An associated model's map to save values into intermediate join + * table + */ + Map> getAssociatedModelsMapForJoinTable() { + if (associatedModelsMapForJoinTable == null) { + associatedModelsMapForJoinTable = new HashMap>(); + } + return associatedModelsMapForJoinTable; + } + + /** + * Add the id of an associated model into self model's association + * collection. The associated model doesn't have a foreign key column in the + * corresponding table. Instead self model has a foreign key column in the + * corresponding table. + * + * @param associatedTableName + * The simple class name of associated model. + * @param associatedId + * The {@link #baseObjId} of associated model after it is saved. + */ + void addAssociatedModelWithoutFK(String associatedTableName, long associatedId) { + getAssociatedModelsMapWithoutFK().put(associatedTableName, associatedId); + } + + /** + * Get the associated model's map of self model. It can be used for + * associations actions of CRUD. The key is the name of associated model's + * table. The value is the id of associated model. + * + * @return An associated model's map to save self model with foreign key. + */ + Map getAssociatedModelsMapWithoutFK() { + if (associatedModelsMapWithoutFK == null) { + associatedModelsMapWithoutFK = new HashMap(); + } + return associatedModelsMapWithoutFK; + } + + /** + * Add a foreign key name into the clear list. + * + * @param foreignKeyName + * The name of foreign key. + */ + void addFKNameToClearSelf(String foreignKeyName) { + List list = getListToClearSelfFK(); + if (!list.contains(foreignKeyName)) { + list.add(foreignKeyName); + } + } + + /** + * Get the foreign key name list to clear foreign key value in current + * model's table. + * + * @return The list of foreign key names to clear in current model's table. + */ + List getListToClearSelfFK() { + if (listToClearSelfFK == null) { + listToClearSelfFK = new ArrayList(); + } + return listToClearSelfFK; + } + + /** + * Add an associated table name into the list to clear. + * + * @param associatedTableName + * The name of associated table. + */ + void addAssociatedTableNameToClearFK(String associatedTableName) { + List list = getListToClearAssociatedFK(); + if (!list.contains(associatedTableName)) { + list.add(associatedTableName); + } + } + + /** + * Get the associated table names list which need to clear their foreign key + * values. + * + * @return The list with associated table names to clear foreign key values. + */ + List getListToClearAssociatedFK() { + if (listToClearAssociatedFK == null) { + listToClearAssociatedFK = new ArrayList(); + } + return listToClearAssociatedFK; + } + + /** + * Clear all the data for storing associated models' data. + */ + void clearAssociatedData() { + clearIdOfModelWithFK(); + clearIdOfModelWithoutFK(); + clearIdOfModelForJoinTable(); + clearFKNameList(); + } + + /** + * Clear all the data in {@link #associatedModelsMapWithFK}. + */ + private void clearIdOfModelWithFK() { + for (String associatedModelName : getAssociatedModelsMapWithFK().keySet()) { + associatedModelsMapWithFK.get(associatedModelName).clear(); + } + associatedModelsMapWithFK.clear(); + } + + /** + * Clear all the data in {@link #associatedModelsMapWithoutFK}. + */ + private void clearIdOfModelWithoutFK() { + getAssociatedModelsMapWithoutFK().clear(); + } + + /** + * Clear all the data in {@link #associatedModelsMapForJoinTable}. + */ + private void clearIdOfModelForJoinTable() { + for (String associatedModelName : getAssociatedModelsMapForJoinTable().keySet()) { + associatedModelsMapForJoinTable.get(associatedModelName).clear(); + } + associatedModelsMapForJoinTable.clear(); + } + + /** + * Clear all the data in {@link #listToClearSelfFK}. + */ + private void clearFKNameList() { + getListToClearSelfFK().clear(); + getListToClearAssociatedFK().clear(); + } + +} \ No newline at end of file diff --git a/YFDXJ/litepal/src/main/java/org/litepal/crud/DeleteHandler.java b/YFDXJ/litepal/src/main/java/org/litepal/crud/DeleteHandler.java new file mode 100644 index 0000000..246aecc --- /dev/null +++ b/YFDXJ/litepal/src/main/java/org/litepal/crud/DeleteHandler.java @@ -0,0 +1,398 @@ +/* + * Copyright (C) Tony Green, LitePal Framework Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.litepal.crud; + +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.litepal.crud.model.AssociationsInfo; +import org.litepal.exceptions.DataSupportException; +import org.litepal.util.BaseUtility; +import org.litepal.util.Const; +import org.litepal.util.DBUtility; + +import android.database.sqlite.SQLiteDatabase; + +/** + * This is a component under DataSupport. It deals with the deleting stuff as + * primary task. If deletes a saved model or delete a record with id, the + * cascade delete function would work. But considering efficiency, if deletes + * with deleteAll method, the referenced data in other tables will not be + * affected. Developers should remove those referenced data by their own. + * + * @author Tony Green + * @since 1.1 + */ +class DeleteHandler extends DataHandler { + + /** + * To store associated tables of current model's table. Only used while + * deleting by id and deleting all by model class. + */ + private List foreignKeyTableToDelete; + + /** + * Initialize {@link org.litepal.crud.DataHandler#mDatabase} for operating database. Do not + * allow to create instance of DeleteHandler out of CRUD package. + * + * @param db + * The instance of SQLiteDatabase. + */ + DeleteHandler(SQLiteDatabase db) { + mDatabase = db; + } + + /** + * The open interface for other classes in CRUD package to delete. Using + * baseObj to decide which record to delete. The baseObj must be saved + * already(using {@link org.litepal.crud.DataSupport#isSaved()} to test), or nothing will be + * deleted. This method can action cascade delete. When the record is + * deleted from database, all the referenced data such as foreign key value + * will be removed too. + * + * @param baseObj + * The record to delete. + * @return The number of rows affected. Including cascade delete rows. + */ + int onDelete(DataSupport baseObj) { + if (baseObj.isSaved()) { + List supportedGenericFields = getSupportedGenericFields(baseObj.getClassName()); + deleteGenericData(baseObj.getClass(), supportedGenericFields, baseObj.getBaseObjId()); + Collection associationInfos = analyzeAssociations(baseObj); + int rowsAffected = deleteCascade(baseObj); + rowsAffected += mDatabase.delete(baseObj.getTableName(), "id = " + + baseObj.getBaseObjId(), null); + clearAssociatedModelSaveState(baseObj, associationInfos); + return rowsAffected; + } + return 0; + } + + /** + * The open interface for other classes in CRUD package to delete. Using + * modelClass to decide which table to delete from, and id to decide a + * specific row. This method can action cascade delete. When the record is + * deleted from database, all the referenced data such as foreign key value + * will be removed too. + * + * @param modelClass + * Which table to delete from. + * @param id + * Which record to delete. + * @return The number of rows affected. Including cascade delete rows. + */ + int onDelete(Class modelClass, long id) { + List supportedGenericFields = getSupportedGenericFields(modelClass.getName()); + deleteGenericData(modelClass, supportedGenericFields, id); + analyzeAssociations(modelClass); + int rowsAffected = deleteCascade(modelClass, id); + rowsAffected += mDatabase.delete(getTableName(modelClass), + "id = " + id, null); + getForeignKeyTableToDelete().clear(); + return rowsAffected; + } + + /** + * The open interface for other classes in CRUD package to delete multiple + * rows. Using tableName to decide which table to delete from, and + * conditions representing the WHERE part of an SQL statement. + * + * @param tableName + * Which table to delete from. + * @param conditions + * A string array representing the WHERE part of an SQL + * statement. + * @return The number of rows affected. + */ + int onDeleteAll(String tableName, String... conditions) { + BaseUtility.checkConditionsCorrect(conditions); + if (conditions != null && conditions.length > 0) { + conditions[0] = DBUtility.convertWhereClauseToColumnName(conditions[0]); + } + return mDatabase.delete(tableName, getWhereClause(conditions), + getWhereArgs(conditions)); + } + + @SuppressWarnings("unchecked") + int onDeleteAll(Class modelClass, String... conditions) { + BaseUtility.checkConditionsCorrect(conditions); + if (conditions != null && conditions.length > 0) { + conditions[0] = DBUtility.convertWhereClauseToColumnName(conditions[0]); + } + List supportedGenericFields = getSupportedGenericFields(modelClass.getName()); + if (!supportedGenericFields.isEmpty()) { + List list = (List) DataSupport.select("id").where(conditions).find(modelClass); + if (list.size() > 0) { + long[] ids = new long[list.size()]; + for (int i = 0; i < ids.length; i++) { + DataSupport dataSupport = list.get(i); + ids[i] = dataSupport.getBaseObjId(); + } + deleteGenericData(modelClass, supportedGenericFields, ids); + } + } + analyzeAssociations(modelClass); + int rowsAffected = deleteAllCascade(modelClass, conditions); + rowsAffected += mDatabase.delete(getTableName(modelClass), getWhereClause(conditions), + getWhereArgs(conditions)); + getForeignKeyTableToDelete().clear(); + return rowsAffected; + } + + /** + * Analyze the associations of modelClass and store the associated tables. + * The associated tables might be used when deleting referenced data of a + * specified row. + * + * @param modelClass + * To get associations of this class. + */ + private void analyzeAssociations(Class modelClass) { + Collection associationInfos = getAssociationInfo(modelClass + .getName()); + for (AssociationsInfo associationInfo : associationInfos) { + String associatedTableName = DBUtility + .getTableNameByClassName(associationInfo + .getAssociatedClassName()); + if (associationInfo.getAssociationType() == Const.Model.MANY_TO_ONE + || associationInfo.getAssociationType() == Const.Model.ONE_TO_ONE) { + String classHoldsForeignKey = associationInfo + .getClassHoldsForeignKey(); + if (!modelClass.getName().equals(classHoldsForeignKey)) { + getForeignKeyTableToDelete().add(associatedTableName); + } + } else if (associationInfo.getAssociationType() == Const.Model.MANY_TO_MANY) { + String joinTableName = DBUtility.getIntermediateTableName( + getTableName(modelClass), associatedTableName); + joinTableName = BaseUtility.changeCase(joinTableName); + getForeignKeyTableToDelete().add(joinTableName); + } + } + } + + /** + * Use the analyzed result of associations to delete referenced data. So + * this method must be called after {@link #analyzeAssociations(Class)}. + * There're two parts of referenced data to delete. The foreign key rows in + * associated table and the foreign key rows in intermediate join table. + * + * @param modelClass + * To get the table name and combine with id as a foreign key + * column. + * @param id + * Delete all the rows which referenced with this id. + * @return The number of rows affected in associated tables and intermediate + * join tables. + */ + private int deleteCascade(Class modelClass, long id) { + int rowsAffected = 0; + for (String associatedTableName : getForeignKeyTableToDelete()) { + String fkName = getForeignKeyColumnName(getTableName(modelClass)); + rowsAffected += mDatabase.delete(associatedTableName, fkName + + " = " + id, null); + } + return rowsAffected; + } + + private int deleteAllCascade(Class modelClass, String... conditions) { + int rowsAffected = 0; + for (String associatedTableName : getForeignKeyTableToDelete()) { + String tableName = getTableName(modelClass); + String fkName = getForeignKeyColumnName(tableName); + StringBuilder whereClause = new StringBuilder(); + whereClause.append(fkName).append(" in (select id from "); + whereClause.append(tableName); + if (conditions != null && conditions.length > 0) { + whereClause.append(" where ").append(buildConditionString(conditions)); + } + whereClause.append(")"); + rowsAffected += mDatabase.delete(associatedTableName, + BaseUtility.changeCase(whereClause.toString()), null); + } + return rowsAffected; + } + + private String buildConditionString(String... conditions) { + int argCount = conditions.length - 1; + String whereClause = conditions[0]; + for (int i = 0; i < argCount; i++) { + whereClause = whereClause.replaceFirst("\\?", "'" + conditions[i+1] + "'"); + } + return whereClause; + } + + /** + * Analyze the associations of baseObj and store the result in it. The + * associations will be used when deleting referenced data of baseObj. + * + * @param baseObj + * The record to delete. + */ + private Collection analyzeAssociations(DataSupport baseObj) { + try { + Collection associationInfos = getAssociationInfo(baseObj + .getClassName()); + analyzeAssociatedModels(baseObj, associationInfos); + return associationInfos; + } catch (Exception e) { + throw new DataSupportException(e.getMessage(), e); + } + } + + /** + * Clear associated models' save state. After this method, the associated + * models of baseObj which data is removed from database will become + * unsaved. + * + * @param baseObj + * The record to delete. + * @param associationInfos + * The associated info. + */ + private void clearAssociatedModelSaveState(DataSupport baseObj, + Collection associationInfos) { + try { + for (AssociationsInfo associationInfo : associationInfos) { + if (associationInfo.getAssociationType() == Const.Model.MANY_TO_ONE + && !baseObj.getClassName().equals( + associationInfo.getClassHoldsForeignKey())) { + Collection associatedModels = getAssociatedModels( + baseObj, associationInfo); + if (associatedModels != null && !associatedModels.isEmpty()) { + for (DataSupport model : associatedModels) { + if (model != null) { + model.clearSavedState(); + } + } + } + } else if (associationInfo.getAssociationType() == Const.Model.ONE_TO_ONE) { + DataSupport model = getAssociatedModel(baseObj, + associationInfo); + if (model != null) { + model.clearSavedState(); + } + } + } + } catch (Exception e) { + throw new DataSupportException(e.getMessage(), e); + } + } + + /** + * Use the analyzed result of associations to delete referenced data. So + * this method must be called after + * {@link #analyzeAssociations(org.litepal.crud.DataSupport)}. There're two parts of + * referenced data to delete. The foreign key rows in associated tables and + * the foreign key rows in intermediate join tables. + * + * @param baseObj + * The record to delete. Now contains associations info. + * @return The number of rows affected in associated table and intermediate + * join table. + */ + private int deleteCascade(DataSupport baseObj) { + int rowsAffected; + rowsAffected = deleteAssociatedForeignKeyRows(baseObj); + rowsAffected += deleteAssociatedJoinTableRows(baseObj); + return rowsAffected; + } + + /** + * Delete the referenced rows of baseObj in associated tables(Many2One and + * One2One conditions). + * + * @param baseObj + * The record to delete. Now contains associations info. + * @return The number of rows affected in all associated tables. + */ + private int deleteAssociatedForeignKeyRows(DataSupport baseObj) { + int rowsAffected = 0; + Map> associatedModelMap = baseObj + .getAssociatedModelsMapWithFK(); + for (String associatedTableName : associatedModelMap.keySet()) { + String fkName = getForeignKeyColumnName(baseObj.getTableName()); + rowsAffected += mDatabase.delete(associatedTableName, fkName + + " = " + baseObj.getBaseObjId(), null); + } + return rowsAffected; + } + + /** + * Delete the referenced rows of baseObj in intermediate join + * tables(Many2Many condition). + * + * @param baseObj + * The record to delete. Now contains associations info. + * @return The number of rows affected in all intermediate join tables. + */ + private int deleteAssociatedJoinTableRows(DataSupport baseObj) { + int rowsAffected = 0; + Set associatedTableNames = baseObj + .getAssociatedModelsMapForJoinTable().keySet(); + for (String associatedTableName : associatedTableNames) { + String joinTableName = DBUtility.getIntermediateTableName( + baseObj.getTableName(), associatedTableName); + String fkName = getForeignKeyColumnName(baseObj.getTableName()); + rowsAffected += mDatabase.delete(joinTableName, fkName + " = " + + baseObj.getBaseObjId(), null); + } + return rowsAffected; + } + + /** + * Get all the associated tables of current model's table. Only used while + * deleting by id. + * + * @return All the associated tables of current model's table. + */ + private List getForeignKeyTableToDelete() { + if (foreignKeyTableToDelete == null) { + foreignKeyTableToDelete = new ArrayList(); + } + return foreignKeyTableToDelete; + } + + /** + * Delete the generic data in generic tables while main data was deleted. + * @param modelClass + * Used to get the generic table name and value id column. + * @param supportedGenericFields + * List of all supported generic fields. + * @param ids + * The id array of models. + */ + private void deleteGenericData(Class modelClass, List supportedGenericFields, long... ids) { + for (Field field : supportedGenericFields) { + String tableName = DBUtility.getGenericTableName(modelClass.getName(), field.getName()); + String genericValueIdColumnName = DBUtility.getGenericValueIdColumnName(modelClass.getName()); + StringBuilder whereClause = new StringBuilder(); + boolean needOr = false; + for (long id : ids) { + if (needOr) { + whereClause.append(" or "); + } + whereClause.append(genericValueIdColumnName).append(" = ").append(id); + needOr = true; + } + mDatabase.delete(tableName, whereClause.toString(), null); + } + } +} diff --git a/YFDXJ/litepal/src/main/java/org/litepal/crud/DynamicExecutor.java b/YFDXJ/litepal/src/main/java/org/litepal/crud/DynamicExecutor.java new file mode 100644 index 0000000..9f3e092 --- /dev/null +++ b/YFDXJ/litepal/src/main/java/org/litepal/crud/DynamicExecutor.java @@ -0,0 +1,147 @@ +/* + * Copyright (C) Tony Green, LitePal Framework Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.litepal.crud; + +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +import org.litepal.exceptions.DataSupportException; + +/** + * This provides a send method to allow calling method in dynamic way. (Just + * like Ruby, but not clean or easy as Ruby to use). + * + * @author Tony Green + * @since 1.1 + */ +class DynamicExecutor { + + /** + * Disable to create an instance of DynamicExecutor. + */ + private DynamicExecutor() { + } + + /** + * This method use java reflect API to execute method dynamically. Most + * importantly, it could access the methods with private modifier to break + * encapsulation. + * + * @param object + * The object to invoke method. + * @param methodName + * The method name to invoke. + * @param parameters + * The parameters. + * @param objectClass + * Use objectClass to find method to invoke. + * @param parameterTypes + * The parameter types. + * @return Returns the result of dynamically invoking method. + * @throws SecurityException + * @throws IllegalArgumentException + * @throws IllegalAccessException + * @throws java.lang.reflect.InvocationTargetException + */ + static Object send(Object object, String methodName, Object[] parameters, Class objectClass, + Class[] parameterTypes) throws SecurityException, IllegalArgumentException, + IllegalAccessException, InvocationTargetException { + try { + if (parameters == null) { + parameters = new Object[] {}; + } + if (parameterTypes == null) { + parameterTypes = new Class[] {}; + } + Method method = objectClass.getDeclaredMethod(methodName, parameterTypes); + method.setAccessible(true); + return method.invoke(object, parameters); + } catch (NoSuchMethodException e) { + throw new DataSupportException(DataSupportException.noSuchMethodException( + objectClass.getSimpleName(), methodName), e); + } + } + + static void set(Object object, String fieldName, Object value, Class objectClass) + throws SecurityException, IllegalArgumentException, IllegalAccessException, NoSuchFieldException { + Field objectField = objectClass.getDeclaredField(fieldName); + objectField.setAccessible(true); + objectField.set(object, value); + } + + /** + * This method use java reflect API to set field value dynamically. Most + * importantly, it could access fields with private modifier to break + * encapsulation. + * + * @param object + * The object to access. + * @param fieldName + * The field name to access. + * @param value + * Assign this value to field. + * @param objectClass + * The class of object. + * @throws SecurityException + * @throws IllegalArgumentException + * @throws IllegalAccessException + */ + static void setField(Object object, String fieldName, Object value, Class objectClass) + throws SecurityException, IllegalArgumentException, IllegalAccessException { + if (objectClass == DataSupport.class || objectClass == Object.class) { + throw new DataSupportException(DataSupportException.noSuchFieldExceptioin( + objectClass.getSimpleName(), fieldName)); + } + try { + set(object, fieldName, value, objectClass); + } catch (NoSuchFieldException e) { + setField(object, fieldName, value, objectClass.getSuperclass()); + } + } + + /** + * This method use java reflect API to get field value dynamically. Most + * importantly, it could access fields with private modifier to break + * encapsulation. + * + * @param object + * The object to access. + * @param fieldName + * The field name to access. + * @param objectClass + * The class of object. + * @throws SecurityException + * @throws IllegalArgumentException + * @throws IllegalAccessException + */ + static Object getField(Object object, String fieldName, Class objectClass) + throws IllegalArgumentException, IllegalAccessException { + if (objectClass == DataSupport.class || objectClass == Object.class) { + throw new DataSupportException(DataSupportException.noSuchFieldExceptioin( + objectClass.getSimpleName(), fieldName)); + } + try { + Field objectField = objectClass.getDeclaredField(fieldName); + objectField.setAccessible(true); + return objectField.get(object); + } catch (NoSuchFieldException e) { + return getField(object, fieldName, objectClass.getSuperclass()); + } + } + +} diff --git a/YFDXJ/litepal/src/main/java/org/litepal/crud/Many2ManyAnalyzer.java b/YFDXJ/litepal/src/main/java/org/litepal/crud/Many2ManyAnalyzer.java new file mode 100644 index 0000000..3fc7708 --- /dev/null +++ b/YFDXJ/litepal/src/main/java/org/litepal/crud/Many2ManyAnalyzer.java @@ -0,0 +1,220 @@ +/* + * Copyright (C) Tony Green, LitePal Framework Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.litepal.crud; + +import java.lang.reflect.InvocationTargetException; +import java.util.Collection; + +import org.litepal.LitePalBase; +import org.litepal.crud.model.AssociationsInfo; +import org.litepal.tablemanager.Connector; +import org.litepal.util.BaseUtility; +import org.litepal.util.DBUtility; + +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; + +/** + * Deals analysis work when comes to two models are associated with Many2Many + * associations. + * + * @author Tony Green + * @since 1.1 + */ +public class Many2ManyAnalyzer extends AssociationsAnalyzer { + + /** + * Analyzing the AssociationInfo. It will help baseObj assign the necessary + * values automatically. If the two associated models have bidirectional + * associations in class files but developer has only build unidirectional + * associations in models, it will force to build the bidirectional + * associations. Besides the + * {@link org.litepal.crud.DataSupport#addAssociatedModelForJoinTable(String, long)} will be called + * here to put right values into tables. + * + * @param baseObj + * The baseObj currently want to persist or update. + * @param associationInfo + * The associated info analyzed by + * {@link LitePalBase#getAssociationInfo(String)}. + * @throws SecurityException + * @throws IllegalArgumentException + * @throws NoSuchMethodException + * @throws IllegalAccessException + * @throws java.lang.reflect.InvocationTargetException + */ + void analyze(DataSupport baseObj, AssociationsInfo associationInfo) throws SecurityException, + IllegalArgumentException, NoSuchMethodException, IllegalAccessException, + InvocationTargetException { + Collection associatedModels = getAssociatedModels(baseObj, associationInfo); + declareAssociations(baseObj, associationInfo); + if (associatedModels != null) { + for (DataSupport associatedModel : associatedModels) { + Collection tempCollection = getReverseAssociatedModels( + associatedModel, associationInfo); + Collection reverseAssociatedModels = checkAssociatedModelCollection( + tempCollection, associationInfo.getAssociateSelfFromOtherModel()); + addNewModelForAssociatedModel(reverseAssociatedModels, baseObj); + setReverseAssociatedModels(associatedModel, associationInfo, + reverseAssociatedModels); + dealAssociatedModel(baseObj, associatedModel); + } + } + } + + /** + * This add an empty set for {@link org.litepal.crud.DataSupport#associatedModelsMapForJoinTable}. + * Might use for updating intermediate join table. + * + * @param baseObj + * The baseObj currently want to persist or update. + * @param associationInfo + * To get associated table name from. + */ + private void declareAssociations(DataSupport baseObj, AssociationsInfo associationInfo) { + baseObj.addEmptyModelForJoinTable(getAssociatedTableName(associationInfo)); + } + + /** + * Force to build bidirectional associations for the associated model. If it + * has already built, ignoring the rest process. + * + * @param associatedModelCollection + * The associated models collection of the associated model. Add + * self model into it if it doesn't contain self model yet. + * @param baseObj + * The baseObj currently want to persist or update. + */ + private void addNewModelForAssociatedModel(Collection associatedModelCollection, + DataSupport baseObj) { + if (!associatedModelCollection.contains(baseObj)) { + associatedModelCollection.add(baseObj); + } + } + + /** + * First of all the associated model need to be saved already, or nothing + * will be executed below. Then add the id of associated model into + * {@link org.litepal.crud.DataSupport#associatedModelsMapForJoinTable} for + * inserting value into intermediate join table after baseObj is saved. + * + * @param baseObj + * The baseObj currently want to persist or update. + * @param associatedModel + * The associated model of baseObj. + */ + private void dealAssociatedModel(DataSupport baseObj, DataSupport associatedModel) { + if (associatedModel.isSaved()) { + baseObj.addAssociatedModelForJoinTable(associatedModel.getTableName(), + associatedModel.getBaseObjId()); + } + } + + /** + * Get the associated table name by {@link org.litepal.crud.model.AssociationsInfo} after case + * changed. + * + * @param associationInfo + * To get the associated table name from. + * @return The name of associated table with changed case. + */ + private String getAssociatedTableName(AssociationsInfo associationInfo) { + return BaseUtility.changeCase(DBUtility.getTableNameByClassName(associationInfo + .getAssociatedClassName())); + } + + /** + * Check if the associations between self model and associated model is + * already saved into intermediate join table.
+ * Make sure baseObj and associatedModel are saved already, or the result + * might be wrong. + * + * @param baseObj + * The baseObj currently want to persist or update. + * @param associatedModel + * The associated model of baseObj. + * @return If the associations between them is saved into intermediate join + * table, return true. Otherwise return false. + */ + @SuppressWarnings("unused") + @Deprecated + private boolean isDataExists(DataSupport baseObj, DataSupport associatedModel) { + boolean exists = false; + SQLiteDatabase db = Connector.getDatabase(); + Cursor cursor = null; + try { + cursor = db.query(getJoinTableName(baseObj, associatedModel), null, + getSelection(baseObj, associatedModel), + getSelectionArgs(baseObj, associatedModel), null, null, null); + exists = cursor.getCount() > 0; + } catch (Exception e) { + e.printStackTrace(); + return true; + } finally { + cursor.close(); + } + return exists; + } + + /** + * Build the selection for querying the data in table. Column names are the + * table names with _id as suffix. + * + * @param baseObj + * The baseObj currently want to persist or update. + * @param associatedModel + * The associated model of baseObj. + * @return The selection clause for querying data. + */ + private String getSelection(DataSupport baseObj, DataSupport associatedModel) { + StringBuilder where = new StringBuilder(); + where.append(getForeignKeyColumnName(baseObj.getTableName())); + where.append(" = ? and "); + where.append(getForeignKeyColumnName(associatedModel.getTableName())); + where.append(" = ?"); + return where.toString(); + } + + /** + * Build the selection arguments to fill selection clause. + * + * @param baseObj + * The baseObj currently want to persist or update. + * @param associatedModel + * The associated model of baseObj. + * @return The selection arguments with the id of baseObj and + * associatedModel to fill. + */ + private String[] getSelectionArgs(DataSupport baseObj, DataSupport associatedModel) { + return new String[] { String.valueOf(baseObj.getBaseObjId()), + String.valueOf(associatedModel.getBaseObjId()) }; + } + + /** + * Get the intermediate join table name for self model and associated model. + * + * @param baseObj + * The baseObj currently want to persist or update. + * @param associatedModel + * The associated model of baseObj. + * @return The intermediate join table name. + */ + private String getJoinTableName(DataSupport baseObj, DataSupport associatedModel) { + return getIntermediateTableName(baseObj, associatedModel.getTableName()); + } + +} diff --git a/YFDXJ/litepal/src/main/java/org/litepal/crud/Many2OneAnalyzer.java b/YFDXJ/litepal/src/main/java/org/litepal/crud/Many2OneAnalyzer.java new file mode 100644 index 0000000..8b25170 --- /dev/null +++ b/YFDXJ/litepal/src/main/java/org/litepal/crud/Many2OneAnalyzer.java @@ -0,0 +1,175 @@ +/* + * Copyright (C) Tony Green, LitePal Framework Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.litepal.crud; + +import java.lang.reflect.InvocationTargetException; +import java.util.Collection; + +import org.litepal.LitePalBase; +import org.litepal.crud.model.AssociationsInfo; +import org.litepal.util.DBUtility; + +/** + * Deals analysis work when comes to two models are associated with Many2One + * associations. + * + * @author Tony Green + * @since 1.1 + */ +class Many2OneAnalyzer extends AssociationsAnalyzer { + + /** + * Analyzing the AssociationInfo. It will help baseObj assign the necessary + * values automatically. If the two associated models have bidirectional + * associations in class files but developer has only build unidirectional + * associations in models, it will force to build the bidirectional + * associations. Besides + * {@link org.litepal.crud.DataSupport#addAssociatedModelWithFK(String, long)} and + * {@link org.litepal.crud.DataSupport#addAssociatedModelWithoutFK(String, long)} will be + * called here to put right values into tables. + * + * @param baseObj + * The baseObj currently want to persist. + * @param associationInfo + * The associated info analyzed by + * {@link LitePalBase#getAssociationInfo(String)}. + * @throws SecurityException + * @throws IllegalArgumentException + * @throws NoSuchMethodException + * @throws IllegalAccessException + * @throws java.lang.reflect.InvocationTargetException + */ + void analyze(DataSupport baseObj, AssociationsInfo associationInfo) throws SecurityException, + IllegalArgumentException, NoSuchMethodException, IllegalAccessException, + InvocationTargetException { + if (baseObj.getClassName().equals(associationInfo.getClassHoldsForeignKey())) { + analyzeManySide(baseObj, associationInfo); + } else { + analyzeOneSide(baseObj, associationInfo); + } + } + + /** + * When it's on the M side. Get the associated model first, then use it to + * get the associated model collection on the O side. Initialize the + * collection by calling + * {@link #checkAssociatedModelCollection(java.util.Collection, java.lang.reflect.Field)} + * and calling + * {@link #dealAssociatedModelOnManySide(java.util.Collection, org.litepal.crud.DataSupport, org.litepal.crud.DataSupport)} + * to set foreign key. + * + * @param baseObj + * The baseObj currently want to persist or update. + * @param associationInfo + * The associated info analyzed by + * {@link LitePalBase#getAssociationInfo(String)}. + * @throws SecurityException + * @throws IllegalArgumentException + * @throws NoSuchMethodException + * @throws IllegalAccessException + * @throws java.lang.reflect.InvocationTargetException + */ + private void analyzeManySide(DataSupport baseObj, AssociationsInfo associationInfo) + throws SecurityException, IllegalArgumentException, NoSuchMethodException, + IllegalAccessException, InvocationTargetException { + DataSupport associatedModel = getAssociatedModel(baseObj, associationInfo); + if (associatedModel != null) { + // now it's m2o bidirectional association. + Collection tempCollection = getReverseAssociatedModels(associatedModel, + associationInfo); + Collection reverseAssociatedModels = checkAssociatedModelCollection( + tempCollection, associationInfo.getAssociateSelfFromOtherModel()); + setReverseAssociatedModels(associatedModel, associationInfo, reverseAssociatedModels); + dealAssociatedModelOnManySide(reverseAssociatedModels, baseObj, associatedModel); + } else { + mightClearFKValue(baseObj, associationInfo); + } + } + + /** + * When it's on the O side. Get the associated model collection first, then + * iterate all the associated models. Each associated model calls + * {@link #buildBidirectionalAssociations(org.litepal.crud.DataSupport, org.litepal.crud.DataSupport, org.litepal.crud.model.AssociationsInfo)} + * to build bidirectional association if they haven't built yet. Then calls + * {@link #dealAssociatedModelOnOneSide(org.litepal.crud.DataSupport, org.litepal.crud.DataSupport)} to set + * foreign key. + * + * @param baseObj + * The baseObj currently want to persist. + * @param associationInfo + * The associated info analyzed by + * {@link LitePalBase#getAssociationInfo(String)}. + * @throws SecurityException + * @throws IllegalArgumentException + * @throws NoSuchMethodException + * @throws IllegalAccessException + * @throws java.lang.reflect.InvocationTargetException + */ + private void analyzeOneSide(DataSupport baseObj, AssociationsInfo associationInfo) + throws SecurityException, IllegalArgumentException, NoSuchMethodException, + IllegalAccessException, InvocationTargetException { + Collection associatedModels = getAssociatedModels(baseObj, associationInfo); + if (associatedModels == null || associatedModels.isEmpty()) { + String tableName = DBUtility.getTableNameByClassName(associationInfo + .getAssociatedClassName()); + baseObj.addAssociatedTableNameToClearFK(tableName); + return; + } + for (DataSupport associatedModel : associatedModels) { + buildBidirectionalAssociations(baseObj, associatedModel, associationInfo); + dealAssociatedModelOnOneSide(baseObj, associatedModel); + } + } + + /** + * Check if the baseObj is already existed in the associatedModels + * collection. If not add baseObj into the collection. Then if the + * associated model is saved, add its' name and id to baseObj by calling + * {@link org.litepal.crud.DataSupport#addAssociatedModelWithFK(String, long)}. + * + * @param associatedModels + * The associated model collection. + * @param baseObj + * The baseObj currently want to persist. + * @param associatedModel + * The associated info analyzed by + * {@link LitePalBase#getAssociationInfo(String)}. + */ + private void dealAssociatedModelOnManySide(Collection associatedModels, + DataSupport baseObj, DataSupport associatedModel) { + if (!associatedModels.contains(baseObj)) { + associatedModels.add(baseObj); + } + if (associatedModel.isSaved()) { + baseObj.addAssociatedModelWithoutFK(associatedModel.getTableName(), + associatedModel.getBaseObjId()); + } + } + + /** + * Deals with associated model on one side. + * + * @param baseObj + * The baseObj currently want to persist. + * @param associatedModel + * The associated info analyzed by + * {@link LitePalBase#getAssociationInfo(String)}. + */ + private void dealAssociatedModelOnOneSide(DataSupport baseObj, DataSupport associatedModel) { + dealsAssociationsOnTheSideWithoutFK(baseObj, associatedModel); + } +} diff --git a/YFDXJ/litepal/src/main/java/org/litepal/crud/One2OneAnalyzer.java b/YFDXJ/litepal/src/main/java/org/litepal/crud/One2OneAnalyzer.java new file mode 100644 index 0000000..8f6e066 --- /dev/null +++ b/YFDXJ/litepal/src/main/java/org/litepal/crud/One2OneAnalyzer.java @@ -0,0 +1,127 @@ +/* + * Copyright (C) Tony Green, LitePal Framework Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.litepal.crud; + +import java.lang.reflect.InvocationTargetException; + +import org.litepal.LitePalBase; +import org.litepal.crud.model.AssociationsInfo; +import org.litepal.util.DBUtility; + +/** + * Deals analysis work when comes to two models are associated with One2One + * associations. + * + * @author Tony Green + * @since 1.1 + */ +public class One2OneAnalyzer extends AssociationsAnalyzer { + + /** + * Analyzing the AssociationInfo. It will help baseObj assign the necessary + * values automatically. If the two associated models have bidirectional + * associations in class files but developer has only build unidirectional + * associations in models, it will force to build the bidirectional + * associations. Besides + * {@link org.litepal.crud.DataSupport#addAssociatedModelWithFK(String, long)} and + * {@link org.litepal.crud.DataSupport#addAssociatedModelWithoutFK(String, long)} will be + * called here to put right values into tables. + * + * @param baseObj + * The baseObj currently want to persist or update. + * @param associationInfo + * The associated info analyzed by + * {@link LitePalBase#getAssociationInfo(String)}. + * @throws SecurityException + * @throws IllegalArgumentException + * @throws NoSuchMethodException + * @throws IllegalAccessException + * @throws java.lang.reflect.InvocationTargetException + */ + void analyze(DataSupport baseObj, AssociationsInfo associationInfo) throws SecurityException, + IllegalArgumentException, NoSuchMethodException, IllegalAccessException, + InvocationTargetException { + DataSupport associatedModel = getAssociatedModel(baseObj, associationInfo); + if (associatedModel != null) { + buildBidirectionalAssociations(baseObj, associatedModel, associationInfo); + dealAssociatedModel(baseObj, associatedModel, associationInfo); + } else { + String tableName = DBUtility.getTableNameByClassName(associationInfo + .getAssociatedClassName()); + baseObj.addAssociatedTableNameToClearFK(tableName); + } + } + + /** + * Check the association type. If it's bidirectional association, calls + * {@link #bidirectionalCondition(org.litepal.crud.DataSupport, org.litepal.crud.DataSupport)}. If it's + * unidirectional association, calls + * {@link #unidirectionalCondition(org.litepal.crud.DataSupport, org.litepal.crud.DataSupport)}. + * + * @param baseObj + * The baseObj currently want to persist. + * @param associatedModel + * The associated model of baseObj. + * @param associationInfo + * The associated info analyzed by + * {@link LitePalBase#getAssociationInfo(String)}. + */ + private void dealAssociatedModel(DataSupport baseObj, DataSupport associatedModel, + AssociationsInfo associationInfo) { + if (associationInfo.getAssociateSelfFromOtherModel() != null) { + bidirectionalCondition(baseObj, associatedModel); + } else { + unidirectionalCondition(baseObj, associatedModel); + } + } + + /** + * Deals bidirectional association condition. If associated model is saved, + * add its' name and id to baseObj by calling + * {@link org.litepal.crud.DataSupport#addAssociatedModelWithFK(String, long)}. Add its' name + * and id to baseObj by calling + * {@link org.litepal.crud.DataSupport#addAssociatedModelWithoutFK(String, long)}. + * + * @param baseObj + * The baseObj currently want to persist. + * @param associatedModel + * The associated model of baseObj. + */ + private void bidirectionalCondition(DataSupport baseObj, DataSupport associatedModel) { + if (associatedModel.isSaved()) { + // use to update associated table after saving + baseObj.addAssociatedModelWithFK(associatedModel.getTableName(), + associatedModel.getBaseObjId()); + // use to add foreign key value while saving + baseObj.addAssociatedModelWithoutFK(associatedModel.getTableName(), + associatedModel.getBaseObjId()); + } + } + + /** + * Deals unidirectional associations condition. + * + * @param baseObj + * The baseObj currently want to persist. + * @param associatedModel + * The associated model of baseObj. + */ + private void unidirectionalCondition(DataSupport baseObj, DataSupport associatedModel) { + dealsAssociationsOnTheSideWithoutFK(baseObj, associatedModel); + } + +} diff --git a/YFDXJ/litepal/src/main/java/org/litepal/crud/QueryHandler.java b/YFDXJ/litepal/src/main/java/org/litepal/crud/QueryHandler.java new file mode 100644 index 0000000..182ea31 --- /dev/null +++ b/YFDXJ/litepal/src/main/java/org/litepal/crud/QueryHandler.java @@ -0,0 +1,274 @@ +/* + * Copyright (C) Tony Green, LitePal Framework Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.litepal.crud; + +import java.util.List; + +import org.litepal.util.BaseUtility; +import org.litepal.util.DBUtility; + +import android.database.sqlite.SQLiteDatabase; + +/** + * This is a component under DataSupport. It deals with query stuff as primary + * task. + * + * @author Tony Green + * @since 1.1 + */ +class QueryHandler extends DataHandler { + + /** + * Initialize {@link org.litepal.crud.DataHandler#mDatabase} for operating database. Do not + * allow to create instance of QueryHandler out of CRUD package. + * + * @param db + * The instance of SQLiteDatabase. + */ + QueryHandler(SQLiteDatabase db) { + mDatabase = db; + } + + /** + * The open interface for other classes in CRUD package to query a record + * based on id. If the result set is empty, gives null back. + * + * @param modelClass + * Which table to query and the object type to return. + * @param id + * Which record to query. + * @param isEager + * True to load the associated models, false not. + * @return An object with found data from database, or null. + */ + T onFind(Class modelClass, long id, boolean isEager) { + List dataList = query(modelClass, null, "id = ?", new String[] { String.valueOf(id) }, + null, null, null, null, getForeignKeyAssociations(modelClass.getName(), isEager)); + if (dataList.size() > 0) { + return dataList.get(0); + } + return null; + } + + /** + * The open interface for other classes in CRUD package to query the first + * record in a table. If the result set is empty, gives null back. + * + * @param modelClass + * Which table to query and the object type to return. + * @param isEager + * True to load the associated models, false not. + * @return An object with data of first row, or null. + */ + T onFindFirst(Class modelClass, boolean isEager) { + List dataList = query(modelClass, null, null, null, null, null, "id", "1", + getForeignKeyAssociations(modelClass.getName(), isEager)); + if (dataList.size() > 0) { + return dataList.get(0); + } + return null; + } + + /** + * The open interface for other classes in CRUD package to query the last + * record in a table. If the result set is empty, gives null back. + * + * @param modelClass + * Which table to query and the object type to return. + * @param isEager + * True to load the associated models, false not. + * @return An object with data of last row, or null. + */ + T onFindLast(Class modelClass, boolean isEager) { + List dataList = query(modelClass, null, null, null, null, null, "id desc", "1", + getForeignKeyAssociations(modelClass.getName(), isEager)); + if (dataList.size() > 0) { + return dataList.get(0); + } + return null; + } + + /** + * The open interface for other classes in CRUD package to query multiple + * records by an id array. Pass no ids means query all rows. + * + * @param modelClass + * Which table to query and the object type to return as a list. + * @param isEager + * True to load the associated models, false not. + * @param ids + * Which records to query. Or do not pass it to find all records. + * @return An object list with found data from database, or an empty list. + */ + List onFindAll(Class modelClass, boolean isEager, long... ids) { + List dataList; + if (isAffectAllLines(ids)) { + dataList = query(modelClass, null, null, null, null, null, "id", null, + getForeignKeyAssociations(modelClass.getName(), isEager)); + } else { + dataList = query(modelClass, null, getWhereOfIdsWithOr(ids), null, null, null, "id", + null, getForeignKeyAssociations(modelClass.getName(), isEager)); + } + return dataList; + } + + /** + * The open interface for other classes in CRUD package to query multiple + * records by parameters. + * + * @param modelClass + * Which table to query and the object type to return as a list. + * @param columns + * A String array of which columns to return. Passing null will + * return all columns. + * @param conditions + * A filter declaring which rows to return, formatted as an SQL + * WHERE clause. Passing null will return all rows. + * @param orderBy + * How to order the rows, formatted as an SQL ORDER BY clause. + * Passing null will use the default sort order, which may be + * unordered. + * @param limit + * Limits the number of rows returned by the query, formatted as + * LIMIT clause. + * @param isEager + * True to load the associated models, false not. + * @return An object list with found data from database, or an empty list. + */ + List onFind(Class modelClass, String[] columns, String[] conditions, String orderBy, + String limit, boolean isEager) { + BaseUtility.checkConditionsCorrect(conditions); + if (conditions != null && conditions.length > 0) { + conditions[0] = DBUtility.convertWhereClauseToColumnName(conditions[0]); + } + orderBy = DBUtility.convertOrderByClauseToValidName(orderBy); + return query(modelClass, columns, getWhereClause(conditions), + getWhereArgs(conditions), null, null, orderBy, limit, + getForeignKeyAssociations(modelClass.getName(), isEager)); + } + + /** + * The open interface for other classes in CRUD package to Count the + * records. + * + * @param tableName + * Which table to query from. + * @param conditions + * A filter declaring which rows to return, formatted as an SQL + * WHERE clause. Passing null will return all rows. + * @return Count of the specified table. + */ + int onCount(String tableName, String[] conditions) { + BaseUtility.checkConditionsCorrect(conditions); + if (conditions != null && conditions.length > 0) { + conditions[0] = DBUtility.convertWhereClauseToColumnName(conditions[0]); + } + return mathQuery(tableName, new String[] { "count(1)" }, conditions, int.class); + } + + /** + * The open interface for other classes in CRUD package to calculate the + * average value on a given column. + * + * @param tableName + * Which table to query from. + * @param column + * The based on column to calculate. + * @param conditions + * A filter declaring which rows to return, formatted as an SQL + * WHERE clause. Passing null will return all rows. + * @return The average value on a given column. + */ + double onAverage(String tableName, String column, String[] conditions) { + BaseUtility.checkConditionsCorrect(conditions); + if (conditions != null && conditions.length > 0) { + conditions[0] = DBUtility.convertWhereClauseToColumnName(conditions[0]); + } + return mathQuery(tableName, new String[] { "avg(" + column + ")" }, conditions, + double.class); + } + + /** + * The open interface for other classes in CRUD package to calculate the + * maximum value on a given column. + * + * @param tableName + * Which table to query from. + * @param column + * The based on column to calculate. + * @param conditions + * A filter declaring which rows to return, formatted as an SQL + * WHERE clause. Passing null will return all rows. + * @param type + * The type of the based on column. + * @return The maximum value on a given column. + */ + T onMax(String tableName, String column, String[] conditions, Class type) { + BaseUtility.checkConditionsCorrect(conditions); + if (conditions != null && conditions.length > 0) { + conditions[0] = DBUtility.convertWhereClauseToColumnName(conditions[0]); + } + return mathQuery(tableName, new String[] { "max(" + column + ")" }, conditions, type); + } + + /** + * The open interface for other classes in CRUD package to calculate the + * minimum value on a given column. + * + * @param tableName + * Which table to query from. + * @param column + * The based on column to calculate. + * @param conditions + * A filter declaring which rows to return, formatted as an SQL + * WHERE clause. Passing null will return all rows. + * @param type + * The type of the based on column. + * @return The minimum value on a given column. + */ + T onMin(String tableName, String column, String[] conditions, Class type) { + BaseUtility.checkConditionsCorrect(conditions); + if (conditions != null && conditions.length > 0) { + conditions[0] = DBUtility.convertWhereClauseToColumnName(conditions[0]); + } + return mathQuery(tableName, new String[] { "min(" + column + ")" }, conditions, type); + } + + /** + * The open interface for other classes in CRUD package to calculate the sum + * of values on a given column. + * + * @param tableName + * Which table to query from. + * @param column + * The based on column to calculate. + * @param conditions + * A filter declaring which rows to return, formatted as an SQL + * WHERE clause. Passing null will return all rows. + * @param type + * The type of the based on column. + * @return The sum value on a given column. + */ + T onSum(String tableName, String column, String[] conditions, Class type) { + BaseUtility.checkConditionsCorrect(conditions); + if (conditions != null && conditions.length > 0) { + conditions[0] = DBUtility.convertWhereClauseToColumnName(conditions[0]); + } + return mathQuery(tableName, new String[] { "sum(" + column + ")" }, conditions, type); + } + +} \ No newline at end of file diff --git a/YFDXJ/litepal/src/main/java/org/litepal/crud/SaveHandler.java b/YFDXJ/litepal/src/main/java/org/litepal/crud/SaveHandler.java new file mode 100644 index 0000000..f77d528 --- /dev/null +++ b/YFDXJ/litepal/src/main/java/org/litepal/crud/SaveHandler.java @@ -0,0 +1,582 @@ +/* + * Copyright (C) Tony Green, LitePal Framework Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.litepal.crud; + +import android.content.ContentValues; +import android.database.sqlite.SQLiteDatabase; + +import org.litepal.crud.model.AssociationsInfo; +import org.litepal.exceptions.DataSupportException; +import org.litepal.util.DBUtility; + +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import static org.litepal.util.BaseUtility.changeCase; + +/** + * This is a component under DataSupport. It deals with the saving stuff as + * primary task. All the implementation based on the java reflection API and + * Android SQLiteDatabase API. It will persist the model class into table. If + * there're some associated models already persisted, it will build the + * associations in database automatically between the current model and the + * associated models. + * + * @author Tony Green + * @since 1.1 + */ +class SaveHandler extends DataHandler { + + /** + * indicates that associations can be ignored while saving. + */ + private boolean ignoreAssociations = false; + + private ContentValues values; + + /** + * Initialize {@link org.litepal.crud.DataHandler#mDatabase} for operating database. Do not + * allow to create instance of SaveHandler out of CRUD package. + * + * @param db + * The instance of SQLiteDatabase. + */ + SaveHandler(SQLiteDatabase db) { + values = new ContentValues(); + mDatabase = db; + } + + /** + * The open interface for other classes in CRUD package to save a model. It + * is called when a model class calls the save method. First of all, the + * passed in baseObj will be saved into database. Then LitePal will analyze + * the associations. If there're associated models detected, each associated + * model which is persisted will build association with current model in + * database. + * + * @param baseObj + * Current model to persist. + * @throws java.lang.reflect.InvocationTargetException + * @throws IllegalAccessException + * @throws NoSuchMethodException + * @throws IllegalArgumentException + * @throws SecurityException + */ + void onSave(DataSupport baseObj) throws SecurityException, IllegalArgumentException, + NoSuchMethodException, IllegalAccessException, InvocationTargetException { + String className = baseObj.getClassName(); + List supportedFields = getSupportedFields(className); + List supportedGenericFields = getSupportedGenericFields(className); + Collection associationInfos = getAssociationInfo(className); + if (!baseObj.isSaved()) { + if (!ignoreAssociations) { + analyzeAssociatedModels(baseObj, associationInfos); + } + doSaveAction(baseObj, supportedFields, supportedGenericFields); + if (!ignoreAssociations) { + analyzeAssociatedModels(baseObj, associationInfos); + } + } else { + if (!ignoreAssociations) { + analyzeAssociatedModels(baseObj, associationInfos); + } + doUpdateAction(baseObj, supportedFields, supportedGenericFields); + } + } + + /** + * The open interface for other classes in CRUD package to save a model. It + * is called when a model class calls the save method. This method will only + * save the baseObj into database without analyzing any association, so that + * the saving process will be faster. + * + * @param baseObj + * Current model to persist. + * @throws java.lang.reflect.InvocationTargetException + * @throws IllegalAccessException + * @throws NoSuchMethodException + * @throws IllegalArgumentException + * @throws SecurityException + */ + void onSaveFast(DataSupport baseObj) throws SecurityException, IllegalArgumentException, + NoSuchMethodException, IllegalAccessException, InvocationTargetException { + ignoreAssociations = true; + onSave(baseObj); + } + + /** + * The open interface for other classes in CRUD package to save a model + * collection. It is called when developer calls + * {@link org.litepal.crud.DataSupport#saveAll(java.util.Collection)}. Each model in the collection + * will be persisted. If there're associated models detected, each + * associated model which is persisted will build association with current + * model in database. + * + * @param collection + * Holds all models to persist. + * @throws java.lang.reflect.InvocationTargetException + * @throws IllegalAccessException + * @throws NoSuchMethodException + * @throws IllegalArgumentException + * @throws SecurityException + */ + void onSaveAll(Collection collection) throws SecurityException, + IllegalArgumentException, NoSuchMethodException, IllegalAccessException, + InvocationTargetException { + if (collection != null && collection.size() > 0) { + DataSupport[] array = collection.toArray(new DataSupport[0]); + DataSupport firstObj = array[0]; + String className = firstObj.getClassName(); + List supportedFields = getSupportedFields(className); + List supportedGenericFields = getSupportedGenericFields(className); + Collection associationInfos = getAssociationInfo(className); + for (DataSupport baseObj : array) { + if (!baseObj.isSaved()) { + analyzeAssociatedModels(baseObj, associationInfos); + doSaveAction(baseObj, supportedFields, supportedGenericFields); + analyzeAssociatedModels(baseObj, associationInfos); + } else { + analyzeAssociatedModels(baseObj, associationInfos); + doUpdateAction(baseObj, supportedFields, supportedGenericFields); + } + baseObj.clearAssociatedData(); + } + } + } + + /** + * Persisting model class into database happens here. But first + * {@link #beforeSave(org.litepal.crud.DataSupport, java.util.List, android.content.ContentValues)} will be called to + * put the values for ContentValues. When the model is saved, + * {@link #afterSave(org.litepal.crud.DataSupport, java.util.List, java.util.List, long)} will be called to do stuffs + * after model is saved. Note that SaveSupport won't help with id. Any + * developer who wants to set value to id will be ignored here. The value of + * id will be generated by SQLite automatically. + * + * @param baseObj + * Current model to persist. + * @param supportedFields + * List of all supported fields. + * @param supportedGenericFields + * List of all supported generic fields. + * @throws java.lang.reflect.InvocationTargetException + * @throws IllegalAccessException + * @throws NoSuchMethodException + * @throws IllegalArgumentException + * @throws SecurityException + */ + private void doSaveAction(DataSupport baseObj, List supportedFields, List supportedGenericFields) + throws SecurityException, IllegalArgumentException, NoSuchMethodException, + IllegalAccessException, InvocationTargetException { + values.clear(); + beforeSave(baseObj, supportedFields, values); + long id = saving(baseObj, values); + afterSave(baseObj, supportedFields, supportedGenericFields, id); + } + + /** + * Before the self model is saved, it will be analyzed first. Put all the + * data contained by the model into ContentValues, including the fields + * value and foreign key value. + * + * @param baseObj + * Current model to persist. + * @param supportedFields + * List of all supported fields. + * @param values + * To store data of current model for persisting. + * @throws java.lang.reflect.InvocationTargetException + * @throws IllegalAccessException + * @throws NoSuchMethodException + * @throws IllegalArgumentException + * @throws SecurityException + */ + private void beforeSave(DataSupport baseObj, List supportedFields, ContentValues values) + throws SecurityException, IllegalArgumentException, NoSuchMethodException, + IllegalAccessException, InvocationTargetException { + putFieldsValue(baseObj, supportedFields, values); + if (!ignoreAssociations) { + putForeignKeyValue(values, baseObj); + } + } + + /** + * Calling {@link android.database.sqlite.SQLiteDatabase#insert(String, String, android.content.ContentValues)} to + * persist the current model. + * + * @param baseObj + * Current model to persist. + * @param values + * To store data of current model for persisting. + * @return The row ID of the newly inserted row, or -1 if an error occurred. + */ + private long saving(DataSupport baseObj, ContentValues values) { + if (values.size() == 0) { + values.putNull("id"); + } + return mDatabase.insert(baseObj.getTableName(), null, values); + } + + /** + * After the model is saved, do the extra work that need to do. + * + * @param baseObj + * Current model that is persisted. + * @param supportedFields + * List of all supported fields. + * @param supportedGenericFields + * List of all supported generic fields. + * @param id + * The current model's id. + */ + private void afterSave(DataSupport baseObj, List supportedFields, + List supportedGenericFields, long id) throws IllegalAccessException, InvocationTargetException { + throwIfSaveFailed(id); + assignIdValue(baseObj, getIdField(supportedFields), id); + updateGenericTables(baseObj, supportedGenericFields, id); + if (!ignoreAssociations) { + updateAssociatedTableWithFK(baseObj); + insertIntermediateJoinTableValue(baseObj, false); + } + } + + /** + * When a model is associated with two different models. + * + * @param baseObj + * The class of base object. + * @param supportedFields + * List of all supported fields. + * @param supportedGenericFields + * List of all supported generic fields. + * @throws java.lang.reflect.InvocationTargetException + * @throws IllegalAccessException + * @throws NoSuchMethodException + * @throws IllegalArgumentException + * @throws SecurityException + */ + private void doUpdateAction(DataSupport baseObj, List supportedFields, List supportedGenericFields) + throws SecurityException, IllegalArgumentException, NoSuchMethodException, + IllegalAccessException, InvocationTargetException { + values.clear(); + beforeUpdate(baseObj, supportedFields, values); + updating(baseObj, values); + afterUpdate(baseObj, supportedGenericFields); + } + + /** + * Before updating model, it will be analyzed first. Put all the data + * contained by the model into ContentValues, including the fields value and + * foreign key value. If the associations between models has been removed. + * The foreign key value in database should be cleared too. + * + * @param baseObj + * Current model to update. + * @param supportedFields + * List of all supported fields. + * @param values + * To store data of current model for updating. + * @throws java.lang.reflect.InvocationTargetException + * @throws IllegalAccessException + * @throws NoSuchMethodException + * @throws IllegalArgumentException + * @throws SecurityException + */ + private void beforeUpdate(DataSupport baseObj, List supportedFields, ContentValues values) + throws SecurityException, IllegalArgumentException, NoSuchMethodException, + IllegalAccessException, InvocationTargetException { + putFieldsValue(baseObj, supportedFields, values); + if (!ignoreAssociations) { + putForeignKeyValue(values, baseObj); + for (String fkName : baseObj.getListToClearSelfFK()) { + values.putNull(fkName); + } + } + } + + /** + * Calling + * {@link android.database.sqlite.SQLiteDatabase#update(String, android.content.ContentValues, String, String[])} to + * update the current model. + * + * @param baseObj + * Current model to update. + * @param values + * To store data of current model for updating. + */ + private void updating(DataSupport baseObj, ContentValues values) { + mDatabase.update(baseObj.getTableName(), values, "id = ?", + new String[] { String.valueOf(baseObj.getBaseObjId()) }); + } + + /** + * After the model is updated, do the extra work that need to do. + * + * @param baseObj + * Current model that is updated. + * @param supportedGenericFields + * List of all supported generic fields. + */ + private void afterUpdate(DataSupport baseObj, List supportedGenericFields) + throws InvocationTargetException, IllegalAccessException { + updateGenericTables(baseObj, supportedGenericFields, baseObj.getBaseObjId()); + if (!ignoreAssociations) { + updateAssociatedTableWithFK(baseObj); + insertIntermediateJoinTableValue(baseObj, true); + clearFKValueInAssociatedTable(baseObj); + } + } + + /** + * Get the id field by the passed in field list. + * + * @param supportedFields + * The field list to find from. + * @return The id field. If not found one return null. + */ + private Field getIdField(List supportedFields) { + for (Field field : supportedFields) { + if (isIdColumn(field.getName())) { + return field; + } + } + return null; + } + + /** + * If the model saved failed, throw an exception. + * + * @param id + * The id returned by SQLite. -1 means saved failed. + */ + private void throwIfSaveFailed(long id) { + if (id == -1) { + throw new DataSupportException(DataSupportException.SAVE_FAILED); + } + } + + /** + * Assign the generated id value to the model. The + * {@link org.litepal.crud.DataSupport#baseObjId} will be assigned anyway. If the model has a + * field named id or _id, LitePal will assign it too. The + * {@link org.litepal.crud.DataSupport#baseObjId} will be used as identify of this model for + * system use. The id or _id field will help developers for their own + * purpose. + * + * @param baseObj + * Current model that is persisted. + * @param idField + * The field of id. + * @param id + * The value of id. + */ + private void assignIdValue(DataSupport baseObj, Field idField, long id) { + try { + giveBaseObjIdValue(baseObj, id); + if (idField != null) { + giveModelIdValue(baseObj, idField.getName(), idField.getType(), id); + } + } catch (Exception e) { + throw new DataSupportException(e.getMessage(), e); + } + } + + /** + * After saving a model, the id for this model will be returned. Assign this + * id to the model's id or _id field if it exists. + * + * @param baseObj + * The class of base object. + * @param idName + * The name of id. Only id or _id is valid. + * @param idType + * The type of id. Only int or long is valid. + * @param id + * The value of id. + * @throws SecurityException + * @throws NoSuchFieldException + * @throws IllegalArgumentException + * @throws IllegalAccessException + */ + private void giveModelIdValue(DataSupport baseObj, String idName, Class idType, long id) + throws SecurityException, NoSuchFieldException, IllegalArgumentException, + IllegalAccessException { + if (shouldGiveModelIdValue(idName, idType, id)) { + Object value; + if (idType == int.class || idType == Integer.class) { + value = (int) id; + } else if (idType == long.class || idType == Long.class) { + value = id; + } else { + throw new DataSupportException(DataSupportException.ID_TYPE_INVALID_EXCEPTION); + } + DynamicExecutor.setField(baseObj, idName, value, baseObj.getClass()); + } + } + + /** + * If the table for this model have a foreign key column, the value of + * foreign key id should be saved too. + * + * @param values + * The instance of ContentValues to put foreign key value. + */ + private void putForeignKeyValue(ContentValues values, DataSupport baseObj) { + Map associatedModelMap = baseObj.getAssociatedModelsMapWithoutFK(); + for (String associatedTableName : associatedModelMap.keySet()) { + values.put(getForeignKeyColumnName(associatedTableName), + associatedModelMap.get(associatedTableName)); + } + } + + /** + * Update the foreign keys in the associated model's table. + * + * @param baseObj + * Current model that is persisted. + */ + private void updateAssociatedTableWithFK(DataSupport baseObj) { + Map> associatedModelMap = baseObj.getAssociatedModelsMapWithFK(); + ContentValues values = new ContentValues(); + for (String associatedTableName : associatedModelMap.keySet()) { + values.clear(); + String fkName = getForeignKeyColumnName(baseObj.getTableName()); + values.put(fkName, baseObj.getBaseObjId()); + Set ids = associatedModelMap.get(associatedTableName); + if (ids != null && !ids.isEmpty()) { + mDatabase.update(associatedTableName, values, getWhereOfIdsWithOr(ids), null); + } + } + } + + /** + * When the associations breaks between current model and associated models, + * clear all the associated models' foreign key value if it exists. + * + * @param baseObj + * Current model that is persisted. + */ + private void clearFKValueInAssociatedTable(DataSupport baseObj) { + List associatedTableNames = baseObj.getListToClearAssociatedFK(); + for (String associatedTableName : associatedTableNames) { + String fkColumnName = getForeignKeyColumnName(baseObj.getTableName()); + ContentValues values = new ContentValues(); + values.putNull(fkColumnName); + String whereClause = fkColumnName + " = " + baseObj.getBaseObjId(); + mDatabase.update(associatedTableName, values, whereClause, null); + } + } + + /** + * Insert values into intermediate join tables for self model and associated + * models. + * + * @param baseObj + * Current model that is persisted. + * @param isUpdate + * The current action is update or not. + */ + private void insertIntermediateJoinTableValue(DataSupport baseObj, boolean isUpdate) { + Map> associatedIdsM2M = baseObj.getAssociatedModelsMapForJoinTable(); + ContentValues values = new ContentValues(); + for (String associatedTableName : associatedIdsM2M.keySet()) { + String joinTableName = getIntermediateTableName(baseObj, associatedTableName); + if (isUpdate) { + mDatabase.delete(joinTableName, getWhereForJoinTableToDelete(baseObj), + new String[] { String.valueOf(baseObj.getBaseObjId()) }); + } + Set associatedIdsM2MSet = associatedIdsM2M.get(associatedTableName); + for (long associatedId : associatedIdsM2MSet) { + values.clear(); + values.put(getForeignKeyColumnName(baseObj.getTableName()), baseObj.getBaseObjId()); + values.put(getForeignKeyColumnName(associatedTableName), associatedId); + mDatabase.insert(joinTableName, null, values); + } + } + } + + /** + * Get the where clause to delete intermediate join table's value for + * updating. + * + * @param baseObj + * Current model that is persisted. + * @return The where clause to execute. + */ + private String getWhereForJoinTableToDelete(DataSupport baseObj) { + StringBuilder where = new StringBuilder(); + where.append(getForeignKeyColumnName(baseObj.getTableName())); + where.append(" = ?"); + return where.toString(); + } + + /** + * Judge should assign id value to model's id field. The principle is that + * if id name is not null, id type is not null and id is greater than 0, + * then should assign id value to it. + * + * @param idName + * The name of id field. + * @param idType + * The type of id field. + * @param id + * The value of id. + * @return If id name is not null, id type is not null and id is greater + * than 0, return true. Otherwise return false. + */ + private boolean shouldGiveModelIdValue(String idName, Class idType, long id) { + return idName != null && idType != null && id > 0; + } + + /** + * Update the generic data in generic tables. Need to delete the related generic data before + * saving, because generic data has no id. + * @param baseObj + * Current model that is persisted. + *@param supportedGenericFields + * List of all supported generic fields. + * @param id + * The id of current model. + * @throws IllegalAccessException + * @throws InvocationTargetException + */ + private void updateGenericTables(DataSupport baseObj, List supportedGenericFields, + long id) throws IllegalAccessException, InvocationTargetException { + for (Field field : supportedGenericFields) { + field.setAccessible(true); + Collection collection = (Collection) field.get(baseObj); + if (collection != null) { + String tableName = DBUtility.getGenericTableName(baseObj.getClassName(), field.getName()); + String genericValueIdColumnName = DBUtility.getGenericValueIdColumnName(baseObj.getClassName()); + mDatabase.delete(tableName, genericValueIdColumnName + " = ?", new String[] {String.valueOf(id)}); + for (Object object : collection) { + ContentValues values = new ContentValues(); + values.put(genericValueIdColumnName, id); + Object[] parameters = new Object[] { changeCase(DBUtility.convertToValidColumnName(field.getName())), object }; + Class[] parameterTypes = new Class[] { String.class, getGenericTypeClass(field) }; + DynamicExecutor.send(values, "put", parameters, values.getClass(), parameterTypes); + mDatabase.insert(tableName, null, values); + } + } + } + } + +} diff --git a/YFDXJ/litepal/src/main/java/org/litepal/crud/UpdateHandler.java b/YFDXJ/litepal/src/main/java/org/litepal/crud/UpdateHandler.java new file mode 100644 index 0000000..469f088 --- /dev/null +++ b/YFDXJ/litepal/src/main/java/org/litepal/crud/UpdateHandler.java @@ -0,0 +1,403 @@ +/* + * Copyright (C) Tony Green, LitePal Framework Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.litepal.crud; + +import android.content.ContentValues; +import android.database.sqlite.SQLiteDatabase; +import android.os.Build; + +import org.litepal.crud.model.AssociationsInfo; +import org.litepal.exceptions.DataSupportException; +import org.litepal.util.BaseUtility; +import org.litepal.util.DBUtility; + +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import static org.litepal.util.BaseUtility.changeCase; + +/** + * This is a component under DataSupport. It deals with the updating stuff as + * primary task. Either updating specifying data with id or updating multiple + * lines with conditions can be done here. + * + * @author Tony Green + * @since 1.1 + */ +class UpdateHandler extends DataHandler { + + /** + * Initialize {@link org.litepal.crud.DataHandler#mDatabase} for operating database. Do not + * allow to create instance of UpdateHandler out of CRUD package. + * + * @param db + * The instance of SQLiteDatabase. + */ + UpdateHandler(SQLiteDatabase db) { + mDatabase = db; + } + + /** + * The open interface for other classes in CRUD package to update. Using + * baseObj to decide which table to update, and id to decide a specific row. + * The value that need to update is stored in baseObj. + * + * @param baseObj + * Which table to update by model instance. + * @param id + * Which record to update. + * @return The number of rows affected. + * @throws java.lang.reflect.InvocationTargetException + * @throws IllegalAccessException + * @throws NoSuchMethodException + * @throws IllegalArgumentException + * @throws SecurityException + */ + int onUpdate(DataSupport baseObj, long id) throws SecurityException, IllegalArgumentException, + NoSuchMethodException, IllegalAccessException, InvocationTargetException { + List supportedFields = getSupportedFields(baseObj.getClassName()); + List supportedGenericFields = getSupportedGenericFields(baseObj.getClassName()); + updateGenericTables(baseObj, supportedGenericFields, id); + ContentValues values = new ContentValues(); + putFieldsValue(baseObj, supportedFields, values); + putFieldsToDefaultValue(baseObj, values, id); + if (values.size() > 0) { + return mDatabase.update(baseObj.getTableName(), values, "id = " + id, null); + } + return 0; + } + + /** + * The open interface for other classes in CRUD package to update. Using + * modelClass to decide which table to update, and id to decide a specific + * row. The value that need to update is stored in ContentValues. + * + * @param modelClass + * Which table to update by class. + * @param id + * Which record to update. + * @param values + * A map from column names to new column values. null is a valid + * value that will be translated to NULL. + * @return The number of rows affected. + */ + int onUpdate(Class modelClass, long id, ContentValues values) { + if (values.size() > 0) { + convertContentValues(values); + return mDatabase.update(getTableName(modelClass), values, "id = " + id, null); + } + return 0; + } + + /** + * The open interface for other classes in CRUD package to update multiple + * rows. Using baseObj to decide which table to update, and conditions + * representing the WHERE part of an SQL statement. The value that need to + * update is stored in baseObj. + * + * @param baseObj + * Which table to update by model instance. + * @param conditions + * A string array representing the WHERE part of an SQL + * statement. + * @return The number of rows affected. + * @throws java.lang.reflect.InvocationTargetException + * @throws IllegalAccessException + * @throws NoSuchMethodException + * @throws IllegalArgumentException + * @throws SecurityException + */ + @SuppressWarnings("unchecked") + int onUpdateAll(DataSupport baseObj, String... conditions) throws SecurityException, + IllegalArgumentException, NoSuchMethodException, IllegalAccessException, + InvocationTargetException { + BaseUtility.checkConditionsCorrect(conditions); + if (conditions != null && conditions.length > 0) { + conditions[0] = DBUtility.convertWhereClauseToColumnName(conditions[0]); + } + List supportedFields = getSupportedFields(baseObj.getClassName()); + List supportedGenericFields = getSupportedGenericFields(baseObj.getClassName()); + long[] ids = null; + if (!supportedGenericFields.isEmpty()) { + List list = (List) DataSupport.select("id").where(conditions).find(baseObj.getClass()); + if (list.size() > 0) { + ids = new long[list.size()]; + for (int i = 0; i < ids.length; i++) { + DataSupport dataSupport = list.get(i); + ids[i] = dataSupport.getBaseObjId(); + } + updateGenericTables(baseObj, supportedGenericFields, ids); + } + } + ContentValues values = new ContentValues(); + putFieldsValue(baseObj, supportedFields, values); + putFieldsToDefaultValue(baseObj, values, ids); + return doUpdateAllAction(baseObj.getTableName(), values, conditions); + } + + /** + * The open interface for other classes in CRUD package to update multiple + * rows. Using tableName to decide which table to update, and conditions + * representing the WHERE part of an SQL statement. The value that need to + * update is stored in ContentValues. + * + * @param tableName + * Which table to update. + * @param conditions + * A string array representing the WHERE part of an SQL + * statement. + * @param values + * A map from column names to new column values. null is a valid + * value that will be translated to NULL. + * @return The number of rows affected. + */ + int onUpdateAll(String tableName, ContentValues values, String... conditions) { + BaseUtility.checkConditionsCorrect(conditions); + if (conditions != null && conditions.length > 0) { + conditions[0] = DBUtility.convertWhereClauseToColumnName(conditions[0]); + } + convertContentValues(values); + return doUpdateAllAction(tableName, values, conditions); + } + + /** + * Do the action for updating multiple rows. It will check the validity of + * conditions, then update rows in database. If the format of conditions is + * invalid, throw DataSupportException. + * + * @param tableName + * Which table to delete from. + * @param conditions + * A string array representing the WHERE part of an SQL + * statement. + * @param values + * A map from column names to new column values. null is a valid + * value that will be translated to NULL. + * @return The number of rows affected. + */ + private int doUpdateAllAction(String tableName, ContentValues values, String... conditions) { + BaseUtility.checkConditionsCorrect(conditions); + if (values.size() > 0) { + return mDatabase.update(tableName, values, getWhereClause(conditions), + getWhereArgs(conditions)); + } + return 0; + } + + /** + * Iterate all the fields that need to set to default value. If the field is + * id, ignore it. Or put the default value of field into ContentValues. + * + * @param baseObj + * Which table to update by model instance. + * @param values + * To store data of current model for persisting or updating. + * @param ids + * The id array of query result. + */ + private void putFieldsToDefaultValue(DataSupport baseObj, ContentValues values, long... ids) { + String fieldName = null; + try { + DataSupport emptyModel = getEmptyModel(baseObj); + Class emptyModelClass = emptyModel.getClass(); + for (String name : baseObj.getFieldsToSetToDefault()) { + if (!isIdColumn(name)) { + fieldName = name; + Field field = emptyModelClass.getDeclaredField(fieldName); + if (isCollection(field.getType())) { + if (ids != null && ids.length > 0) { + String genericTypeName = getGenericTypeName(field); + if (BaseUtility.isGenericTypeSupported(genericTypeName)) { + String tableName = DBUtility.getGenericTableName(baseObj.getClassName(), field.getName()); + String genericValueIdColumnName = DBUtility.getGenericValueIdColumnName(baseObj.getClassName()); + StringBuilder whereClause = new StringBuilder(); + boolean needOr = false; + for (long id : ids) { + if (needOr) { + whereClause.append(" or "); + } + whereClause.append(genericValueIdColumnName).append(" = ").append(id); + needOr = true; + } + mDatabase.delete(tableName, whereClause.toString(), null); + } + } + } else { + putContentValuesForUpdate(emptyModel, field, values); + } + } + } + } catch (NoSuchFieldException e) { + throw new DataSupportException(DataSupportException.noSuchFieldExceptioin( + baseObj.getClassName(), fieldName), e); + } catch (Exception e) { + throw new DataSupportException(e.getMessage(), e); + } + } + + /** + * Unused currently. + */ + @SuppressWarnings("unused") + private int doUpdateAssociations(DataSupport baseObj, long id, ContentValues values) { + int rowsAffected = 0; + analyzeAssociations(baseObj); + updateSelfTableForeignKey(baseObj, values); + rowsAffected += updateAssociatedTableForeignKey(baseObj, id); + return rowsAffected; + } + + /** + * Analyze the associations of baseObj and store the result in it. The + * associations will be used when deleting referenced data of baseObj. + * Unused currently. + * + * @param baseObj + * The record to update. + */ + private void analyzeAssociations(DataSupport baseObj) { + try { + Collection associationInfos = getAssociationInfo(baseObj + .getClassName()); + analyzeAssociatedModels(baseObj, associationInfos); + } catch (Exception e) { + throw new DataSupportException(e.getMessage(), e); + } + } + + /** + * Unused currently. + */ + private void updateSelfTableForeignKey(DataSupport baseObj, ContentValues values) { + Map associatedModelMap = baseObj.getAssociatedModelsMapWithoutFK(); + for (String associatedTable : associatedModelMap.keySet()) { + String fkName = getForeignKeyColumnName(associatedTable); + values.put(fkName, associatedModelMap.get(associatedTable)); + } + } + + /** + * Unused currently. + */ + private int updateAssociatedTableForeignKey(DataSupport baseObj, long id) { + Map> associatedModelMap = baseObj.getAssociatedModelsMapWithFK(); + ContentValues values = new ContentValues(); + for (String associatedTable : associatedModelMap.keySet()) { + values.clear(); + String fkName = getForeignKeyColumnName(baseObj.getTableName()); + values.put(fkName, id); + Set ids = associatedModelMap.get(associatedTable); + if (ids != null && !ids.isEmpty()) { + return mDatabase.update(associatedTable, values, getWhereOfIdsWithOr(ids), null); + } + } + return 0; + } + + /** + * Update the generic data in generic tables. Need to delete the related generic data before + * saving, because generic data has no id. If generic collection is null or empty, the operation + * will be abort. Clear generic collection data while updating should use {@link DataSupport#setToDefault(String)} + * method. + * @param baseObj + * Current model that is persisted. + *@param supportedGenericFields + * List of all supported generic fields. + * @param ids + * The id array of models. + * @throws IllegalAccessException + * @throws InvocationTargetException + */ + private void updateGenericTables(DataSupport baseObj, List supportedGenericFields, + long... ids) throws IllegalAccessException, InvocationTargetException { + if (ids != null && ids.length > 0) { + for (Field field : supportedGenericFields) { + field.setAccessible(true); + Collection collection = (Collection) field.get(baseObj); + if (collection != null && !collection.isEmpty()) { + String tableName = DBUtility.getGenericTableName(baseObj.getClassName(), field.getName()); + String genericValueIdColumnName = DBUtility.getGenericValueIdColumnName(baseObj.getClassName()); + for (long id : ids) { + mDatabase.delete(tableName, genericValueIdColumnName + " = ?", new String[] {String.valueOf(id)}); + for (Object object : collection) { + ContentValues values = new ContentValues(); + values.put(genericValueIdColumnName, id); + Object[] parameters = new Object[] { DBUtility.convertToValidColumnName(changeCase(field.getName())), object }; + Class[] parameterTypes = new Class[] { String.class, getGenericTypeClass(field) }; + DynamicExecutor.send(values, "put", parameters, values.getClass(), parameterTypes); + mDatabase.insert(tableName, null, values); + } + } + } + } + } + } + + /** + * The keys in ContentValues may be put as valid in Java but invalid in database. So convert + * them into valid keys. + * @param values + * A map from column names to new column values. null is a valid + * value that will be translated to NULL. + */ + private void convertContentValues(ContentValues values) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { + Map valuesToConvert = new HashMap(); + for (String key : values.keySet()) { + if (DBUtility.isFieldNameConflictWithSQLiteKeywords(key)) { + Object object = values.get(key); + valuesToConvert.put(key, object); + } + } + for (String key : valuesToConvert.keySet()) { + String convertedKey = DBUtility.convertToValidColumnName(key); + Object object = values.get(key); + values.remove(key); + if (object == null) { + values.putNull(convertedKey); + } else { + String className = object.getClass().getName(); + if ("java.lang.Byte".equals(className)) { + values.put(convertedKey, (Byte) object); + } else if ("[B".equals(className)) { + values.put(convertedKey, (byte[]) object); + } else if ("java.lang.Boolean".equals(className)) { + values.put(convertedKey, (Boolean) object); + } else if ("java.lang.String".equals(className)) { + values.put(convertedKey, (String) object); + } else if ("java.lang.Float".equals(className)) { + values.put(convertedKey, (Float) object); + } else if ("java.lang.Long".equals(className)) { + values.put(convertedKey, (Long) object); + } else if ("java.lang.Integer".equals(className)) { + values.put(convertedKey, (Integer) object); + } else if ("java.lang.Short".equals(className)) { + values.put(convertedKey, (Short) object); + } else if ("java.lang.Double".equals(className)) { + values.put(convertedKey, (Double) object); + } + } + } + } + } + +} \ No newline at end of file diff --git a/YFDXJ/litepal/src/main/java/org/litepal/crud/async/AsyncExecutor.java b/YFDXJ/litepal/src/main/java/org/litepal/crud/async/AsyncExecutor.java new file mode 100644 index 0000000..f70cb7b --- /dev/null +++ b/YFDXJ/litepal/src/main/java/org/litepal/crud/async/AsyncExecutor.java @@ -0,0 +1,49 @@ +/* + * Copyright (C) Tony Green, LitePal Framework Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.litepal.crud.async; + +/** + * A simple async executor to run tasks in background thread. + * + * @author Tony Green + * @since 2017/2/22 + */ +public abstract class AsyncExecutor { + + /** + * Task that pending to run. + */ + private Runnable pendingTask; + + /** + * Submit a task for pending executing. + * @param task + * The task with specific database operation. + */ + public void submit(Runnable task) { + pendingTask = task; + } + + /** + * Run the pending task in background thread. + */ + void execute() { + if (pendingTask != null) { + new Thread(pendingTask).start(); + } + } + +} \ No newline at end of file diff --git a/YFDXJ/litepal/src/main/java/org/litepal/crud/async/AverageExecutor.java b/YFDXJ/litepal/src/main/java/org/litepal/crud/async/AverageExecutor.java new file mode 100644 index 0000000..1907233 --- /dev/null +++ b/YFDXJ/litepal/src/main/java/org/litepal/crud/async/AverageExecutor.java @@ -0,0 +1,44 @@ +/* + * Copyright (C) Tony Green, LitePal Framework Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.litepal.crud.async; + +import org.litepal.crud.callback.AverageCallback; + +/** + * Executor for average query in background. + * + * @author Tony Green + * @since 2017/2/22 + */ +public class AverageExecutor extends AsyncExecutor { + + private AverageCallback cb; + + /** + * Register a callback listener and async task will start executing right away. + * @param callback + * Callback for average query in background. + */ + public void listen(AverageCallback callback) { + cb = callback; + execute(); + } + + public AverageCallback getListener() { + return cb; + } + +} \ No newline at end of file diff --git a/YFDXJ/litepal/src/main/java/org/litepal/crud/async/CountExecutor.java b/YFDXJ/litepal/src/main/java/org/litepal/crud/async/CountExecutor.java new file mode 100644 index 0000000..08d9fc4 --- /dev/null +++ b/YFDXJ/litepal/src/main/java/org/litepal/crud/async/CountExecutor.java @@ -0,0 +1,44 @@ +/* + * Copyright (C) Tony Green, LitePal Framework Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.litepal.crud.async; + +import org.litepal.crud.callback.CountCallback; + +/** + * Executor for count query in background. + * + * @author Tony Green + * @since 2017/2/22 + */ +public class CountExecutor extends AsyncExecutor { + + private CountCallback cb; + + /** + * Register a callback listener and async task will start executing right away. + * @param callback + * Callback for count query in background. + */ + public void listen(CountCallback callback) { + cb = callback; + execute(); + } + + public CountCallback getListener() { + return cb; + } + +} \ No newline at end of file diff --git a/YFDXJ/litepal/src/main/java/org/litepal/crud/async/FindExecutor.java b/YFDXJ/litepal/src/main/java/org/litepal/crud/async/FindExecutor.java new file mode 100644 index 0000000..c17da21 --- /dev/null +++ b/YFDXJ/litepal/src/main/java/org/litepal/crud/async/FindExecutor.java @@ -0,0 +1,44 @@ +/* + * Copyright (C) Tony Green, LitePal Framework Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.litepal.crud.async; + +import org.litepal.crud.callback.FindCallback; + +/** + * Executor for find record in background. + * + * @author Tony Green + * @since 2017/2/22 + */ +public class FindExecutor extends AsyncExecutor { + + private FindCallback cb; + + /** + * Register a callback listener and async task will start executing right away. + * @param callback + * Callback for find record in background. + */ + public void listen(FindCallback callback) { + cb = callback; + execute(); + } + + public FindCallback getListener() { + return cb; + } + +} diff --git a/YFDXJ/litepal/src/main/java/org/litepal/crud/async/FindMultiExecutor.java b/YFDXJ/litepal/src/main/java/org/litepal/crud/async/FindMultiExecutor.java new file mode 100644 index 0000000..a39e062 --- /dev/null +++ b/YFDXJ/litepal/src/main/java/org/litepal/crud/async/FindMultiExecutor.java @@ -0,0 +1,44 @@ +/* + * Copyright (C) Tony Green, LitePal Framework Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.litepal.crud.async; + +import org.litepal.crud.callback.FindMultiCallback; + +/** + * Executor for find multiple records in background. + * + * @author Tony Green + * @since 2017/2/22 + */ +public class FindMultiExecutor extends AsyncExecutor { + + private FindMultiCallback cb; + + /** + * Register a callback listener and async task will start executing right away. + * @param callback + * Callback for find multiple records in background. + */ + public void listen(FindMultiCallback callback) { + cb = callback; + execute(); + } + + public FindMultiCallback getListener() { + return cb; + } + +} diff --git a/YFDXJ/litepal/src/main/java/org/litepal/crud/async/SaveExecutor.java b/YFDXJ/litepal/src/main/java/org/litepal/crud/async/SaveExecutor.java new file mode 100644 index 0000000..e88be99 --- /dev/null +++ b/YFDXJ/litepal/src/main/java/org/litepal/crud/async/SaveExecutor.java @@ -0,0 +1,44 @@ +/* + * Copyright (C) Tony Green, LitePal Framework Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.litepal.crud.async; + +import org.litepal.crud.callback.SaveCallback; + +/** + * Executor for save records in background. + * + * @author Tony Green + * @since 2017/2/22 + */ +public class SaveExecutor extends AsyncExecutor { + + private SaveCallback cb; + + /** + * Register a callback listener and async task will start executing right away. + * @param callback + * Callback for save records in background. + */ + public void listen(SaveCallback callback) { + cb = callback; + execute(); + } + + public SaveCallback getListener() { + return cb; + } + +} \ No newline at end of file diff --git a/YFDXJ/litepal/src/main/java/org/litepal/crud/async/UpdateOrDeleteExecutor.java b/YFDXJ/litepal/src/main/java/org/litepal/crud/async/UpdateOrDeleteExecutor.java new file mode 100644 index 0000000..986390e --- /dev/null +++ b/YFDXJ/litepal/src/main/java/org/litepal/crud/async/UpdateOrDeleteExecutor.java @@ -0,0 +1,44 @@ +/* + * Copyright (C) Tony Green, LitePal Framework Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.litepal.crud.async; + +import org.litepal.crud.callback.UpdateOrDeleteCallback; + +/** + * Executor for update or delete records in background. + * + * @author Tony Green + * @since 2017/2/22 + */ +public class UpdateOrDeleteExecutor extends AsyncExecutor { + + private UpdateOrDeleteCallback cb; + + /** + * Register a callback listener and async task will start executing right away. + * @param callback + * Callback for update or delete records in background. + */ + public void listen(UpdateOrDeleteCallback callback) { + cb = callback; + execute(); + } + + public UpdateOrDeleteCallback getListener() { + return cb; + } + +} \ No newline at end of file diff --git a/YFDXJ/litepal/src/main/java/org/litepal/crud/callback/AverageCallback.java b/YFDXJ/litepal/src/main/java/org/litepal/crud/callback/AverageCallback.java new file mode 100644 index 0000000..1f9a91d --- /dev/null +++ b/YFDXJ/litepal/src/main/java/org/litepal/crud/callback/AverageCallback.java @@ -0,0 +1,28 @@ +/* + * Copyright (C) Tony Green, LitePal Framework Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.litepal.crud.callback; + +/** + * Callback for average query in background. + * + * @author Tony Green + * @since 2017/2/22 + */ +public interface AverageCallback { + + void onFinish(double average); + +} \ No newline at end of file diff --git a/YFDXJ/litepal/src/main/java/org/litepal/crud/callback/CountCallback.java b/YFDXJ/litepal/src/main/java/org/litepal/crud/callback/CountCallback.java new file mode 100644 index 0000000..e5b592d --- /dev/null +++ b/YFDXJ/litepal/src/main/java/org/litepal/crud/callback/CountCallback.java @@ -0,0 +1,28 @@ +/* + * Copyright (C) Tony Green, LitePal Framework Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.litepal.crud.callback; + +/** + * Callback for count query in background. + * + * @author Tony Green + * @since 2017/2/22 + */ +public interface CountCallback { + + void onFinish(int count); + +} \ No newline at end of file diff --git a/YFDXJ/litepal/src/main/java/org/litepal/crud/callback/FindCallback.java b/YFDXJ/litepal/src/main/java/org/litepal/crud/callback/FindCallback.java new file mode 100644 index 0000000..5a02242 --- /dev/null +++ b/YFDXJ/litepal/src/main/java/org/litepal/crud/callback/FindCallback.java @@ -0,0 +1,28 @@ +/* + * Copyright (C) Tony Green, LitePal Framework Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.litepal.crud.callback; + +/** + * Callback for find record in background. + * + * @author Tony Green + * @since 2017/2/22 + */ +public interface FindCallback { + + void onFinish(T t); + +} diff --git a/YFDXJ/litepal/src/main/java/org/litepal/crud/callback/FindMultiCallback.java b/YFDXJ/litepal/src/main/java/org/litepal/crud/callback/FindMultiCallback.java new file mode 100644 index 0000000..ab2d25d --- /dev/null +++ b/YFDXJ/litepal/src/main/java/org/litepal/crud/callback/FindMultiCallback.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) Tony Green, LitePal Framework Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.litepal.crud.callback; + +import java.util.List; + +/** + * Callback for find multiple records in background. + * + * @author Tony Green + * @since 2017/2/22 + */ +public interface FindMultiCallback { + + void onFinish(List t); + +} \ No newline at end of file diff --git a/YFDXJ/litepal/src/main/java/org/litepal/crud/callback/SaveCallback.java b/YFDXJ/litepal/src/main/java/org/litepal/crud/callback/SaveCallback.java new file mode 100644 index 0000000..562ed0b --- /dev/null +++ b/YFDXJ/litepal/src/main/java/org/litepal/crud/callback/SaveCallback.java @@ -0,0 +1,28 @@ +/* + * Copyright (C) Tony Green, LitePal Framework Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.litepal.crud.callback; + +/** + * Callback for save records in background. + * + * @author Tony Green + * @since 2017/2/22 + */ +public interface SaveCallback { + + void onFinish(boolean success); + +} \ No newline at end of file diff --git a/YFDXJ/litepal/src/main/java/org/litepal/crud/callback/UpdateOrDeleteCallback.java b/YFDXJ/litepal/src/main/java/org/litepal/crud/callback/UpdateOrDeleteCallback.java new file mode 100644 index 0000000..2e37218 --- /dev/null +++ b/YFDXJ/litepal/src/main/java/org/litepal/crud/callback/UpdateOrDeleteCallback.java @@ -0,0 +1,28 @@ +/* + * Copyright (C) Tony Green, LitePal Framework Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.litepal.crud.callback; + +/** + * Callback for update or delete records in background. + * + * @author Tony Green + * @since 2017/2/22 + */ +public interface UpdateOrDeleteCallback { + + void onFinish(int rowsAffected); + +} \ No newline at end of file diff --git a/YFDXJ/litepal/src/main/java/org/litepal/crud/model/AssociationsInfo.java b/YFDXJ/litepal/src/main/java/org/litepal/crud/model/AssociationsInfo.java new file mode 100644 index 0000000..4e709bd --- /dev/null +++ b/YFDXJ/litepal/src/main/java/org/litepal/crud/model/AssociationsInfo.java @@ -0,0 +1,208 @@ +/* + * Copyright (C) Tony Green, LitePal Framework Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.litepal.crud.model; + +import java.lang.reflect.Field; + +/** + * This model holds necessary information when comes to analyze and handle + * associated models of self model. + * + * @author Tony Green + * @since 1.1 + */ +public class AssociationsInfo { + + /** + * The class name of self class. + */ + private String selfClassName; + + /** + * The class name of the class which associated with self class. + */ + private String associatedClassName; + + /** + * The class which holds foreign key. + */ + private String classHoldsForeignKey; + + /** + * The field of self class to declare has association with other class. + */ + private Field associateOtherModelFromSelf; + + /** + * The field of the associated class to declare has association with self + * class. + */ + private Field associateSelfFromOtherModel; + + /** + * The association type, including Many2One One2One Many2Many. + */ + private int associationType; + + /** + * Get the class name of self class. + * + * @return The self class name. + */ + public String getSelfClassName() { + return selfClassName; + } + + /** + * Set the class name of self class. + * + * @param selfClassName + * The self class name to set. + */ + public void setSelfClassName(String selfClassName) { + this.selfClassName = selfClassName; + } + + /** + * Get the class name of the class which associated with self class. + * + * @return The associated class name. + */ + public String getAssociatedClassName() { + return associatedClassName; + } + + /** + * Set the class name of the class which associated with self class. + * + * @param associatedClassName + * The associated class name to set. + */ + public void setAssociatedClassName(String associatedClassName) { + this.associatedClassName = associatedClassName; + } + + /** + * Get the class which holds foreign key. + * + * @return The class which holds foreign key. + */ + public String getClassHoldsForeignKey() { + return classHoldsForeignKey; + } + + /** + * Set the class which holds foreign key. + * + * @param classHoldsForeignKey + * The class which holds foreign key to set. + */ + public void setClassHoldsForeignKey(String classHoldsForeignKey) { + this.classHoldsForeignKey = classHoldsForeignKey; + } + + /** + * Get the field of self class which declares has association with other + * class. + * + * @return The field which declares has association with other class. + */ + public Field getAssociateOtherModelFromSelf() { + return associateOtherModelFromSelf; + } + + /** + * Set the field of self class which declares has association with other + * class. + * + * @param associateOtherModelFromSelf + * The field which declares has association with other class to + * set. + */ + public void setAssociateOtherModelFromSelf(Field associateOtherModelFromSelf) { + this.associateOtherModelFromSelf = associateOtherModelFromSelf; + } + + /** + * Get the field of the associated class which declares has association with + * self class. + * + * @return The field of the associated class which declares has association + * with self class. + */ + public Field getAssociateSelfFromOtherModel() { + return associateSelfFromOtherModel; + } + + /** + * Set the field of the associated class which declares has association with + * self class. + * + * @param associateSelfFromOtherModel + * The field of the associated class which declares has + * association with self class to set. + */ + public void setAssociateSelfFromOtherModel(Field associateSelfFromOtherModel) { + this.associateSelfFromOtherModel = associateSelfFromOtherModel; + } + + /** + * Get the association type. + * + * @return The association type. + */ + public int getAssociationType() { + return associationType; + } + + /** + * Set the association type. + * + * @param associationType + * Within ONE_TO_ONE, MANY_TO_ONE and MANY_TO_MANY constants. + */ + public void setAssociationType(int associationType) { + this.associationType = associationType; + } + + /** + * Override equals method to make sure that if two associated classes in the + * association info model are same ignoring sides, they are same association + * info model. + */ + @Override + public boolean equals(Object o) { + if (o instanceof AssociationsInfo) { + AssociationsInfo other = (AssociationsInfo) o; + if (o != null && other != null) { + if (other.getAssociationType() == associationType + && other.getClassHoldsForeignKey().equals(classHoldsForeignKey)) { + if (other.getSelfClassName().equals(selfClassName) + && other.getAssociatedClassName().equals(associatedClassName)) { + return true; + } + if (other.getSelfClassName().equals(associatedClassName) + && other.getAssociatedClassName().equals(selfClassName)) { + return true; + } + } + } + } + return false; + } + +} diff --git a/YFDXJ/litepal/src/main/java/org/litepal/exceptions/DataSupportException.java b/YFDXJ/litepal/src/main/java/org/litepal/exceptions/DataSupportException.java new file mode 100644 index 0000000..c9954fb --- /dev/null +++ b/YFDXJ/litepal/src/main/java/org/litepal/exceptions/DataSupportException.java @@ -0,0 +1,113 @@ +/* + * Copyright (C) Tony Green, LitePal Framework Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.litepal.exceptions; + +/** + * When LitePal deals with CRUD actions of DataSupport, it may throw + * DataSupportException. + * + * @author Tony Green + * @since 1.1 + */ +public class DataSupportException extends RuntimeException { + private static final long serialVersionUID = 1L; + + /** + * Thrown when models have invalid type for id fields. Only int or long is + * supported. + */ + public static final String ID_TYPE_INVALID_EXCEPTION = "id type is not supported. Only int or long is acceptable for id"; + + /** + * Thrown when the saving model is not an instance of DataSupport. + */ + public static final String MODEL_IS_NOT_AN_INSTANCE_OF_DATA_SUPPORT = " should be inherited from DataSupport"; + + /** + * Thrown when developers use wrong field to declare many2one or many2many + * associations. + */ + public static final String WRONG_FIELD_TYPE_FOR_ASSOCIATIONS = "The field to declare many2one or many2many associations should be List or Set."; + + /** + * Thrown when fail to save a model. + */ + public static final String SAVE_FAILED = "Save current model failed."; + + /** + * Thrown when there is no default constructor in model class to update. + */ + public static final String INSTANTIATION_EXCEPTION = " needs a default constructor."; + + /** + * Thrown when the parameters in conditions are incorrect. + */ + public static final String UPDATE_CONDITIONS_EXCEPTION = "The parameters in conditions are incorrect."; + + /** + * Constructor of DataSupportException. + * + * @param errorMessage + * the description of this exception. + */ + public DataSupportException(String errorMessage) { + super(errorMessage); + } + + /** + * Constructor of DataSupportException. + * + * @param errorMessage + * the description of this exception. + * @param throwable + * the cause of this exception. + */ + public DataSupportException(String errorMessage, Throwable throwable) { + super(errorMessage, throwable); + } + + /** + * Thrown when the VM notices that a program tries to reference, on a class + * or object, a method that does not exist. + * + * @param className + * The class name. + * @param methodName + * The method name which is missing. + * @return Exception message. + */ + public static String noSuchMethodException(String className, String methodName) { + return "The " + methodName + " method in " + className + + " class is necessary which does not exist."; + } + + /** + * Thrown when the virtual machine notices that a program tries to + * reference, on a class or object, a field that does not exist. + * + * @param className + * The class name. + * @param fieldName + * The field name which is missing. + * @return Exception message. + */ + public static String noSuchFieldExceptioin(String className, String fieldName) { + return "The " + fieldName + " field in " + className + + " class is necessary which does not exist."; + } + +} diff --git a/YFDXJ/litepal/src/main/java/org/litepal/exceptions/DatabaseGenerateException.java b/YFDXJ/litepal/src/main/java/org/litepal/exceptions/DatabaseGenerateException.java new file mode 100644 index 0000000..623c5fd --- /dev/null +++ b/YFDXJ/litepal/src/main/java/org/litepal/exceptions/DatabaseGenerateException.java @@ -0,0 +1,64 @@ +/* + * Copyright (C) Tony Green, LitePal Framework Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.litepal.exceptions; + +/** + * When LitePal generate or update tables, it may throw DatabaseGenerateException. + * + * @author Tony Green + * @since 1.0 + */ +public class DatabaseGenerateException extends RuntimeException { + private static final long serialVersionUID = 1L; + + /** + * Can not find a class with the passing class name. + */ + public static final String CLASS_NOT_FOUND = "can not find a class named "; + + /** + * An exception that indicates there was an error with SQL parsing or + * execution. + */ + public static final String SQL_ERROR = "An exception that indicates there was an error with SQL parsing or execution. "; + + /** + * SQL syntax error when executing generation job. + */ + public static final String SQL_SYNTAX_ERROR = "SQL syntax error happens while executing "; + + /** + * Can not find a table with the passing table name when executing SQL. + */ + public static final String TABLE_DOES_NOT_EXIST_WHEN_EXECUTING = "Table doesn't exist when executing "; + + /** + * Can not find a table with the passing table name. + */ + public static final String TABLE_DOES_NOT_EXIST = "Table doesn't exist with the name of "; + + /** + * Constructor of DatabaseGenerateException. + * + * @param errorMessage + * the description of this DatabaseGenerateException. + */ + public DatabaseGenerateException(String errorMessage) { + super(errorMessage); + } + +} diff --git a/YFDXJ/litepal/src/main/java/org/litepal/exceptions/GlobalException.java b/YFDXJ/litepal/src/main/java/org/litepal/exceptions/GlobalException.java new file mode 100644 index 0000000..bf2b35d --- /dev/null +++ b/YFDXJ/litepal/src/main/java/org/litepal/exceptions/GlobalException.java @@ -0,0 +1,42 @@ +/* + * Copyright (C) Tony Green, LitePal Framework Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.litepal.exceptions; + +/** + * This is where all the global exceptions declared of LitePal. + * + * @author Tony Green + * @since 1.0 + */ +public class GlobalException extends RuntimeException { + private static final long serialVersionUID = 1L; + + /** + * Application context is null. + */ + public static final String APPLICATION_CONTEXT_IS_NULL = "Application context is null. Maybe you neither configured your application name with \"org.litepal.LitePalApplication\" in your AndroidManifest.xml, nor called LitePal.initialize(Context) method."; + + /** + * Constructor of GlobalException. + * + * @param errorMessage + * the description of this exception. + */ + public GlobalException(String errorMessage) { + super(errorMessage); + } +} diff --git a/YFDXJ/litepal/src/main/java/org/litepal/exceptions/InvalidAttributesException.java b/YFDXJ/litepal/src/main/java/org/litepal/exceptions/InvalidAttributesException.java new file mode 100644 index 0000000..d013679 --- /dev/null +++ b/YFDXJ/litepal/src/main/java/org/litepal/exceptions/InvalidAttributesException.java @@ -0,0 +1,60 @@ +/* + * Copyright (C) Tony Green, LitePal Framework Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.litepal.exceptions; + +/** + * Reading the attributes in the litepal.xml file. Check all the attributes if they + * are valid value. If anyone of them is not under rules, throw + * InvalidAttributesException exception. + * + * @author Tony Green + * @since 1.0 + */ +public class InvalidAttributesException extends RuntimeException { + private static final long serialVersionUID = 1L; + + /** + * dbname is empty or not defined in litepal.xml file. + */ + public static final String DBNAME_IS_EMPTY_OR_NOT_DEFINED = "dbname is empty or not defined in litepal.xml file, or your litepal.xml file is missing."; + + /** + * the version of database can not be less than 1. + */ + public static final String VERSION_OF_DATABASE_LESS_THAN_ONE = "the version of database can not be less than 1"; + + /** + * the version in litepal.xml is earlier than the current version. + */ + public static final String VERSION_IS_EARLIER_THAN_CURRENT = "the version in litepal.xml is earlier than the current version"; + + /** + * There's an invalid value in cases mark. Only keep, upper, lower allowed. + */ + public static final String CASES_VALUE_IS_INVALID = " is an invalid value for "; + + /** + * Constructor of InvalidAttributesException. + * + * @param errorMessage + * the description of this InvalidAttributesException. + */ + public InvalidAttributesException(String errorMessage) { + super(errorMessage); + } + +} diff --git a/YFDXJ/litepal/src/main/java/org/litepal/exceptions/ParseConfigurationFileException.java b/YFDXJ/litepal/src/main/java/org/litepal/exceptions/ParseConfigurationFileException.java new file mode 100644 index 0000000..34bb7b2 --- /dev/null +++ b/YFDXJ/litepal/src/main/java/org/litepal/exceptions/ParseConfigurationFileException.java @@ -0,0 +1,59 @@ +/* + * Copyright (C) Tony Green, LitePal Framework Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.litepal.exceptions; + +/** + * Using SAX way parsing XML by default. If any problem happens when parsing the + * litepal.xml file, ParseConfigurationFileException will be thrown. + * + * @author Tony Green + * @since 1.0 + */ +public class ParseConfigurationFileException extends RuntimeException { + private static final long serialVersionUID = 1L; + + /** + * can not find the litepal.xml file by the given id. + */ + public static final String CAN_NOT_FIND_LITEPAL_FILE = "litepal.xml file is missing. Please ensure it under assets folder."; + + /** + * can not parse the litepal.xml, check if it's in correct format. + */ + public static final String FILE_FORMAT_IS_NOT_CORRECT = "can not parse the litepal.xml, check if it's in correct format"; + + /** + * parse configuration is failed. + */ + public static final String PARSE_CONFIG_FAILED = "parse configuration is failed"; + + /** + * IO exception happened. + */ + public static final String IO_EXCEPTION = "IO exception happened"; + + /** + * Constructor of ParseConfigurationFileException. + * + * @param errorMessage + * the description of this exception. + */ + public ParseConfigurationFileException(String errorMessage) { + super(errorMessage); + } + +} diff --git a/YFDXJ/litepal/src/main/java/org/litepal/model/Table_Schema.java b/YFDXJ/litepal/src/main/java/org/litepal/model/Table_Schema.java new file mode 100644 index 0000000..de75dc7 --- /dev/null +++ b/YFDXJ/litepal/src/main/java/org/litepal/model/Table_Schema.java @@ -0,0 +1,80 @@ +/* + * Copyright (C) Tony Green, LitePal Framework Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.litepal.model; + +/** + * This class is a constant model class. It stores each table name of the + * corresponding model classes added by developers. When synchronizing the + * tables with the model classes, the table names are necessary to decide which + * tables are created by the developers and which are created by system. The + * values in table_schema are totally generated automatically, do not try to + * change any value in it or the synchronization might be failed. + * + * @author Tony Green + * @since 1.0 + */ +public class Table_Schema { + + /** + * The table name of model class. + */ + private String name; + + /** + * Type of the table. 0 normal table, 1 intermediate join table. + */ + private int type; + + /** + * Get the table name. + * + * @return The table name. + */ + public String getName() { + return name; + } + + /** + * Set the table name. + * + * @param name + * The table name. + */ + public void setName(String name) { + this.name = name; + } + + /** + * Get the table type. + * + * @return The table type. + */ + public int getType() { + return type; + } + + /** + * Set the table type. + * + * @param type + * The table type to set + */ + public void setType(int type) { + this.type = type; + } + +} diff --git a/YFDXJ/litepal/src/main/java/org/litepal/parser/LitePalAttr.java b/YFDXJ/litepal/src/main/java/org/litepal/parser/LitePalAttr.java new file mode 100644 index 0000000..6c331d3 --- /dev/null +++ b/YFDXJ/litepal/src/main/java/org/litepal/parser/LitePalAttr.java @@ -0,0 +1,217 @@ +/* + * Copyright (C) Tony Green, LitePal Framework Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.litepal.parser; + +import java.util.ArrayList; +import java.util.List; + +import org.litepal.exceptions.InvalidAttributesException; +import org.litepal.util.BaseUtility; +import org.litepal.util.Const; +import org.litepal.util.SharedUtil; + +import android.text.TextUtils; + +/** + * The object model for the litepal.xml file. Once database connection happens, + * LitePal will try to analysis the litepal.xml, and read all the attribute into + * the LitePalAttr model for further usage. + * + * @author Tony Green + * @since 1.0 + */ +public final class LitePalAttr { + + /** + * Static litePalAttr object. + */ + private static LitePalAttr litePalAttr; + + /** + * The version of database. + */ + private int version; + + /** + * The name of database. + */ + private String dbName; + + /** + * The case of table names and column names and SQL. + */ + private String cases; + + /** + * Define where the .db file should be. Option values: internal external. + */ + private String storage; + + /** + * All the model classes that want to map in the database. Each class should + * be given the full name including package name. + */ + private List classNames; + + /** + * Extra name as key for saving the database version in SharedUtil. + */ + private String extraKeyName; + + /** + * Do not allow new a LitePalAttr object. Makes it a singleton class. + */ + private LitePalAttr() { + } + + /** + * Provide a way to get the instance of LitePalAttr. + * @return the singleton instance of LitePalAttr + */ + public static LitePalAttr getInstance() { + if (litePalAttr == null) { + synchronized (LitePalAttr.class) { + if (litePalAttr == null) { + litePalAttr = new LitePalAttr(); + if (BaseUtility.isLitePalXMLExists()) { + LitePalConfig config = LitePalParser.parseLitePalConfiguration(); + litePalAttr.setDbName(config.getDbName()); + litePalAttr.setVersion(config.getVersion()); + litePalAttr.setClassNames(config.getClassNames()); + litePalAttr.setCases(config.getCases()); + litePalAttr.setStorage(config.getStorage()); + } + } + } + } + return litePalAttr; + } + + /** + * Clear the instance of LitePalAttr. + */ + public static void clearInstance() { + litePalAttr = null; + } + + public int getVersion() { + return version; + } + + public void setVersion(int version) { + this.version = version; + } + + public String getDbName() { + return dbName; + } + + public void setDbName(String dbName) { + this.dbName = dbName; + } + + public String getStorage() { + return storage; + } + + public void setStorage(String storage) { + this.storage = storage; + } + + public String getExtraKeyName() { + return extraKeyName; + } + + public void setExtraKeyName(String extraKeyName) { + this.extraKeyName = extraKeyName; + } + + /** + * Get the class name list. Always add table_schema as a value. + * + * @return The class name list. + */ + public List getClassNames() { + if (classNames == null) { + classNames = new ArrayList(); + classNames.add("org.litepal.model.Table_Schema"); + } else if (classNames.isEmpty()) { + classNames.add("org.litepal.model.Table_Schema"); + } + return classNames; + } + + /** + * Add a class name into the current mapping model list. + * + * @param className + * Full package class name. + */ + public void addClassName(String className) { + getClassNames().add(className); + } + + public void setClassNames(List classNames) { + this.classNames = classNames; + } + + public String getCases() { + return cases; + } + + public void setCases(String cases) { + this.cases = cases; + } + + /** + * Before application build the connection with database, check the fields + * in LitePalAttr. If all of the fields are passed, the connection will be + * continued.If anyone of them doesn't pass, an exception will be thrown. + * If dbname is undefined, or version is less than 1, or version is earlier + * than current version, throw InvalidAttributesException. + * + * @throws org.litepal.exceptions.InvalidAttributesException + */ + public void checkSelfValid() { + if (TextUtils.isEmpty(dbName)) { + throw new InvalidAttributesException( + InvalidAttributesException.DBNAME_IS_EMPTY_OR_NOT_DEFINED); + } + if (!dbName.endsWith(Const.Config.DB_NAME_SUFFIX)) { + dbName = dbName + Const.Config.DB_NAME_SUFFIX; + } + if (version < 1) { + throw new InvalidAttributesException( + InvalidAttributesException.VERSION_OF_DATABASE_LESS_THAN_ONE); + } + if (version < SharedUtil.getLastVersion(extraKeyName)) { + throw new InvalidAttributesException( + InvalidAttributesException.VERSION_IS_EARLIER_THAN_CURRENT); + } + if (TextUtils.isEmpty(cases)) { + cases = Const.Config.CASES_LOWER; + } else { + if (!cases.equals(Const.Config.CASES_UPPER) + && !cases.equals(Const.Config.CASES_LOWER) + && !cases.equals(Const.Config.CASES_KEEP)) { + throw new InvalidAttributesException(cases + + InvalidAttributesException.CASES_VALUE_IS_INVALID); + } + } + } + +} diff --git a/YFDXJ/litepal/src/main/java/org/litepal/parser/LitePalConfig.java b/YFDXJ/litepal/src/main/java/org/litepal/parser/LitePalConfig.java new file mode 100644 index 0000000..f2f47a1 --- /dev/null +++ b/YFDXJ/litepal/src/main/java/org/litepal/parser/LitePalConfig.java @@ -0,0 +1,116 @@ +/* + * Copyright (C) Tony Green, LitePal Framework Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.litepal.parser; + +import java.util.ArrayList; +import java.util.List; + +/** + * Model for litepal.xml configuration file. + * @author guolin + * @since 2016/11/10 + */ +public class LitePalConfig { + + /** + * The version of database. + */ + private int version; + + /** + * The name of database. + */ + private String dbName; + + /** + * The case of table names and column names and SQL. + */ + private String cases; + + /** + * Define where the .db file should be. Option values: internal external. + */ + private String storage; + + /** + * All the model classes that want to map in the database. Each class should + * be given the full name including package name. + */ + private List classNames; + + public int getVersion() { + return version; + } + + public void setVersion(int version) { + this.version = version; + } + + public String getDbName() { + return dbName; + } + + public void setDbName(String dbName) { + this.dbName = dbName; + } + + public String getStorage() { + return storage; + } + + public void setStorage(String storage) { + this.storage = storage; + } + + /** + * Get the class name list. Always add table_schema as a value. + * + * @return The class name list. + */ + public List getClassNames() { + if (classNames == null) { + classNames = new ArrayList(); + classNames.add("org.litepal.model.Table_Schema"); + } else if (classNames.isEmpty()) { + classNames.add("org.litepal.model.Table_Schema"); + } + return classNames; + } + + /** + * Add a class name into the current mapping model list. + * + * @param className + * Full package class name. + */ + public void addClassName(String className) { + getClassNames().add(className); + } + + public void setClassNames(List classNames) { + this.classNames = classNames; + } + + public String getCases() { + return cases; + } + + public void setCases(String cases) { + this.cases = cases; + } + +} diff --git a/YFDXJ/litepal/src/main/java/org/litepal/parser/LitePalContentHandler.java b/YFDXJ/litepal/src/main/java/org/litepal/parser/LitePalContentHandler.java new file mode 100644 index 0000000..2f567b9 --- /dev/null +++ b/YFDXJ/litepal/src/main/java/org/litepal/parser/LitePalContentHandler.java @@ -0,0 +1,109 @@ +/* + * Copyright (C) Tony Green, LitePal Framework Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.litepal.parser; + +import org.xml.sax.Attributes; +import org.xml.sax.SAXException; +import org.xml.sax.helpers.DefaultHandler; + +/** + * This is the content handler for analysis the litepal.xml file by SAXParser, + * and temporarily the only correct way to generate LitePalAttr model with + * values. + * + * @author Tony Green + * @since 1.0 + */ +public class LitePalContentHandler extends DefaultHandler { + + /** + * Store the parsed value of litepal.xml. + */ + private LitePalAttr litePalAttr; + + /** + * Characters in the characters tag. Decide to not use this method + * temporarily. Use value attribute instead. + */ + @Override + public void characters(char[] ch, int start, int length) throws SAXException { + } + + /** + * End of the document. Doing nothing temporarily. + */ + @Override + public void endDocument() throws SAXException { + } + + /** + * End of the element. Doing nothing temporarily. + */ + @Override + public void endElement(String uri, String localName, String qName) throws SAXException { + } + + /** + * Start of the document. Generate a LitePalAttr model at the same time. + */ + @Override + public void startDocument() throws SAXException { + litePalAttr = LitePalAttr.getInstance(); + litePalAttr.getClassNames().clear(); + } + + /** + * Start analysis the litepal.xml file. Set all the parsed value into the + * LitePalAttr model. + */ + @Override + public void startElement(String uri, String localName, String qName, Attributes attributes) + throws SAXException { + if (LitePalParser.NODE_DB_NAME.equalsIgnoreCase(localName)) { + for (int i = 0; i < attributes.getLength(); i++) { + if (LitePalParser.ATTR_VALUE.equalsIgnoreCase(attributes.getLocalName(i))) { + litePalAttr.setDbName(attributes.getValue(i).trim()); + } + } + } else if (LitePalParser.NODE_VERSION.equalsIgnoreCase(localName)) { + for (int i = 0; i < attributes.getLength(); i++) { + if (LitePalParser.ATTR_VALUE.equalsIgnoreCase(attributes.getLocalName(i))) { + litePalAttr.setVersion(Integer.parseInt(attributes.getValue(i).trim())); + } + } + } else if (LitePalParser.NODE_MAPPING.equalsIgnoreCase(localName)) { + for (int i = 0; i < attributes.getLength(); i++) { + if (LitePalParser.ATTR_CLASS.equalsIgnoreCase(attributes.getLocalName(i))) { + litePalAttr.addClassName(attributes.getValue(i).trim()); + } + } + } else if (LitePalParser.NODE_CASES.equalsIgnoreCase(localName)) { + for (int i = 0; i < attributes.getLength(); i++) { + if (LitePalParser.ATTR_VALUE.equalsIgnoreCase(attributes.getLocalName(i))) { + litePalAttr.setCases(attributes.getValue(i).trim()); + } + } + } else if (LitePalParser.NODE_STORAGE.equalsIgnoreCase(localName)) { + for (int i = 0; i < attributes.getLength(); i++) { + if (LitePalParser.ATTR_VALUE.equalsIgnoreCase(attributes.getLocalName(i))) { + litePalAttr.setStorage(attributes.getValue(i).trim()); + } + } + } + } + +} diff --git a/YFDXJ/litepal/src/main/java/org/litepal/parser/LitePalParser.java b/YFDXJ/litepal/src/main/java/org/litepal/parser/LitePalParser.java new file mode 100644 index 0000000..d463a0e --- /dev/null +++ b/YFDXJ/litepal/src/main/java/org/litepal/parser/LitePalParser.java @@ -0,0 +1,208 @@ +/* + * Copyright (C) Tony Green, LitePal Framework Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.litepal.parser; + +import java.io.IOException; +import java.io.InputStream; + +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.parsers.SAXParserFactory; + +import org.litepal.LitePalApplication; +import org.litepal.exceptions.ParseConfigurationFileException; +import org.litepal.util.Const; +import org.xml.sax.InputSource; +import org.xml.sax.SAXException; +import org.xml.sax.XMLReader; +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; +import org.xmlpull.v1.XmlPullParserFactory; + +import android.content.res.AssetManager; +import android.content.res.Resources.NotFoundException; + +/** + * The class is used to parse the litepal.xml file. There're three usual ways to + * parse XML in android, SAX, Pull and DOM. LitePal use SAX as default option, + * and DOM parser will be added soon. + * + * @author Tony Green + * @since 1.0 + */ +public class LitePalParser { + + /** + * Node name dbname. + */ + static final String NODE_DB_NAME = "dbname"; + + /** + * Node name version. + */ + static final String NODE_VERSION = "version"; + + /** + * Node name list. Currently not used. + */ + static final String NODE_LIST = "list"; + + /** + * Node name mapping. + */ + static final String NODE_MAPPING = "mapping"; + + /** + * Node name column case. + */ + static final String NODE_CASES = "cases"; + + /** + * Node name column storage. + */ + static final String NODE_STORAGE = "storage"; + + /** + * Attribute name value, for dbname and version node. + */ + static final String ATTR_VALUE = "value"; + + /** + * Attribute name class, for mapping node. + */ + static final String ATTR_CLASS = "class"; + + /** + * Store the parsed value of litepal.xml. + */ + private static LitePalParser parser; + + /** + * Analyze litepal.xml, and store the analyzed result in LitePalParser. Use + * DomParse to parse the configuration file as default. SAXParser and + * XmlPullParser is also optional, but not visible to developers. + */ + public static LitePalConfig parseLitePalConfiguration() { + if (parser == null) { + parser = new LitePalParser(); + } + return parser.usePullParse(); + } + + /** + * Use SAXParser to parse the litepal.xml file. It will get the parsed + * result from LitePalContentHandler and stored in the instance of + * LitePalAttr. + * + * Note while analyzing litepal.xml file, ParseConfigurationFileException + * could be thrown. Be careful of writing litepal.xml file, or developer's + * application may be crash. + */ + private void useSAXParser() { + LitePalContentHandler handler; + try { + SAXParserFactory factory = SAXParserFactory.newInstance(); + XMLReader xmlReader = factory.newSAXParser().getXMLReader(); + handler = new LitePalContentHandler(); + xmlReader.setContentHandler(handler); + xmlReader.parse(new InputSource(getConfigInputStream())); + } catch (NotFoundException e) { + throw new ParseConfigurationFileException( + ParseConfigurationFileException.CAN_NOT_FIND_LITEPAL_FILE); + } catch (SAXException e) { + throw new ParseConfigurationFileException( + ParseConfigurationFileException.FILE_FORMAT_IS_NOT_CORRECT); + } catch (ParserConfigurationException e) { + throw new ParseConfigurationFileException( + ParseConfigurationFileException.PARSE_CONFIG_FAILED); + } catch (IOException e) { + throw new ParseConfigurationFileException(ParseConfigurationFileException.IO_EXCEPTION); + } + } + + /** + * Use XmlPullParser to parse the litepal.xml file. It will store the result + * in the instance of LitePalAttr. + * + * Note while analyzing litepal.xml file, ParseConfigurationFileException + * could be thrown. Be careful of writing litepal.xml file, or developer's + * application may be crash. + */ + private LitePalConfig usePullParse() { + try { + LitePalConfig litePalConfig = new LitePalConfig(); + XmlPullParserFactory factory = XmlPullParserFactory.newInstance(); + XmlPullParser xmlPullParser = factory.newPullParser(); + xmlPullParser.setInput(getConfigInputStream(), "UTF-8"); + int eventType = xmlPullParser.getEventType(); + while (eventType != XmlPullParser.END_DOCUMENT) { + String nodeName = xmlPullParser.getName(); + switch (eventType) { + case XmlPullParser.START_TAG: { + if (NODE_DB_NAME.equals(nodeName)) { + String dbName = xmlPullParser.getAttributeValue("", ATTR_VALUE); + litePalConfig.setDbName(dbName); + } else if (NODE_VERSION.equals(nodeName)) { + String version = xmlPullParser.getAttributeValue("", ATTR_VALUE); + litePalConfig.setVersion(Integer.parseInt(version)); + } else if (NODE_MAPPING.equals(nodeName)) { + String className = xmlPullParser.getAttributeValue("", ATTR_CLASS); + litePalConfig.addClassName(className); + } else if (NODE_CASES.equals(nodeName)) { + String cases = xmlPullParser.getAttributeValue("", ATTR_VALUE); + litePalConfig.setCases(cases); + } else if (NODE_STORAGE.equals(nodeName)) { + String storage = xmlPullParser.getAttributeValue("", ATTR_VALUE); + litePalConfig.setStorage(storage); + } + break; + } + default: + break; + } + eventType = xmlPullParser.next(); + } + return litePalConfig; + } catch (XmlPullParserException e) { + throw new ParseConfigurationFileException( + ParseConfigurationFileException.FILE_FORMAT_IS_NOT_CORRECT); + } catch (IOException e) { + throw new ParseConfigurationFileException(ParseConfigurationFileException.IO_EXCEPTION); + } + } + + /** + * Iterates all files in the root of assets folder. If find litepal.xml, + * open this file and return the input stream. Or throw + * ParseConfigurationFileException. + * + * @return The input stream of litepal.xml. + * @throws java.io.IOException + */ + private InputStream getConfigInputStream() throws IOException { + AssetManager assetManager = LitePalApplication.getContext().getAssets(); + String[] fileNames = assetManager.list(""); + if (fileNames != null && fileNames.length > 0) { + for (String fileName : fileNames) { + if (Const.Config.CONFIGURATION_FILE_NAME.equalsIgnoreCase(fileName)) { + return assetManager.open(fileName, AssetManager.ACCESS_BUFFER); + } + } + } + throw new ParseConfigurationFileException( + ParseConfigurationFileException.CAN_NOT_FIND_LITEPAL_FILE); + } +} diff --git a/YFDXJ/litepal/src/main/java/org/litepal/tablemanager/AssociationCreator.java b/YFDXJ/litepal/src/main/java/org/litepal/tablemanager/AssociationCreator.java new file mode 100644 index 0000000..8bc5de4 --- /dev/null +++ b/YFDXJ/litepal/src/main/java/org/litepal/tablemanager/AssociationCreator.java @@ -0,0 +1,437 @@ +/* + * Copyright (C) Tony Green, LitePal Framework Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.litepal.tablemanager; + +import android.content.ContentValues; +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; +import android.text.TextUtils; + +import org.litepal.exceptions.DatabaseGenerateException; +import org.litepal.tablemanager.model.AssociationsModel; +import org.litepal.tablemanager.model.ColumnModel; +import org.litepal.tablemanager.model.GenericModel; +import org.litepal.util.BaseUtility; +import org.litepal.util.Const; +import org.litepal.util.DBUtility; +import org.litepal.util.LogUtil; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +/** + * When models have associations such as one2one, many2one or many2many, tables + * should add foreign key column or create intermediate table to make the object + * association mapping right. This process will be proceed automatically without + * concerning by users. To make this happen, user just need to declare the + * associations clearly in the models, and make sure all the mapping models are + * added in the litepal.xml file. + * + * @author Tony Green + * @since 1.0 + */ +public abstract class AssociationCreator extends Generator { + + protected abstract void createOrUpgradeTable(SQLiteDatabase db, boolean force); + + /** + * {@link org.litepal.tablemanager.AssociationCreator} analyzes two things. Add associations + * including add foreign key column to tables and create intermediate join + * tables. + */ + @Override + protected void addOrUpdateAssociation(SQLiteDatabase db, boolean force) { + addAssociations(getAllAssociations(), db, force); + } + + /** + * Generate a create table SQL by the passed in parameters. Note that it + * will always generate a SQL with id/_id column in it as primary key and + * this id is auto increment as integer if the autoIncrementId is true, or + * no primary key will be added. + * + * @param tableName + * The table name. + * @param columnModels + * A list contains all column models with column info. + * @param autoIncrementId + * Generate an auto increment id or not. Only intermediate join table doesn't need + * an auto increment id. + * @return A generated create table SQL. + */ + protected String generateCreateTableSQL(String tableName, List columnModels, + boolean autoIncrementId) { + StringBuilder createTableSQL = new StringBuilder("create table "); + createTableSQL.append(tableName).append(" ("); + if (autoIncrementId) { + createTableSQL.append("id integer primary key autoincrement,"); + } + if (isContainsOnlyIdField(columnModels)) { + // Remove the last comma when only have id field in model. + createTableSQL.deleteCharAt(createTableSQL.length() - 1); + } + boolean needSeparator = false; + for (ColumnModel columnModel : columnModels) { + if (columnModel.isIdColumn()) { + continue; + } + if (needSeparator) { + createTableSQL.append(", "); + } + needSeparator = true; + createTableSQL.append(columnModel.getColumnName()).append(" ").append(columnModel.getColumnType()); + if (!columnModel.isNullable()) { + createTableSQL.append(" not null"); + } + if (columnModel.isUnique()) { + createTableSQL.append(" unique"); + } + String defaultValue = columnModel.getDefaultValue(); + if (!TextUtils.isEmpty(defaultValue)) { + createTableSQL.append(" default ").append(defaultValue); + } + } + createTableSQL.append(")"); + LogUtil.d(TAG, "create table sql is >> " + createTableSQL); + return createTableSQL.toString(); + } + + /** + * Generate a SQL for dropping table. + * + * @param tableName + * The table name. + * @return A SQL to drop table. + */ + protected String generateDropTableSQL(String tableName) { + return "drop table if exists " + tableName; + } + + /** + * Generate a SQL for add new column into the existing table. + * @param tableName + * The table which want to add a column + * @param columnModel + * Which contains column info + * @return A SQL to add new column. + */ + protected String generateAddColumnSQL(String tableName, ColumnModel columnModel) { + StringBuilder addColumnSQL = new StringBuilder(); + addColumnSQL.append("alter table ").append(tableName); + addColumnSQL.append(" add column ").append(columnModel.getColumnName()); + addColumnSQL.append(" ").append(columnModel.getColumnType()); + if (!columnModel.isNullable()) { + addColumnSQL.append(" not null"); + } + if (columnModel.isUnique()) { + addColumnSQL.append(" unique"); + } + String defaultValue = columnModel.getDefaultValue(); + if (!TextUtils.isEmpty(defaultValue)) { + addColumnSQL.append(" default ").append(defaultValue); + } else { + if (!columnModel.isNullable()) { + if ("integer".equalsIgnoreCase(columnModel.getColumnType())) { + defaultValue = "0"; + } else if ("text".equalsIgnoreCase(columnModel.getColumnType())) { + defaultValue = "''"; + } else if ("real".equalsIgnoreCase(columnModel.getColumnType())) { + defaultValue = "0.0"; + } + addColumnSQL.append(" default ").append(defaultValue); + } + } + LogUtil.d(TAG, "add column sql is >> " + addColumnSQL); + return addColumnSQL.toString(); + } + + /** + * Judge the passed in column is a foreign key column format or not. Each + * column name ends with _id will be considered as foreign key column + * format. + * + * @param columnName + * The name of column. + * @return Return true if it's foreign column format, otherwise return + * false. + */ + protected boolean isForeignKeyColumnFormat(String columnName) { + if (!TextUtils.isEmpty(columnName)) { + return columnName.toLowerCase().endsWith("_id") && !columnName.equalsIgnoreCase("_id"); + } + return false; + } + + /** + * Once there's new table created. The table name will be saved into + * table_schema as a copy. Each table name will be saved only once. + * + * @param tableName + * The table name. + * @param tableType + * 0 means normal table, 1 means intermediate join table. + * @param db + * Instance of SQLiteDatabase. + */ + protected void giveTableSchemaACopy(String tableName, int tableType, SQLiteDatabase db) { + StringBuilder sql = new StringBuilder("select * from "); + sql.append(Const.TableSchema.TABLE_NAME); + LogUtil.d(TAG, "giveTableSchemaACopy SQL is >> " + sql); + Cursor cursor = null; + try { + cursor = db.rawQuery(sql.toString(), null); + if (isNeedtoGiveACopy(cursor, tableName)) { + ContentValues values = new ContentValues(); + values.put(Const.TableSchema.COLUMN_NAME, BaseUtility.changeCase(tableName)); + values.put(Const.TableSchema.COLUMN_TYPE, tableType); + db.insert(Const.TableSchema.TABLE_NAME, null, values); + } + } catch (Exception e) { + e.printStackTrace(); + } finally { + if (cursor != null) { + cursor.close(); + } + } + } + + /** + * Save the name of a created table into table_schema, but there're some + * extra rules. Each table name should be only saved once, and special + * tables will not be saved. + * + * @param cursor + * The cursor used to iterator values in the table. + * @param tableName + * The table name. + * @return If all rules are passed return true, any of them failed return + * false. + */ + private boolean isNeedtoGiveACopy(Cursor cursor, String tableName) { + return !isValueExists(cursor, tableName) && !isSpecialTable(tableName); + } + + /** + * Judge the table name has already exist in the table_schema or not. + * + * @param cursor + * The cursor used to iterator values in the table. + * @param tableName + * The table name. + * @return If value exists return true, or return false. + */ + private boolean isValueExists(Cursor cursor, String tableName) { + boolean exist = false; + if (cursor.moveToFirst()) { + do { + String name = cursor.getString(cursor + .getColumnIndexOrThrow(Const.TableSchema.COLUMN_NAME)); + if (name.equalsIgnoreCase(tableName)) { + exist = true; + break; + } + } while (cursor.moveToNext()); + } + return exist; + } + + /** + * Judge a table is a special table or not. Currently table_schema is a + * special table. + * + * @param tableName + * The table name. + * @return Return true if it's special table. + */ + private boolean isSpecialTable(String tableName) { + return Const.TableSchema.TABLE_NAME.equalsIgnoreCase(tableName); + } + + /** + * Analyzing all the association models in the collection. Judge their + * association types. If it's one2one or many2one associations, add the + * foreign key column to the associated table. If it's many2many + * associations, create an intermediate join table. + * + * @param associatedModels + * A collection contains all the association models.Use the + * association models to get association type and associated + * table names. + * @param db + * Instance of SQLiteDatabase. + * @param force + * Drop the table first if it already exists. + */ + private void addAssociations(Collection associatedModels, SQLiteDatabase db, + boolean force) { + for (AssociationsModel associationModel : associatedModels) { + if (Const.Model.MANY_TO_ONE == associationModel.getAssociationType() + || Const.Model.ONE_TO_ONE == associationModel.getAssociationType()) { + addForeignKeyColumn(associationModel.getTableName(), + associationModel.getAssociatedTableName(), + associationModel.getTableHoldsForeignKey(), db); + } else if (Const.Model.MANY_TO_MANY == associationModel.getAssociationType()) { + createIntermediateTable(associationModel.getTableName(), + associationModel.getAssociatedTableName(), db, force); + } + } + for (GenericModel genericModel : getGenericModels()) { + createGenericTable(genericModel, db, force); + } + } + + /** + * When it comes to many2many associations. Database need to create an + * intermediate table for mapping this association. This method helps create + * such a table, and the table name follows the concatenation of the two + * target table names in alphabetical order with underline in the middle. + * + * @param tableName + * The table name. + * @param associatedTableName + * The associated table name. + * @param db + * Instance of SQLiteDatabase. + * @param force + * Drop the table first if it already exists. + */ + private void createIntermediateTable(String tableName, String associatedTableName, + SQLiteDatabase db, boolean force) { + List columnModelList = new ArrayList(); + ColumnModel column1 = new ColumnModel(); + column1.setColumnName(tableName + "_id"); + column1.setColumnType("integer"); + ColumnModel column2 = new ColumnModel(); + column2.setColumnName(associatedTableName + "_id"); + column2.setColumnType("integer"); + columnModelList.add(column1); + columnModelList.add(column2); + String intermediateTableName = DBUtility.getIntermediateTableName(tableName, + associatedTableName); + List sqls = new ArrayList(); + if (DBUtility.isTableExists(intermediateTableName, db)) { + if (force) { + sqls.add(generateDropTableSQL(intermediateTableName)); + sqls.add(generateCreateTableSQL(intermediateTableName, columnModelList, false)); + } + } else { + sqls.add(generateCreateTableSQL(intermediateTableName, columnModelList, false)); + } + execute(sqls, db); + giveTableSchemaACopy(intermediateTableName, Const.TableSchema.INTERMEDIATE_JOIN_TABLE, db); + } + + /** + * When declared generic collection fields in model class. Database need to create + * generic tables for mapping these fields. This method helps create such a table. + * + * @param genericModel + * The GenericModel instance. + * @param db + * Instance of SQLiteDatabase. + * @param force + * Drop the table first if it already exists. + */ + private void createGenericTable(GenericModel genericModel, SQLiteDatabase db, boolean force) { + String tableName = genericModel.getTableName(); + String valueColumnName = genericModel.getValueColumnName(); + String valueColumnType = genericModel.getValueColumnType(); + String valueIdColumnName = genericModel.getValueIdColumnName(); + List columnModelList = new ArrayList(); + ColumnModel column1 = new ColumnModel(); + column1.setColumnName(valueColumnName); + column1.setColumnType(valueColumnType); + ColumnModel column2 = new ColumnModel(); + column2.setColumnName(valueIdColumnName); + column2.setColumnType("integer"); + columnModelList.add(column1); + columnModelList.add(column2); + List sqls = new ArrayList(); + if (DBUtility.isTableExists(tableName, db)) { + if (force) { + sqls.add(generateDropTableSQL(tableName)); + sqls.add(generateCreateTableSQL(tableName, columnModelList, false)); + } + } else { + sqls.add(generateCreateTableSQL(tableName, columnModelList, false)); + } + execute(sqls, db); + giveTableSchemaACopy(tableName, Const.TableSchema.GENERIC_TABLE, db); + } + + /** + * This method is used to add many to one association or one to one + * association on tables. It will automatically build a SQL to add foreign + * key to a table. If the passed in table name or associated table name + * doesn't exist, it will throw an exception. + * + * @param tableName + * The table name. + * @param associatedTableName + * The associated table name. + * @param tableHoldsForeignKey + * The table which holds the foreign key. + * @param db + * Instance of SQLiteDatabase. + * + * @throws org.litepal.exceptions.DatabaseGenerateException + */ + protected void addForeignKeyColumn(String tableName, String associatedTableName, + String tableHoldsForeignKey, SQLiteDatabase db) { + if (DBUtility.isTableExists(tableName, db)) { + if (DBUtility.isTableExists(associatedTableName, db)) { + String foreignKeyColumn = null; + if (tableName.equals(tableHoldsForeignKey)) { + foreignKeyColumn = getForeignKeyColumnName(associatedTableName); + } else if (associatedTableName.equals(tableHoldsForeignKey)) { + foreignKeyColumn = getForeignKeyColumnName(tableName); + } + if (!DBUtility.isColumnExists(foreignKeyColumn, tableHoldsForeignKey, db)) { + ColumnModel columnModel = new ColumnModel(); + columnModel.setColumnName(foreignKeyColumn); + columnModel.setColumnType("integer"); + List sqls = new ArrayList(); + sqls.add(generateAddColumnSQL(tableHoldsForeignKey, columnModel)); + execute(sqls, db); + } else { + LogUtil.d(TAG, "column " + foreignKeyColumn + + " is already exist, no need to add one"); + } + } else { + throw new DatabaseGenerateException(DatabaseGenerateException.TABLE_DOES_NOT_EXIST + + associatedTableName); + } + } else { + throw new DatabaseGenerateException(DatabaseGenerateException.TABLE_DOES_NOT_EXIST + + tableName); + } + } + + /** + * Check if the ColumnModel list contains only id field. + * @param columnModels + * List contains model fields. + * @return If ColumnModel list is empty or contains only id, _id field, return true. Otherwise return false. + */ + private boolean isContainsOnlyIdField(List columnModels) { + return columnModels.size() == 0 + || (columnModels.size() == 1 && isIdColumn(columnModels.get(0).getColumnName())) + || (columnModels.size() == 2 && isIdColumn(columnModels.get(0).getColumnName()) && isIdColumn(columnModels.get(1).getColumnName())); + } + +} diff --git a/YFDXJ/litepal/src/main/java/org/litepal/tablemanager/AssociationUpdater.java b/YFDXJ/litepal/src/main/java/org/litepal/tablemanager/AssociationUpdater.java new file mode 100644 index 0000000..7b6d837 --- /dev/null +++ b/YFDXJ/litepal/src/main/java/org/litepal/tablemanager/AssociationUpdater.java @@ -0,0 +1,497 @@ +/* + * Copyright (C) Tony Green, LitePal Framework Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.litepal.tablemanager; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +import org.litepal.parser.LitePalAttr; +import org.litepal.tablemanager.model.AssociationsModel; +import org.litepal.tablemanager.model.ColumnModel; +import org.litepal.tablemanager.model.GenericModel; +import org.litepal.tablemanager.model.TableModel; +import org.litepal.util.Const; +import org.litepal.util.DBUtility; +import org.litepal.util.BaseUtility; +import org.litepal.util.LogUtil; + +import android.database.sqlite.SQLiteDatabase; + +/** + * Upgrade the associations between model classes into tables. Creating new + * tables and adding new foreign key columns are done in + * {@link org.litepal.tablemanager.AssociationUpdater}. So this class just deal with the simple job of + * removing foreign key columns and dropping dump intermediate join tables. + * + * @author Tony Green + * @since 1.0 + */ +public abstract class AssociationUpdater extends Creator { + + public static final String TAG = "AssociationUpdater"; + + /** + * A collection contains all the association models. + */ + private Collection mAssociationModels; + + /** + * Instance of SQLiteDatabase. + */ + protected SQLiteDatabase mDb; + + /** + * Analysis the {@link org.litepal.tablemanager.model.TableModel} by the purpose of subclasses, and + * generate a SQL to do the intention job. The implementation of this method + * is totally delegated to the subclasses. + */ + @Override + protected abstract void createOrUpgradeTable(SQLiteDatabase db, boolean force); + + /** + * {@link org.litepal.tablemanager.AssociationUpdater} does two jobs. Removing foreign key columns + * when two models are not associated anymore, and remove the intermediate + * join tables when two models are not associated anymore. + */ + @Override + protected void addOrUpdateAssociation(SQLiteDatabase db, boolean force) { + mAssociationModels = getAllAssociations(); + mDb = db; + removeAssociations(); + } + + /** + * This method looks around all the columns in the table, and judge which of + * them are foreign key columns. + * + * @param tableModel + * Use the TableModel to get table name and columns name to + * generate SQL. + * @return All the foreign key columns in a list. + */ + protected List getForeignKeyColumns(TableModel tableModel) { + List foreignKeyColumns = new ArrayList(); + List columnModelList = getTableModelFromDB(tableModel.getTableName()).getColumnModels(); + for (ColumnModel columnModel : columnModelList) { + String columnName = columnModel.getColumnName(); + if (isForeignKeyColumnFormat(columnModel.getColumnName())) { + if (!tableModel.containsColumn(columnName)) { + // Now this is a foreign key column. + LogUtil.d(TAG, "getForeignKeyColumnNames >> foreign key column is " + columnName); + foreignKeyColumns.add(columnName); + } + } + } + return foreignKeyColumns; + } + + /** + * Judge the passed in column is a foreign key column or not. Each column + * name ends with _id will be considered as foreign key column. + * + * @param tableModel + * Use the TableModel to get table name and columns name to + * generate SQL. + * @param columnName + * The column to judge. + * @return Return true if it's foreign column, otherwise return false. + */ + protected boolean isForeignKeyColumn(TableModel tableModel, String columnName) { + return BaseUtility.containsIgnoreCases(getForeignKeyColumns(tableModel), columnName); + } + + /** + * Look from the database to find a table named same as the table name in + * table model. Then iterate the columns and types of this table to create a + * new instance of table model. If there's no such a table in the database, + * then throw DatabaseGenerateException. + * + * @param tableName + * The table name use to get table model from database. + * @return A table model object with values from database table. + * @throws org.litepal.exceptions.DatabaseGenerateException + */ + protected TableModel getTableModelFromDB(String tableName) { + return DBUtility.findPragmaTableInfo(tableName, mDb); + } + + /** + * Drop the tables by the passing table name. + * + * @param dropTableNames + * The names of the tables that need to drop. + * @param db + * Instance of SQLiteDatabase. + */ + protected void dropTables(List dropTableNames, SQLiteDatabase db) { + if (dropTableNames != null && !dropTableNames.isEmpty()) { + List dropTableSQLS = new ArrayList(); + for (int i = 0; i < dropTableNames.size(); i++) { + dropTableSQLS.add(generateDropTableSQL(dropTableNames.get(i))); + } + execute(dropTableSQLS, db); + } + } + + /** + * When some fields are removed from class, the table should synchronize the + * changes by removing the corresponding columns. + * + * @param removeColumnNames + * The column names that need to remove. + * @param tableName + * The table name to remove columns from. + */ + protected void removeColumns(Collection removeColumnNames, String tableName) { + if (removeColumnNames != null && !removeColumnNames.isEmpty()) { + execute(getRemoveColumnSQLs(removeColumnNames, tableName), mDb); + } + } + + /** + * The values in table_schame should be synchronized with the model tables + * in the database. If a model table is dropped, the corresponding data + * should be removed from table_schema too. + * + * @param tableNames + * The table names need to remove from table_schema. + */ + protected void clearCopyInTableSchema(List tableNames) { + if (tableNames != null && !tableNames.isEmpty()) { + StringBuilder deleteData = new StringBuilder("delete from "); + deleteData.append(Const.TableSchema.TABLE_NAME).append(" where"); + boolean needOr = false; + for (String tableName : tableNames) { + if (needOr) { + deleteData.append(" or "); + } + needOr = true; + deleteData.append(" lower(").append(Const.TableSchema.COLUMN_NAME).append(") "); + deleteData.append("=").append(" lower('").append(tableName).append("')"); + } + LogUtil.d(TAG, "clear table schema value sql is " + deleteData); + List sqls = new ArrayList(); + sqls.add(deleteData.toString()); + execute(sqls, mDb); + } + } + + /** + * When the association between two tables are no longer associated in the + * classes, database should remove the foreign key column or intermediate + * join table that keeps these two tables associated. + */ + private void removeAssociations() { + removeForeignKeyColumns(); + removeIntermediateTables(); + removeGenericTables(); + } + + /** + * Analyzing the table models, then remove all the foreign key columns if + * their association in model classes are no longer exist any more. + */ + private void removeForeignKeyColumns() { + for (String className : LitePalAttr.getInstance().getClassNames()) { + TableModel tableModel = getTableModel(className); + removeColumns(findForeignKeyToRemove(tableModel), tableModel.getTableName()); + } + } + + /** + * If there're intermediate join tables for two tables, when the two classes + * are not associated, the join table should be dropped. + */ + private void removeIntermediateTables() { + List tableNamesToDrop = findIntermediateTablesToDrop(); + dropTables(tableNamesToDrop, mDb); + clearCopyInTableSchema(tableNamesToDrop); + } + + /** + * If there're generic tables for generic fields, when the fields are removed + * from class, the generic tables should be dropped. + */ + private void removeGenericTables() { + List tableNamesToDrop = findGenericTablesToDrop(); + dropTables(tableNamesToDrop, mDb); + clearCopyInTableSchema(tableNamesToDrop); + } + + /** + * This method gives back the names of the foreign key columns that need to + * remove, cause their associations in the classes are no longer exist. + * + * @param tableModel + * Use the TableModel to get table name and columns name to + * generate SQL. + * @return The foreign key columns need to remove in a list. + */ + private List findForeignKeyToRemove(TableModel tableModel) { + List removeRelations = new ArrayList(); + List foreignKeyColumns = getForeignKeyColumns(tableModel); + String selfTableName = tableModel.getTableName(); + for (String foreignKeyColumn : foreignKeyColumns) { + String associatedTableName = DBUtility.getTableNameByForeignColumn(foreignKeyColumn); + if (shouldDropForeignKey(selfTableName, associatedTableName)) { + removeRelations.add(foreignKeyColumn); + } + } + LogUtil.d(TAG, "findForeignKeyToRemove >> " + tableModel.getTableName() + " " + + removeRelations); + return removeRelations; + } + + /** + * When many2many associations are no longer exist between two models, the + * intermediate join table should be dropped from database. This method + * helps find out those intermediate join tables which should be dropped + * cause their associations in classes are done. + * + * @return A list with all intermediate join tables to drop. + */ + private List findIntermediateTablesToDrop() { + List intermediateTables = new ArrayList(); + for (String tableName : DBUtility.findAllTableNames(mDb)) { + if (DBUtility.isIntermediateTable(tableName, mDb)) { + boolean dropIntermediateTable = true; + for (AssociationsModel associationModel : mAssociationModels) { + if (associationModel.getAssociationType() == Const.Model.MANY_TO_MANY) { + String intermediateTableName = DBUtility.getIntermediateTableName( + associationModel.getTableName(), + associationModel.getAssociatedTableName()); + if (tableName.equalsIgnoreCase(intermediateTableName)) { + dropIntermediateTable = false; + } + } + } + if (dropIntermediateTable) { + // drop the intermediate join table + intermediateTables.add(tableName); + } + } + } + LogUtil.d(TAG, "findIntermediateTablesToDrop >> " + intermediateTables); + return intermediateTables; + } + + /** + * When generic fields are no longer exist in the class models, the generic tables should be + * dropped from database. This method helps find out those generic tables which should be dropped + * cause their generic fields in classes are removed. + * + * @return A list with all generic tables to drop. + */ + private List findGenericTablesToDrop() { + List genericTablesToDrop = new ArrayList(); + for (String tableName : DBUtility.findAllTableNames(mDb)) { + if (DBUtility.isGenericTable(tableName, mDb)) { + boolean dropGenericTable = true; + for (GenericModel genericModel : getGenericModels()) { + String genericTableName = genericModel.getTableName(); + if (tableName.equalsIgnoreCase(genericTableName)) { + dropGenericTable = false; + } + } + if (dropGenericTable) { + // drop the generic table + genericTablesToDrop.add(tableName); + } + } + } + return genericTablesToDrop; + } + + /** + * Generate a SQL for renaming the table into a temporary table. + * + * @param tableName + * The table name use to alter to temporary table. + * @return SQL to rename table. + */ + protected String generateAlterToTempTableSQL(String tableName) { + StringBuilder sql = new StringBuilder(); + sql.append("alter table ").append(tableName).append(" rename to ") + .append(getTempTableName(tableName)); + return sql.toString(); + } + + /** + * Generate a SQL to create new table by the table model from database. Also + * it will remove the columns that need to remove before generating the SQL. + * + * @param removeColumnNames + * The column names need to remove. + * @param tableModel + * Which contains table name use to create new table. + * @return SQL to create new table. + */ + private String generateCreateNewTableSQL(Collection removeColumnNames, TableModel tableModel) { + for (String removeColumnName : removeColumnNames) { + tableModel.removeColumnModelByName(removeColumnName); + } + return generateCreateTableSQL(tableModel); + } + + /** + * Generate a SQL to do the data migration job to avoid losing data. + * + * @param tableModel + * Which contains table name use to migrate data. + * @return SQL to migrate data. + */ + protected String generateDataMigrationSQL(TableModel tableModel) { + String tableName = tableModel.getTableName(); + List columnModels = tableModel.getColumnModels(); + if (!columnModels.isEmpty()) { + StringBuilder sql = new StringBuilder(); + sql.append("insert into ").append(tableName).append("("); + boolean needComma = false; + for (ColumnModel columnModel : columnModels) { + if (needComma) { + sql.append(", "); + } + needComma = true; + sql.append(columnModel.getColumnName()); + } + sql.append(") "); + sql.append("select "); + needComma = false; + for (ColumnModel columnModel : columnModels) { + if (needComma) { + sql.append(", "); + } + needComma = true; + sql.append(columnModel.getColumnName()); + } + sql.append(" from ").append(getTempTableName(tableName)); + return sql.toString(); + } else { + return null; + } + } + + /** + * Generate a SQL to drop the temporary table. + * + * @param tableName + * The table name use to drop temporary table. + * @return SQL to drop the temporary table. + */ + protected String generateDropTempTableSQL(String tableName) { + return generateDropTableSQL(getTempTableName(tableName)); + } + + /** + * Removing or resizing columns from tables must need a temporary table to + * store data, and here's the table name. + * + * @param tableName + * The table name use to generate temporary table name. + * @return Temporary table name + */ + protected String getTempTableName(String tableName) { + return tableName + "_temp"; + } + + /** + * This method create a SQL array for the whole remove dump columns job. + * + * @param removeColumnNames + * The column names need to remove. + * @param tableName + * The table name to remove from. + * @return A SQL list contains create temporary table, create new table, + * migrate data and drop temporary table. + */ + private List getRemoveColumnSQLs(Collection removeColumnNames, String tableName) { + TableModel tableModelFromDB = getTableModelFromDB(tableName); + String alterToTempTableSQL = generateAlterToTempTableSQL(tableName); + LogUtil.d(TAG, "generateRemoveColumnSQL >> " + alterToTempTableSQL); + String createNewTableSQL = generateCreateNewTableSQL(removeColumnNames, tableModelFromDB); + LogUtil.d(TAG, "generateRemoveColumnSQL >> " + createNewTableSQL); + String dataMigrationSQL = generateDataMigrationSQL(tableModelFromDB); + LogUtil.d(TAG, "generateRemoveColumnSQL >> " + dataMigrationSQL); + String dropTempTableSQL = generateDropTempTableSQL(tableName); + LogUtil.d(TAG, "generateRemoveColumnSQL >> " + dropTempTableSQL); + List sqls = new ArrayList(); + sqls.add(alterToTempTableSQL); + sqls.add(createNewTableSQL); + sqls.add(dataMigrationSQL); + sqls.add(dropTempTableSQL); + return sqls; + } + + /** + * Judge if the current iterated foreign key column should be dropped. It is + * only used in {@link #findForeignKeyToRemove(org.litepal.tablemanager.model.TableModel)} when iterating + * the foreign key column list. When this foreign key can not be found in + * the association model collection, this foreign key should be dropped. + * + * @param selfTableName + * The table name of currently table model. + * @param associatedTableName + * The associated table name of current table model. + * @return If the foreign key currently iterated should be dropped, return + * true. Otherwise return false. + */ + private boolean shouldDropForeignKey(String selfTableName, String associatedTableName) { + for (AssociationsModel associationModel : mAssociationModels) { + if (associationModel.getAssociationType() == Const.Model.ONE_TO_ONE) { + if (selfTableName.equalsIgnoreCase(associationModel.getTableHoldsForeignKey())) { + if (associationModel.getTableName().equalsIgnoreCase(selfTableName)) { + if (isRelationCorrect(associationModel, selfTableName, associatedTableName)) { + return false; + } + } else if (associationModel.getAssociatedTableName().equalsIgnoreCase( + selfTableName)) { + if (isRelationCorrect(associationModel, associatedTableName, selfTableName)) { + return false; + } + } + } + } else if (associationModel.getAssociationType() == Const.Model.MANY_TO_ONE) { + if (isRelationCorrect(associationModel, associatedTableName, selfTableName)) { + return false; + } + } + } + return true; + } + + /** + * Judge if the tableName1 equals {@link org.litepal.tablemanager.model.AssociationsModel#getTableName()} + * and tableName2 equals {@link org.litepal.tablemanager.model.AssociationsModel#getAssociatedTableName()}. + * + * @param associationModel + * The association model to get table name and associated table + * name. + * @param tableName1 + * The table name to match table name from association model. + * @param tableName2 + * The table name to match associated table name from association + * model. + * @return Return true if the description is true, otherwise return false. + */ + private boolean isRelationCorrect(AssociationsModel associationModel, String tableName1, + String tableName2) { + return associationModel.getTableName().equalsIgnoreCase(tableName1) + && associationModel.getAssociatedTableName().equalsIgnoreCase(tableName2); + } + +} diff --git a/YFDXJ/litepal/src/main/java/org/litepal/tablemanager/Connector.java b/YFDXJ/litepal/src/main/java/org/litepal/tablemanager/Connector.java new file mode 100644 index 0000000..00e4e3a --- /dev/null +++ b/YFDXJ/litepal/src/main/java/org/litepal/tablemanager/Connector.java @@ -0,0 +1,120 @@ +/* + * Copyright (C) Tony Green, LitePal Framework Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.litepal.tablemanager; + +import org.litepal.LitePal; +import org.litepal.LitePalApplication; +import org.litepal.exceptions.InvalidAttributesException; +import org.litepal.parser.LitePalAttr; +import org.litepal.parser.LitePalConfig; +import org.litepal.parser.LitePalParser; + +import android.database.sqlite.SQLiteDatabase; + +/** + * The connector to connect database provided by LitePal. Users can use this + * class to get the instance of SQLiteDatabase. But users still need to write + * their own CRUD logic by the returned SQLiteDatabase. It will be improved in + * the future. + * + * @author Tony Green + * @since 1.0 + */ +public class Connector { + + /** + * The quote of LitePalHelper. + */ + private static LitePalOpenHelper mLitePalHelper; + + /** + * Get a writable SQLiteDatabase. + * + * There're a lot of ways to operate database in android. But LitePal + * doesn't support using ContentProvider currently. The best way to use + * LitePal well is get the SQLiteDatabase instance and use the methods like + * SQLiteDatabase#save, SQLiteDatabase#update, SQLiteDatabase#delete, + * SQLiteDatabase#query in the SQLiteDatabase class to do the database + * operation. It will be improved in the future. + * + * @return A writable SQLiteDatabase instance + */ + public synchronized static SQLiteDatabase getWritableDatabase() { + LitePalOpenHelper litePalHelper = buildConnection(); + return litePalHelper.getWritableDatabase(); + } + + /** + * Deprecated. Using {@link LitePal#getDatabase()} instead. + * + * @return A readable SQLiteDatabase instance. + */ + @Deprecated + public synchronized static SQLiteDatabase getReadableDatabase() { + LitePalOpenHelper litePalHelper = buildConnection(); + return litePalHelper.getReadableDatabase(); + } + + /** + * Call getDatabase directly will invoke the getWritableDatabase method by + * default. + * + * This is method is alias of getWritableDatabase. + * + * @return A writable SQLiteDatabase instance + */ + public static SQLiteDatabase getDatabase() { + return getWritableDatabase(); + } + + /** + * Build a connection to the database. This progress will analysis the + * litepal.xml file, and will check if the fields in LitePalAttr are valid, + * and it will open a SQLiteOpenHelper to decide to create tables or update + * tables or doing nothing depends on the version attributes. + * + * After all the stuffs above are finished. This method will return a + * LitePalHelper object.Notes this method could throw a lot of exceptions. + * + * @return LitePalHelper object. + * + * @throws org.litepal.exceptions.InvalidAttributesException + */ + private static LitePalOpenHelper buildConnection() { + LitePalAttr litePalAttr = LitePalAttr.getInstance(); + litePalAttr.checkSelfValid(); + if (mLitePalHelper == null) { + String dbName = litePalAttr.getDbName(); + if ("external".equalsIgnoreCase(litePalAttr.getStorage())) { + dbName = LitePalApplication.getContext().getExternalFilesDir("") + "/databases/" + dbName; + } + mLitePalHelper = new LitePalOpenHelper(dbName, litePalAttr.getVersion()); + } + return mLitePalHelper; + } + + /** + * Never call this method. This is only used by internal. + */ + public static void clearLitePalOpenHelperInstance() { + if (mLitePalHelper != null) { + mLitePalHelper.getWritableDatabase().close(); + mLitePalHelper = null; + } + } + +} diff --git a/YFDXJ/litepal/src/main/java/org/litepal/tablemanager/Creator.java b/YFDXJ/litepal/src/main/java/org/litepal/tablemanager/Creator.java new file mode 100644 index 0000000..4fb6503 --- /dev/null +++ b/YFDXJ/litepal/src/main/java/org/litepal/tablemanager/Creator.java @@ -0,0 +1,112 @@ +/* + * Copyright (C) Tony Green, LitePal Framework Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.litepal.tablemanager; + +import org.litepal.tablemanager.model.TableModel; +import org.litepal.util.Const; +import org.litepal.util.DBUtility; + +import android.database.sqlite.SQLiteDatabase; + +import java.util.ArrayList; +import java.util.List; + +/** + * This is a subclass of Generator. Use to create tables. It will automatically + * build a create table SQL based on the passing TableModel object. In case of + * there's already a table with the same name in the database, LitePal will + * always drop the table first before create a new one. If there's syntax error + * in the executing SQL by accident, Creator will throw a + * DatabaseGenerateException. + * + * @author Tony Green + * @since 1.0 + */ +class Creator extends AssociationCreator { + public static final String TAG = "Creator"; + + /** + * Analyzing the table model, create a table in the database based on the + * table model's value. + */ + @Override + protected void createOrUpgradeTable(SQLiteDatabase db, boolean force) { + for (TableModel tableModel : getAllTableModels()) { + createOrUpgradeTable(tableModel, db, force); + } + } + + protected void createOrUpgradeTable(TableModel tableModel, SQLiteDatabase db, boolean force) { + execute(getCreateTableSQLs(tableModel, db, force), db); + giveTableSchemaACopy(tableModel.getTableName(), Const.TableSchema.NORMAL_TABLE, db); + } + + /** + * When creating a new table, it should always try to drop the same name + * table if exists. This method create a SQL array for the whole create + * table job. + * + * @param tableModel + * The table model. + * @param db + * Instance of SQLiteDatabase. + * @param force + * Drop the table first if it already exists. + * @return A SQL array contains drop table if it exists and create new + * table. + */ + protected List getCreateTableSQLs(TableModel tableModel, SQLiteDatabase db, boolean force) { + List sqls = new ArrayList(); + if (force) { + sqls.add(generateDropTableSQL(tableModel)); + sqls.add(generateCreateTableSQL(tableModel)); + } else { + if (DBUtility.isTableExists(tableModel.getTableName(), db)) { + return null; + } else { + sqls.add(generateCreateTableSQL(tableModel)); + } + } + return sqls; + } + + /** + * Generate a SQL for dropping table. + * + * @param tableModel + * The table model. + * @return A SQL to drop table. + */ + private String generateDropTableSQL(TableModel tableModel) { + return generateDropTableSQL(tableModel.getTableName()); + } + + /** + * Generate a create table SQL by analyzing the TableModel. Note that it + * will always generate a SQL with id/_id column in it as primary key, and + * this id is auto increment as integer. Do not try to assign or modify it. + * + * @param tableModel + * Use the TableModel to get table name and columns name to + * generate SQL. + * @return A generated create table SQL. + */ + String generateCreateTableSQL(TableModel tableModel) { + return generateCreateTableSQL(tableModel.getTableName(), tableModel.getColumnModels(), true); + } + +} diff --git a/YFDXJ/litepal/src/main/java/org/litepal/tablemanager/Dropper.java b/YFDXJ/litepal/src/main/java/org/litepal/tablemanager/Dropper.java new file mode 100644 index 0000000..358993a --- /dev/null +++ b/YFDXJ/litepal/src/main/java/org/litepal/tablemanager/Dropper.java @@ -0,0 +1,135 @@ +/* + * Copyright (C) Tony Green, LitePal Framework Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.litepal.tablemanager; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +import org.litepal.tablemanager.model.TableModel; +import org.litepal.util.BaseUtility; +import org.litepal.util.Const; +import org.litepal.util.LogUtil; + +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; + +/** + * When developers defined some model classes and define them in the mapping + * list. All corresponding tables will be created automatically. But developers + * might realize some model classes are useless or can be optimized to remove, + * and they somehow drop the model classes. If the tables are still in the + * database, it will be a mess soon. So this class helps do the dropping job. + * Keep developers' database synchronized and clean. + * + * @author Tony Green + * @since 1.0 + */ +public class Dropper extends AssociationUpdater { + + /** + * Use the TableModel to get table name and columns name to generate SQL. + */ + private Collection mTableModels; + + /** + * Analyzing the table model, to see which tables has no model classes + * anymore and can be dropped. + */ + @Override + protected void createOrUpgradeTable(SQLiteDatabase db, boolean force) { + mTableModels = getAllTableModels(); + mDb = db; + dropTables(); + } + + /** + * Drop the tables which are not exist in the mapping list to keep + * synchronization. + */ + private void dropTables() { + List tableNamesToDrop = findTablesToDrop(); + dropTables(tableNamesToDrop, mDb); + clearCopyInTableSchema(tableNamesToDrop); + } + + /** + * It will find all the tables need to drop in the database, following the + * rules of {@link #shouldDropThisTable(String, int)}. + * + * @return A list contains all the table names need to drop. + */ + private List findTablesToDrop() { + List dropTableNames = new ArrayList(); + Cursor cursor = null; + try { + cursor = mDb.query(Const.TableSchema.TABLE_NAME, null, null, null, null, null, null); + if (cursor.moveToFirst()) { + do { + String tableName = cursor.getString(cursor + .getColumnIndexOrThrow(Const.TableSchema.COLUMN_NAME)); + int tableType = cursor.getInt(cursor + .getColumnIndexOrThrow(Const.TableSchema.COLUMN_TYPE)); + if (shouldDropThisTable(tableName, tableType)) { + // need to drop tableNameDB + LogUtil.d(TAG, "need to drop " + tableName); + dropTableNames.add(tableName); + } + } while (cursor.moveToNext()); + } + } catch (Exception ex) { + ex.printStackTrace(); + } finally { + if (cursor != null) { + cursor.close(); + } + } + return dropTableNames; + } + + /** + * Get a list only with table names. + * + * @return A list only contains table names. + */ + private List pickTableNamesFromTableModels() { + List tableNames = new ArrayList(); + for (TableModel tableModel : mTableModels) { + tableNames.add(tableModel.getTableName()); + } + return tableNames; + } + + /** + * It gets all the table names generated by the mapping classes and create a + * table name list. Compare table name list with the table name passed in. + * If the table name is not existed in the table name list and the table + * type is {@link org.litepal.util.Const.TableSchema#NORMAL_TABLE}, then this table should be + * dropped. + * + * @param tableName + * The table name to check. + * @param tableType + * The table type to check. + * @return If this table should be dropped return true. Otherwise return + * false. + */ + private boolean shouldDropThisTable(String tableName, int tableType) { + return !BaseUtility.containsIgnoreCases(pickTableNamesFromTableModels(), tableName) + && tableType == Const.TableSchema.NORMAL_TABLE; + } +} diff --git a/YFDXJ/litepal/src/main/java/org/litepal/tablemanager/Generator.java b/YFDXJ/litepal/src/main/java/org/litepal/tablemanager/Generator.java new file mode 100644 index 0000000..cb92dd9 --- /dev/null +++ b/YFDXJ/litepal/src/main/java/org/litepal/tablemanager/Generator.java @@ -0,0 +1,253 @@ +/* + * Copyright (C) Tony Green, LitePal Framework Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.litepal.tablemanager; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +import org.litepal.LitePalBase; +import org.litepal.exceptions.DatabaseGenerateException; +import org.litepal.parser.LitePalAttr; +import org.litepal.tablemanager.model.AssociationsModel; +import org.litepal.tablemanager.model.TableModel; +import org.litepal.util.BaseUtility; + +import android.database.SQLException; +import android.database.sqlite.SQLiteDatabase; +import android.text.TextUtils; + +/** + * This class is the basic class for managing database dynamically. It is used + * to create or update tables by the mapping classes from litepal.xml file. + * Generator is a superclass, it just read the fields from classes and format + * the fields types into database types. The analysis job is delegated to the + * subclasses to do. Then subclasses can invoke the execute method to finish the + * job or they can override to do their own logic. + * + * @author Tony Green + * @since 1.0 + */ +public abstract class Generator extends LitePalBase { + public static final String TAG = "Generator"; + + /** + * The collection contains all table models. Use a global variable store + * table model to improve performance. Avoiding look up for table model each + * time. + */ + private Collection mTableModels; + + /** + * The collection contains all association models. + */ + private Collection mAllRelationModels; + + /** + * This is a shortcut way to get all the table models for each model class + * defined in the mapping list. No need to iterate all the model classes and + * get table model for each one. + * + * @return A collection contains all table models. + */ + protected Collection getAllTableModels() { + if (mTableModels == null) { + mTableModels = new ArrayList(); + } + if (!canUseCache()) { + mTableModels.clear(); + for (String className : LitePalAttr.getInstance().getClassNames()) { + mTableModels.add(getTableModel(className)); + } + } + return mTableModels; + } + + /** + * This method is used to get all the association models which in the + * mapping list of litepal.xml file. + * + * @return Collection of RelationModel for all the mapping classes. + */ + protected Collection getAllAssociations() { + if (mAllRelationModels == null || mAllRelationModels.isEmpty()) { + mAllRelationModels = getAssociations(LitePalAttr.getInstance().getClassNames()); + } + return mAllRelationModels; + } + + /** + * Use the parameter SQLiteDatabase to execute the passing SQLs. Subclasses + * can add their own logic when do the executing job by overriding this + * method. + * + * @param sqls + * SQLs that want to execute. + * @param db + * instance of SQLiteDatabase + * + * @throws org.litepal.exceptions.DatabaseGenerateException + */ + protected void execute(List sqls, SQLiteDatabase db) { + String throwSQL = ""; + try { + if (sqls != null && !sqls.isEmpty()) { + for (String sql : sqls) { + if (!TextUtils.isEmpty(sql)) { + throwSQL = BaseUtility.changeCase(sql); + db.execSQL(throwSQL); + } + } + } + } catch (SQLException e) { + throw new DatabaseGenerateException(DatabaseGenerateException.SQL_ERROR + throwSQL); + } + } + + /** + * Add association to all the tables based on the associations between class + * models. + * + * @param db + * Instance of SQLiteDatabase. + * @param force + * Drop the table first if it already exists. + */ + private static void addAssociation(SQLiteDatabase db, boolean force) { + AssociationCreator associationsCreator = new Creator(); + associationsCreator.addOrUpdateAssociation(db, force); + } + + /** + * Update associations to all the associated tables in the database. Remove + * dump foreign key columns and dump intermediate join tables. + * + * @param db + * Instance of SQLiteDatabase. + */ + private static void updateAssociations(SQLiteDatabase db) { + AssociationUpdater associationUpgrader = new Upgrader(); + associationUpgrader.addOrUpdateAssociation(db, false); + } + + /** + * Upgrade all the tables in the database, including remove dump columns and + * add new columns. + * + * @param db + * Instance of SQLiteDatabase. + */ + private static void upgradeTables(SQLiteDatabase db) { + Upgrader upgrader = new Upgrader(); + upgrader.createOrUpgradeTable(db, false); + } + + /** + * Create tables based on the class models defined in the litepal.xml file. + * After the tables are created, add association to these tables based on + * the associations between class models. + * + * @param db + * Instance of SQLiteDatabase. + * @param force + * Drop the table first if it already exists. + */ + private static void create(SQLiteDatabase db, boolean force) { + Creator creator = new Creator(); + creator.createOrUpgradeTable(db, force); + } + + /** + * Drop the tables which are no longer exist in the mapping list but created + * before. + * + * @param db + * Instance of SQLiteDatabase. + */ + private static void drop(SQLiteDatabase db) { + Dropper dropper = new Dropper(); + dropper.createOrUpgradeTable(db, false); + } + + /** + * If the table models in the collection has the same size as classes + * defined in the mapping list, it means that the table models are exist. + * Can use cache. + * + * @return Can use the cache for the table models or not. + */ + private boolean canUseCache() { + if (mTableModels == null) { + return false; + } + return mTableModels.size() == LitePalAttr.getInstance().getClassNames().size(); + } + + /** + * Create tables based on the class models defined in the litepal.xml file. + * After the tables are created, add association to these tables based on + * the associations between class models. + * + * @param db + * Instance of SQLiteDatabase. + */ + static void create(SQLiteDatabase db) { + create(db, true); + addAssociation(db, true); + } + + /** + * Upgrade tables to make sure when model classes are changed, the + * corresponding tables in the database should be always synchronized with + * them. + * + * @param db + * Instance of SQLiteDatabase. + */ + static void upgrade(SQLiteDatabase db) { + drop(db); + create(db, false); + updateAssociations(db); + upgradeTables(db); + addAssociation(db, false); + } + + /** + * Analysis the TableModel by the purpose of subclasses, and generate a SQL + * to do the intention job. The implementation of this method is totally + * delegated to the subclasses. + * + * @param db + * Instance of SQLiteDatabase. + * @param force + * Drop the table first if it already exists. + */ + protected abstract void createOrUpgradeTable(SQLiteDatabase db, boolean force); + + /** + * Analysis the {@link org.litepal.tablemanager.model.AssociationsModel} by the purpose of subclasses, and + * generate a SQL to do the intention job. The implementation of this method + * is totally delegated to the subclasses. + * + * @param db + * Instance of SQLiteDatabase. + * @param force + * Drop the table first if it already exists. + */ + protected abstract void addOrUpdateAssociation(SQLiteDatabase db, boolean force); + +} diff --git a/YFDXJ/litepal/src/main/java/org/litepal/tablemanager/LitePalOpenHelper.java b/YFDXJ/litepal/src/main/java/org/litepal/tablemanager/LitePalOpenHelper.java new file mode 100644 index 0000000..af7abcb --- /dev/null +++ b/YFDXJ/litepal/src/main/java/org/litepal/tablemanager/LitePalOpenHelper.java @@ -0,0 +1,92 @@ +/* + * Copyright (C) Tony Green, LitePal Framework Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.litepal.tablemanager; + +import android.content.Context; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteDatabase.CursorFactory; +import android.database.sqlite.SQLiteOpenHelper; + +import org.litepal.LitePalApplication; +import org.litepal.parser.LitePalAttr; +import org.litepal.util.SharedUtil; + +/** + * The database helper to generate and manage the tables. It will automate + * create or upgrade the database file depends on the parameters passed in. + * + * LitePal makes it easy for managing tables. It used the dynamic features of + * Java with reflection API to achieve that. Developers won't need to write + * their own SQL for managing tables, LitePal will do that for them. Developers + * just need to write their model classes and add right associations. LitePal + * will take all the rest job to manager tables in database. + * + * @author Tony Green + * @since 1.0 + */ +class LitePalOpenHelper extends SQLiteOpenHelper { + public static final String TAG = "LitePalHelper"; + + /** + * The standard constructor for SQLiteOpenHelper. + * + * @param context + * To use to open or create the database. + * @param name + * The database file. + * @param factory + * To use for creating cursor objects, or null for the default + * version number of the database (starting at 1); if the + * database is older, onUpgrade. + * @param version + * (SQLiteDatabase, int, int) will be used to upgrade the + * database; if the database is newer, + * onDowngrade(SQLiteDatabase, int, int) will be used to + * downgrade the database + */ + LitePalOpenHelper(Context context, String name, CursorFactory factory, int version) { + super(context, name, factory, version); + } + + /** + * A simple constructor for SQLiteOpenHelper with null for CursorFactory as + * default. + * + * @param dbName + * The database file. + * @param version + * (SQLiteDatabase, int, int) will be used to upgrade the + * database; if the database is newer, + * onDowngrade(SQLiteDatabase, int, int) will be used to + * downgrade the database + */ + LitePalOpenHelper(String dbName, int version) { + this(LitePalApplication.getContext(), dbName, null, version); + } + + @Override + public void onCreate(SQLiteDatabase db) { + Generator.create(db); + } + + @Override + public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { + Generator.upgrade(db); + SharedUtil.updateVersion(LitePalAttr.getInstance().getExtraKeyName(), newVersion); + } + +} diff --git a/YFDXJ/litepal/src/main/java/org/litepal/tablemanager/Upgrader.java b/YFDXJ/litepal/src/main/java/org/litepal/tablemanager/Upgrader.java new file mode 100644 index 0000000..ee8784e --- /dev/null +++ b/YFDXJ/litepal/src/main/java/org/litepal/tablemanager/Upgrader.java @@ -0,0 +1,362 @@ +/* + * Copyright (C) Tony Green, LitePal Framework Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.litepal.tablemanager; + +import android.database.sqlite.SQLiteDatabase; +import android.text.TextUtils; + +import org.litepal.crud.model.AssociationsInfo; +import org.litepal.tablemanager.model.ColumnModel; +import org.litepal.tablemanager.model.TableModel; +import org.litepal.util.Const; +import org.litepal.util.DBUtility; +import org.litepal.util.LogUtil; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +/** + * Upgrade the database. The first step is to remove the columns that can not + * find the corresponding field in the model class. Then add the new added field + * as new column into the table. At last it will check all the types of columns + * to see which are changed. + * + * @author Tony Green + * @since 1.0 + */ +public class Upgrader extends AssociationUpdater { + /** + * Model class for table. + */ + protected TableModel mTableModel; + + /** + * Model class for table from database. + */ + protected TableModel mTableModelDB; + + /** + * Indicates that column constraints has changed or not. + */ + private boolean hasConstraintChanged; + + /** + * Analyzing the table model, them remove the dump columns and add new + * columns of a table. + */ + @Override + protected void createOrUpgradeTable(SQLiteDatabase db, boolean force) { + mDb = db; + for (TableModel tableModel : getAllTableModels()) { + mTableModel = tableModel; + mTableModelDB = getTableModelFromDB(tableModel.getTableName()); + LogUtil.d(TAG, "createOrUpgradeTable: model is " + mTableModel.getTableName()); + upgradeTable(); + } + } + + /** + * Upgrade table actions. Include remove dump columns, add new columns and + * change column types. All the actions above will be done by the description + * order. + */ + private void upgradeTable() { + if (hasNewUniqueOrNotNullColumn()) { + // Need to drop the table and create new one. Cause unique column can not be added, and null data can not be migrated. + createOrUpgradeTable(mTableModel, mDb, true); + // add foreign keys of the table. + Collection associationsInfo = getAssociationInfo(mTableModel.getClassName()); + for (AssociationsInfo info : associationsInfo) { + if (info.getAssociationType() == Const.Model.MANY_TO_ONE + || info.getAssociationType() == Const.Model.ONE_TO_ONE) { + if (info.getClassHoldsForeignKey().equalsIgnoreCase(mTableModel.getClassName())) { + String associatedTableName = DBUtility.getTableNameByClassName(info.getAssociatedClassName()); + addForeignKeyColumn(mTableModel.getTableName(), associatedTableName, mTableModel.getTableName(), mDb); + } + } + } + } else { + hasConstraintChanged = false; + removeColumns(findColumnsToRemove()); + addColumns(findColumnsToAdd()); + changeColumnsType(findColumnTypesToChange()); + changeColumnsConstraints(); + } + } + + /** + * Check if the current model add or upgrade an unique or not null column. + * @return True if has new unique or not null column. False otherwise. + */ + private boolean hasNewUniqueOrNotNullColumn() { + List columnModelList = mTableModel.getColumnModels(); + for (ColumnModel columnModel : columnModelList) { + ColumnModel columnModelDB = mTableModelDB.getColumnModelByName(columnModel.getColumnName()); + if (columnModel.isUnique()) { + if (columnModelDB == null || !columnModelDB.isUnique()) { + return true; + } + } + if (columnModelDB != null && !columnModel.isNullable() && columnModelDB.isNullable()) { + return true; + } + } + return false; + } + + /** + * It will find the difference between class model and table model. If + * there's a field in the class without a corresponding column in the table, + * this field is a new added column. This method find all new added columns. + * + * @return List with ColumnModel contains information of new columns. + */ + private List findColumnsToAdd() { + List columnsToAdd = new ArrayList(); + for (ColumnModel columnModel : mTableModel.getColumnModels()) { + String columnName = columnModel.getColumnName(); + if (!mTableModelDB.containsColumn(columnName)) { + // add column action + columnsToAdd.add(columnModel); + } + } + return columnsToAdd; + } + + /** + * This method helps find the difference between table model from class and + * table model from database. Database should always be synchronized with + * model class. If there're some fields are removed from class, the table + * model from database will be compared to find out which fields are + * removed. But there're still some exceptions. The columns named id or _id + * won't ever be removed. The foreign key column will be checked some where + * else, not from here. + * + * @return A list with column names need to remove. + */ + private List findColumnsToRemove() { + String tableName = mTableModel.getTableName(); + List removeColumns = new ArrayList(); + List columnModelList = mTableModelDB.getColumnModels(); + for (ColumnModel columnModel : columnModelList) { + String dbColumnName = columnModel.getColumnName(); + if (isNeedToRemove(dbColumnName)) { + removeColumns.add(dbColumnName); + } + } + LogUtil.d(TAG, "remove columns from " + tableName + " >> " + removeColumns); + return removeColumns; + } + + /** + * It will check each class in the mapping list. Find their types for each + * field is changed or not by comparing with the types in table columns. If + * there's a column have same name as a field in class but with different + * type, then it's a type changed column. + * + * @return A list contains all ColumnModel which type are changed from database. + */ + private List findColumnTypesToChange() { + List columnsToChangeType = new ArrayList(); + for (ColumnModel columnModelDB : mTableModelDB.getColumnModels()) { + for (ColumnModel columnModel : mTableModel.getColumnModels()) { + if (columnModelDB.getColumnName().equalsIgnoreCase(columnModel.getColumnName())) { + if (!columnModelDB.getColumnType().equalsIgnoreCase(columnModel.getColumnType())) { + if (columnModel.getColumnType().equalsIgnoreCase("blob") && TextUtils.isEmpty(columnModelDB.getColumnType())) { + // Case for binary array type upgrade. Do nothing under this condition. + } else { + // column type is changed + columnsToChangeType.add(columnModel); + } + } + if (!hasConstraintChanged) { + // for reducing loops, check column constraints change here. + LogUtil.d(TAG, "default value db is:" + columnModelDB.getDefaultValue() + ", default value is:" + columnModel.getDefaultValue()); + if (columnModelDB.isNullable() != columnModel.isNullable() || + !columnModelDB.getDefaultValue().equalsIgnoreCase(columnModel.getDefaultValue()) || + (columnModelDB.isUnique() && !columnModel.isUnique())) { // unique constraint can not be added + hasConstraintChanged = true; + } + } + } + } + } + return columnsToChangeType; + } + + /** + * Tell LitePal the column is need to remove or not. The column can be + * remove only on the condition that the following three rules are all + * passed. First the corresponding field for this column is removed in the + * class. Second this column is not an id column. Third this column is not a + * foreign key column. + * + * @param columnName + * The column name to judge + * @return Need to remove return true, otherwise return false. + */ + private boolean isNeedToRemove(String columnName) { + return isRemovedFromClass(columnName) && !isIdColumn(columnName) + && !isForeignKeyColumn(mTableModel, columnName); + } + + /** + * Read a column name from database, and judge the corresponding field in + * class is removed or not. + * + * @param columnName + * The column name to judge. + * @return If it's removed return true, or return false. + */ + private boolean isRemovedFromClass(String columnName) { + return !mTableModel.containsColumn(columnName); + } + + /** + * Generate a SQL for add new column into the existing table. + * + * @param columnModel + * Which contains column info. + * @return A SQL to add new column. + */ + private String generateAddColumnSQL(ColumnModel columnModel) { + return generateAddColumnSQL(mTableModel.getTableName(), columnModel); + } + + /** + * This method create a SQL array for the all new columns to add them into + * table. + * + * @param columnModelList + * List with ColumnModel to add new column. + * @return A SQL list contains add all new columns job. + */ + private List getAddColumnSQLs(List columnModelList) { + List sqls = new ArrayList(); + for (ColumnModel columnModel : columnModelList) { + sqls.add(generateAddColumnSQL(columnModel)); + } + return sqls; + } + + /** + * When some fields are removed from class, the table should synchronize the + * changes by removing the corresponding columns. + * + * @param removeColumnNames + * The column names that need to remove. + */ + private void removeColumns(List removeColumnNames) { + LogUtil.d(TAG, "do removeColumns " + removeColumnNames); + removeColumns(removeColumnNames, mTableModel.getTableName()); + for (String columnName : removeColumnNames) { + mTableModelDB.removeColumnModelByName(columnName); + } + } + + /** + * When some fields are added into the class after last upgrade, the table + * should synchronize the changes by adding the corresponding columns. + * + * @param columnModelList + * List with ColumnModel to add new column. + */ + private void addColumns(List columnModelList) { + LogUtil.d(TAG, "do addColumn"); + execute(getAddColumnSQLs(columnModelList), mDb); + for (ColumnModel columnModel : columnModelList) { + mTableModelDB.addColumnModel(columnModel); + } + } + + /** + * When some fields type are changed in class, the table should drop the + * before columns and create new columns with same name but new types. + * + * @param columnModelList + * List with ColumnModel to change column type. + */ + private void changeColumnsType(List columnModelList) { + LogUtil.d(TAG, "do changeColumnsType"); + List columnNames = new ArrayList(); + if (columnModelList != null && !columnModelList.isEmpty()) { + for (ColumnModel columnModel : columnModelList) { + columnNames.add(columnModel.getColumnName()); + } + } + removeColumns(columnNames); + addColumns(columnModelList); + } + + /** + * When fields annotation changed in class, table should change the corresponding constraints + * make them sync to the fields annotation. + */ + private void changeColumnsConstraints() { + if (hasConstraintChanged) { + LogUtil.d(TAG, "do changeColumnsConstraints"); + execute(getChangeColumnsConstraintsSQL(), mDb); + } + } + + /** + * This method create a SQL array for the whole changing column constraints job. + * @return A SQL list contains create temporary table, create new table, add foreign keys, + * migrate data and drop temporary table. + */ + private List getChangeColumnsConstraintsSQL() { + String alterToTempTableSQL = generateAlterToTempTableSQL(mTableModel.getTableName()); + String createNewTableSQL = generateCreateTableSQL(mTableModel); + List addForeignKeySQLs = generateAddForeignKeySQL(); + String dataMigrationSQL = generateDataMigrationSQL(mTableModelDB); + String dropTempTableSQL = generateDropTempTableSQL(mTableModel.getTableName()); + List sqls = new ArrayList(); + sqls.add(alterToTempTableSQL); + sqls.add(createNewTableSQL); + sqls.addAll(addForeignKeySQLs); + sqls.add(dataMigrationSQL); + sqls.add(dropTempTableSQL); + LogUtil.d(TAG, "generateChangeConstraintSQL >> "); + for (String sql : sqls) { + LogUtil.d(TAG, sql); + } + LogUtil.d(TAG, "<< generateChangeConstraintSQL"); + return sqls; + } + + /** + * Generate a SQL List for adding foreign keys. Changing constraints job should remain all the + * existing columns including foreign keys. This method add origin foreign keys after creating + * table. + * @return A SQL List for adding foreign keys. + */ + private List generateAddForeignKeySQL() { + List addForeignKeySQLs = new ArrayList(); + List foreignKeyColumns = getForeignKeyColumns(mTableModel); + for (String foreignKeyColumn : foreignKeyColumns) { + if (!mTableModel.containsColumn(foreignKeyColumn)) { + ColumnModel columnModel = new ColumnModel(); + columnModel.setColumnName(foreignKeyColumn); + columnModel.setColumnType("integer"); + addForeignKeySQLs.add(generateAddColumnSQL(mTableModel.getTableName(), columnModel)); + } + } + return addForeignKeySQLs; + } + +} diff --git a/YFDXJ/litepal/src/main/java/org/litepal/tablemanager/model/AssociationsModel.java b/YFDXJ/litepal/src/main/java/org/litepal/tablemanager/model/AssociationsModel.java new file mode 100644 index 0000000..4e0ee93 --- /dev/null +++ b/YFDXJ/litepal/src/main/java/org/litepal/tablemanager/model/AssociationsModel.java @@ -0,0 +1,160 @@ +/* + * Copyright (C) Tony Green, LitePal Framework Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.litepal.tablemanager.model; + +import org.litepal.util.Const; + +/** + * This is a model class for table associations. It stores table name, + * associated table name, table name which holds foreign key, and association + * type. Relations have three types. One2One, Many2One and Many2Many. If the + * association type is One2One or Many2One, the foreign key will be on the side + * of tableHoldsForeignKey. If the association is Many2Many, a intermediate join + * table will be built and named by the concatenation of the two target table + * names in alphabetical order with underline in the middle. + * + * @author Tony Green + * @since 1.0 + */ +public class AssociationsModel { + + /** + * Table name. + */ + private String tableName; + + /** + * Associated table name. + */ + private String associatedTableName; + + /** + * The table which holds foreign key. + */ + private String tableHoldsForeignKey; + + /** + * The association type, including {@link Const.Model#MANY_TO_ONE}, + * {@link Const.Model#MANY_TO_MANY}, {@link Const.Model#ONE_TO_ONE}. + */ + private int associationType; + + /** + * Get table name. + * + * @return Return the table name. + */ + public String getTableName() { + return tableName; + } + + /** + * Set table name. + * + * @param tableName + * The table name to set. + */ + public void setTableName(String tableName) { + this.tableName = tableName; + } + + /** + * Get associated table name. + * + * @return Return the associated table name. + */ + public String getAssociatedTableName() { + return associatedTableName; + } + + /** + * Set associated table name. + * + * @param associatedTableName + * The associated table name. + */ + public void setAssociatedTableName(String associatedTableName) { + this.associatedTableName = associatedTableName; + } + + /** + * Get the table which holds foreign key. + * + * @return The table which holds foreign key. + */ + public String getTableHoldsForeignKey() { + return tableHoldsForeignKey; + } + + /** + * Set the table which holds foreign key. + * + * @param tableHoldsForeignKey + * The table which holds foreign key to set. + */ + public void setTableHoldsForeignKey(String tableHoldsForeignKey) { + this.tableHoldsForeignKey = tableHoldsForeignKey; + } + + /** + * Get the association type. + * + * @return The association type. + */ + public int getAssociationType() { + return associationType; + } + + /** + * Set the association type. + * + * @param associationType + * Within {@link Const.Model#MANY_TO_ONE}, + * {@link Const.Model#MANY_TO_MANY}, + * {@link Const.Model#ONE_TO_ONE}. + */ + public void setAssociationType(int associationType) { + this.associationType = associationType; + } + + /** + * Override equals method to make sure that if two associated tables in the + * association model are same ignoring sides, they are same association + * model. + */ + @Override + public boolean equals(Object o) { + if (o instanceof AssociationsModel) { + AssociationsModel association = (AssociationsModel) o; + if (association.getTableName() != null && association.getAssociatedTableName() != null) { + if (association.getAssociationType() == associationType + && association.getTableHoldsForeignKey().equals(tableHoldsForeignKey)) { + if (association.getTableName().equals(tableName) + && association.getAssociatedTableName().equals(associatedTableName) + && association.getTableHoldsForeignKey().equals(tableHoldsForeignKey)) { + return true; + } else if (association.getTableName().equals(associatedTableName) + && association.getAssociatedTableName().equals(tableName) + && association.getTableHoldsForeignKey().equals(tableHoldsForeignKey)) { + return true; + } + } + } + } + return false; + } +} diff --git a/YFDXJ/litepal/src/main/java/org/litepal/tablemanager/model/ColumnModel.java b/YFDXJ/litepal/src/main/java/org/litepal/tablemanager/model/ColumnModel.java new file mode 100644 index 0000000..3e4838c --- /dev/null +++ b/YFDXJ/litepal/src/main/java/org/litepal/tablemanager/model/ColumnModel.java @@ -0,0 +1,108 @@ +/* + * Copyright (C) Tony Green, LitePal Framework Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.litepal.tablemanager.model; + +import android.text.TextUtils; + +/** + * This is a model class for columns. It stores column name, column type, and column constraints + * information. + * @author Tony Green + * @since 1.3 + */ +public class ColumnModel { + + /** + * Name of column. + */ + private String columnName; + + /** + * Type for column. + */ + private String columnType; + + /** + * Nullable constraint. + */ + private boolean isNullable = true; + + /** + * Unique constraint. + */ + private boolean isUnique = false; + + /** + * Default constraint. + */ + private String defaultValue = ""; + + public String getColumnName() { + return columnName; + } + + public void setColumnName(String columnName) { + this.columnName = columnName; + } + + public String getColumnType() { + return columnType; + } + + public void setColumnType(String columnType) { + this.columnType = columnType; + } + + public boolean isNullable() { + return isNullable; + } + + public void setNullable(boolean isNullable) { + this.isNullable = isNullable; + } + + public boolean isUnique() { + return isUnique; + } + + public void setUnique(boolean isUnique) { + this.isUnique = isUnique; + } + + public String getDefaultValue() { + return defaultValue; + } + + public void setDefaultValue(String defaultValue) { + if ("text".equalsIgnoreCase(columnType)) { + if (!TextUtils.isEmpty(defaultValue)) { + this.defaultValue = "'" + defaultValue + "'"; + } + } else { + this.defaultValue = defaultValue; + } + } + + /** + * Judge current ColumnModel is id column or not. + * @return True if it's id column. False otherwise. + */ + public boolean isIdColumn() { + return "_id".equalsIgnoreCase(columnName) || "id".equalsIgnoreCase(columnName); + } + +} diff --git a/YFDXJ/litepal/src/main/java/org/litepal/tablemanager/model/GenericModel.java b/YFDXJ/litepal/src/main/java/org/litepal/tablemanager/model/GenericModel.java new file mode 100644 index 0000000..2383da7 --- /dev/null +++ b/YFDXJ/litepal/src/main/java/org/litepal/tablemanager/model/GenericModel.java @@ -0,0 +1,93 @@ +/* + * Copyright (C) Tony Green, LitePal Framework Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.litepal.tablemanager.model; + +/** + * This is a model class for generic table. It stores table name, value column name, value column + * type and value id column name. This class is used to create generic tables when generic collection + * fields are declared in the model class. + * + * @author Tony Green + * @since 1.4 + */ +public class GenericModel { + + /** + * Table name. + */ + private String tableName; + + /** + * Column name for storing value. + */ + private String valueColumnName; + + /** + * Column type for storing value. + */ + private String valueColumnType; + + /** + * Column name for reference with main table. + */ + private String valueIdColumnName; + + /** + * Only used when query generic data. This is cache fields for improving performance. + */ + private String getMethodName; + + public String getTableName() { + return tableName; + } + + public void setTableName(String tableName) { + this.tableName = tableName; + } + + public String getValueColumnName() { + return valueColumnName; + } + + public void setValueColumnName(String valueColumnName) { + this.valueColumnName = valueColumnName; + } + + public String getValueColumnType() { + return valueColumnType; + } + + public void setValueColumnType(String valueColumnType) { + this.valueColumnType = valueColumnType; + } + + public String getValueIdColumnName() { + return valueIdColumnName; + } + + public void setValueIdColumnName(String valueIdColumnName) { + this.valueIdColumnName = valueIdColumnName; + } + + public String getGetMethodName() { + return getMethodName; + } + + public void setGetMethodName(String getMethodName) { + this.getMethodName = getMethodName; + } +} diff --git a/YFDXJ/litepal/src/main/java/org/litepal/tablemanager/model/TableModel.java b/YFDXJ/litepal/src/main/java/org/litepal/tablemanager/model/TableModel.java new file mode 100644 index 0000000..e34d35e --- /dev/null +++ b/YFDXJ/litepal/src/main/java/org/litepal/tablemanager/model/TableModel.java @@ -0,0 +1,157 @@ +/* + * Copyright (C) Tony Green, LitePal Framework Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.litepal.tablemanager.model; + +import android.text.TextUtils; + +import java.util.ArrayList; +import java.util.List; + +/** + * This is a model class for tables. It stores a table name and a HashMap for + * columns in the table. + * + * @author Tony Green + * @since 1.0 + */ +public class TableModel { + + /** + * Table name. + */ + private String tableName; + + /** + * A list contains all column models with column name, type and constraints. + */ + private List columnModels = new ArrayList(); + + /** + * Class name for the table name. This value might be null. Don't rely on it. + */ + private String className; + + /** + * Get table name. + * + * @return Name of table. + */ + public String getTableName() { + return tableName; + } + + /** + * Set table name. + * + * @param tableName + * Name of table. + */ + public void setTableName(String tableName) { + this.tableName = tableName; + } + + /** + * Get class name. + * + * @return Return the class name or null. + */ + public String getClassName() { + return className; + } + + /** + * Set class name. + * + * @param className + * The class name. + */ + public void setClassName(String className) { + this.className = className; + } + + /** + * Add a column model into the table model. + * + * @param columnModel + * A column model contains name, type and constraints. + */ + public void addColumnModel(ColumnModel columnModel) { + columnModels.add(columnModel); + } + + /** + * Find all the column models of the current table model. + * @return A list contains all column models. + */ + public List getColumnModels() { + return columnModels; + } + + /** + * Find the ColumnModel which can map the column name passed in. + * @param columnName + * Name of column. + * @return A ColumnModel which can map the column name passed in. Or null. + */ + public ColumnModel getColumnModelByName(String columnName) { + for (ColumnModel columnModel : columnModels) { + if (columnModel.getColumnName().equalsIgnoreCase(columnName)) { + return columnModel; + } + } + return null; + } + + /** + * Remove a column model by the specified column name. + * @param columnName + * Name of the column to remove. + */ + public void removeColumnModelByName(String columnName) { + if (TextUtils.isEmpty(columnName)) { + return; + } + int indexToRemove = -1; + for (int i = 0; i < columnModels.size(); i++) { + ColumnModel columnModel = columnModels.get(i); + if (columnName.equalsIgnoreCase(columnModel.getColumnName())) { + indexToRemove = i; + break; + } + } + if (indexToRemove != -1) { + columnModels.remove(indexToRemove); + } + } + + /** + * Judge the table model has such a column or not. + * @param columnName + * The name of column to check. + * @return True if matches a column in the table model. False otherwise. + */ + public boolean containsColumn(String columnName) { + for (int i = 0; i < columnModels.size(); i++) { + ColumnModel columnModel = columnModels.get(i); + if (columnName.equalsIgnoreCase(columnModel.getColumnName())) { + return true; + } + } + return false; + } + +} diff --git a/YFDXJ/litepal/src/main/java/org/litepal/tablemanager/typechange/BlobOrm.java b/YFDXJ/litepal/src/main/java/org/litepal/tablemanager/typechange/BlobOrm.java new file mode 100644 index 0000000..88ab58e --- /dev/null +++ b/YFDXJ/litepal/src/main/java/org/litepal/tablemanager/typechange/BlobOrm.java @@ -0,0 +1,25 @@ +package org.litepal.tablemanager.typechange; + +/** + * This class deals with byte type. + * + * @author Tony Green + * @since 1.3.1 + */ +public class BlobOrm extends OrmChange{ + + /** + * If the field type passed in is byte, it will change it into blob as + * column type. + */ + @Override + public String object2Relation(String fieldType) { + if (fieldType != null) { + if (fieldType.equals("[B")) { + return "blob"; + } + } + return null; + } + +} diff --git a/YFDXJ/litepal/src/main/java/org/litepal/tablemanager/typechange/BooleanOrm.java b/YFDXJ/litepal/src/main/java/org/litepal/tablemanager/typechange/BooleanOrm.java new file mode 100644 index 0000000..519f0ef --- /dev/null +++ b/YFDXJ/litepal/src/main/java/org/litepal/tablemanager/typechange/BooleanOrm.java @@ -0,0 +1,41 @@ +/* + * Copyright (C) Tony Green, Litepal Framework Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.litepal.tablemanager.typechange; + +/** + * This class deals with boolean type. + * + * @author Tony Green + * @since 1.0 + */ +public class BooleanOrm extends OrmChange { + + /** + * If the field type passed in is boolean, it will change it into integer as + * column type. + */ + @Override + public String object2Relation(String fieldType) { + if (fieldType != null) { + if (fieldType.equals("boolean") || fieldType.equals("java.lang.Boolean")) { + return "integer"; + } + } + return null; + } + +} diff --git a/YFDXJ/litepal/src/main/java/org/litepal/tablemanager/typechange/DateOrm.java b/YFDXJ/litepal/src/main/java/org/litepal/tablemanager/typechange/DateOrm.java new file mode 100644 index 0000000..138d5a7 --- /dev/null +++ b/YFDXJ/litepal/src/main/java/org/litepal/tablemanager/typechange/DateOrm.java @@ -0,0 +1,41 @@ +/* + * Copyright (C) Tony Green, Litepal Framework Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.litepal.tablemanager.typechange; + +/** + * This class deals with date type. + * + * @author Tony Green + * @since 1.1 + */ +public class DateOrm extends OrmChange { + + /** + * If the field type passed in is Date, it will change it into integer as + * column type. + */ + @Override + public String object2Relation(String fieldType) { + if (fieldType != null) { + if (fieldType.equals("java.util.Date")) { + return "integer"; + } + } + return null; + } + +} \ No newline at end of file diff --git a/YFDXJ/litepal/src/main/java/org/litepal/tablemanager/typechange/DecimalOrm.java b/YFDXJ/litepal/src/main/java/org/litepal/tablemanager/typechange/DecimalOrm.java new file mode 100644 index 0000000..f0436ec --- /dev/null +++ b/YFDXJ/litepal/src/main/java/org/litepal/tablemanager/typechange/DecimalOrm.java @@ -0,0 +1,44 @@ +/* + * Copyright (C) Tony Green, Litepal Framework Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.litepal.tablemanager.typechange; + +/** + * This class deals with decimal type. + * + * @author Tony Green + * @since 1.0 + */ +public class DecimalOrm extends OrmChange { + + /** + * If the field type passed in is float or double, it will change it into + * real as column type. + */ + @Override + public String object2Relation(String fieldType) { + if (fieldType != null) { + if (fieldType.equals("float") || fieldType.equals("java.lang.Float")) { + return "real"; + } + if (fieldType.equals("double") || fieldType.equals("java.lang.Double")) { + return "real"; + } + } + return null; + } + +} diff --git a/YFDXJ/litepal/src/main/java/org/litepal/tablemanager/typechange/NumericOrm.java b/YFDXJ/litepal/src/main/java/org/litepal/tablemanager/typechange/NumericOrm.java new file mode 100644 index 0000000..37a9007 --- /dev/null +++ b/YFDXJ/litepal/src/main/java/org/litepal/tablemanager/typechange/NumericOrm.java @@ -0,0 +1,47 @@ +/* + * Copyright (C) Tony Green, Litepal Framework Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.litepal.tablemanager.typechange; + +/** + * This class deals with numeric type. + * + * @author Tony Green + * @since 1.0 + */ +public class NumericOrm extends OrmChange { + + /** + * If the field type passed in is int, long or short, it will change it into + * integer as column type. + */ + @Override + public String object2Relation(String fieldType) { + if (fieldType != null) { + if (fieldType.equals("int") || fieldType.equals("java.lang.Integer")) { + return "integer"; + } + if (fieldType.equals("long") || fieldType.equals("java.lang.Long")) { + return "integer"; + } + if (fieldType.equals("short") || fieldType.equals("java.lang.Short")) { + return "integer"; + } + } + return null; + } + +} diff --git a/YFDXJ/litepal/src/main/java/org/litepal/tablemanager/typechange/OrmChange.java b/YFDXJ/litepal/src/main/java/org/litepal/tablemanager/typechange/OrmChange.java new file mode 100644 index 0000000..72a965c --- /dev/null +++ b/YFDXJ/litepal/src/main/java/org/litepal/tablemanager/typechange/OrmChange.java @@ -0,0 +1,39 @@ +/* + * Copyright (C) Tony Green, Litepal Framework Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.litepal.tablemanager.typechange; + +/** + * This is abstract super class to map the object field types to database column + * types. The purpose of this class is to define a abstract method, and let all + * subclasses implement it. Each subclass deals with a kind of changing and each + * subclass will do their own logic to finish the changing job. + * + * @author Tony Green + * @since 1.0 + */ +public abstract class OrmChange { + + /** + * Subclasses implement this method to do their own logic to change types. + * + * @param fieldType + * The field type passed in. + * @return Column type. + */ + public abstract String object2Relation(String fieldType); + +} diff --git a/YFDXJ/litepal/src/main/java/org/litepal/tablemanager/typechange/TextOrm.java b/YFDXJ/litepal/src/main/java/org/litepal/tablemanager/typechange/TextOrm.java new file mode 100644 index 0000000..5fb819a --- /dev/null +++ b/YFDXJ/litepal/src/main/java/org/litepal/tablemanager/typechange/TextOrm.java @@ -0,0 +1,44 @@ +/* + * Copyright (C) Tony Green, Litepal Framework Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.litepal.tablemanager.typechange; + +/** + * This class deals with text type. + * + * @author Tony Green + * @since 1.0 + */ +public class TextOrm extends OrmChange { + + /** + * If the field type passed in is char or String, it will change it into + * text as column type. + */ + @Override + public String object2Relation(String fieldType) { + if (fieldType != null) { + if (fieldType.equals("char") || fieldType.equals("java.lang.Character")) { + return "text"; + } + if (fieldType.equals("java.lang.String")) { + return "text"; + } + } + return null; + } + +} diff --git a/YFDXJ/litepal/src/main/java/org/litepal/util/BaseUtility.java b/YFDXJ/litepal/src/main/java/org/litepal/util/BaseUtility.java new file mode 100644 index 0000000..6438e32 --- /dev/null +++ b/YFDXJ/litepal/src/main/java/org/litepal/util/BaseUtility.java @@ -0,0 +1,248 @@ +/* + * Copyright (C) Tony Green, LitePal Framework Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.litepal.util; + +import android.content.res.AssetManager; +import android.text.TextUtils; + +import org.litepal.LitePalApplication; +import org.litepal.exceptions.DataSupportException; +import org.litepal.parser.LitePalAttr; + +import java.io.IOException; +import java.util.Collection; +import java.util.Locale; + +/** + * A utility class to help LitePal with some base actions that might through any + * components. These actions can help classes just do the jobs they care, and + * help them out of the trivial work. + * + * @author Tony Green + * @since 1.0 + */ +public class BaseUtility { + + /** + * Disable to create an instance of BaseUtility. + */ + private BaseUtility() { + } + + /** + * It will change the case of the passing parameter into the case defined in + * litepal.xml file. + * + * @param string + * The string want to change case. + * @return The string after changing case. If the name is null, then simply + * return null. + */ + public static String changeCase(String string) { + if (string != null) { + LitePalAttr litePalAttr = LitePalAttr.getInstance(); + String cases = litePalAttr.getCases(); + if (Const.Config.CASES_KEEP.equals(cases)) { + return string; + } else if (Const.Config.CASES_UPPER.equals(cases)) { + return string.toUpperCase(Locale.US); + } + return string.toLowerCase(Locale.US); + } + return null; + } + + /** + * This helper method makes up the shortage of contains method in Collection + * to support the function of case insensitive contains. It only supports + * the String generic type of collection, cause other types have no cases + * concept. + * + * @param collection + * The collection contains string data. + * @param string + * The string want to look for in the collection. + * @return If the string is in the collection without case concern return + * true, otherwise return false. If the collection is null, return + * false. + */ + public static boolean containsIgnoreCases(Collection collection, String string) { + if (collection == null) { + return false; + } + if (string == null) { + return collection.contains(null); + } + boolean contains = false; + for (String element : collection) { + if (string.equalsIgnoreCase(element)) { + contains = true; + break; + } + } + return contains; + } + + /** + * Capitalize make the first letter of the word be upper case. + * + * @param string + * The word to capitalize. + * @return The word after capitalize. + */ + public static String capitalize(String string) { + if (!TextUtils.isEmpty(string)) { + return string.substring(0, 1).toUpperCase(Locale.US) + string.substring(1); + } + return string == null ? null : ""; + } + + /** + * Count how many marks existed in string. + * + * @param string + * The source sentence. + * @param mark + * The specific substring to count. + * @return The number of marks existed in string. + */ + public static int count(String string, String mark) { + if (!TextUtils.isEmpty(string) && !TextUtils.isEmpty(mark)) { + int count = 0; + int index = string.indexOf(mark); + while (index != -1) { + count++; + string = string.substring(index + mark.length()); + index = string.indexOf(mark); + } + return count; + } + return 0; + } + + /** + * Check the number of question mark existed in conditions[0] equals the + * number of rest conditions elements or not. If not equals, throws + * DataSupportException. + * + * @param conditions + * A string array representing the WHERE part of an SQL + * statement. + * @throws org.litepal.exceptions.DataSupportException + */ + public static void checkConditionsCorrect(String... conditions) { + if (conditions != null) { + int conditionsSize = conditions.length; + if (conditionsSize > 0) { + String whereClause = conditions[0]; + int placeHolderSize = BaseUtility.count(whereClause, "?"); + if (conditionsSize != placeHolderSize + 1) { + throw new DataSupportException(DataSupportException.UPDATE_CONDITIONS_EXCEPTION); + } + } + } + } + + /** + * Judge a field type is supported or not. Currently only basic data types + * and String are supported. + * + * @param fieldType + * Type of the field. + * @return Supported return true, not supported return false. + */ + public static boolean isFieldTypeSupported(String fieldType) { + if ("boolean".equals(fieldType) || "java.lang.Boolean".equals(fieldType)) { + return true; + } + if ("float".equals(fieldType) || "java.lang.Float".equals(fieldType)) { + return true; + } + if ("double".equals(fieldType) || "java.lang.Double".equals(fieldType)) { + return true; + } + if ("int".equals(fieldType) || "java.lang.Integer".equals(fieldType)) { + return true; + } + if ("long".equals(fieldType) || "java.lang.Long".equals(fieldType)) { + return true; + } + if ("short".equals(fieldType) || "java.lang.Short".equals(fieldType)) { + return true; + } + if ("char".equals(fieldType) || "java.lang.Character".equals(fieldType)) { + return true; + } + if ("[B".equals(fieldType) || "[Ljava.lang.Byte;".equals(fieldType)) { + return true; + } + if ("java.lang.String".equals(fieldType) || "java.util.Date".equals(fieldType)) { + return true; + } + return false; + } + + /** + * Judge a generic type is supported or not. Currently only basic data types + * and String are supported. + * + * @param genericType + * Type of the generic field. + * @return Supported return true, not supported return false. + */ + public static boolean isGenericTypeSupported(String genericType) { + if ("java.lang.String".equals(genericType)) { + return true; + } else if ("java.lang.Integer".equals(genericType)) { + return true; + } else if ("java.lang.Float".equals(genericType)) { + return true; + } else if ("java.lang.Double".equals(genericType)) { + return true; + } else if ("java.lang.Long".equals(genericType)) { + return true; + } else if ("java.lang.Short".equals(genericType)) { + return true; + } else if ("java.lang.Boolean".equals(genericType)) { + return true; + } else if ("java.lang.Character".equals(genericType)) { + return true; + } + return false; + } + + /** + * If the litepal.xml configuration file exists. + * @return True if exists, false otherwise. + */ + public static boolean isLitePalXMLExists() { + try { + AssetManager assetManager = LitePalApplication.getContext().getAssets(); + String[] fileNames = assetManager.list(""); + if (fileNames != null && fileNames.length > 0) { + for (String fileName : fileNames) { + if (Const.Config.CONFIGURATION_FILE_NAME.equalsIgnoreCase(fileName)) { + return true; + } + } + } + } catch (IOException e) { + } + return false; + } + +} diff --git a/YFDXJ/litepal/src/main/java/org/litepal/util/Const.java b/YFDXJ/litepal/src/main/java/org/litepal/util/Const.java new file mode 100644 index 0000000..4459c45 --- /dev/null +++ b/YFDXJ/litepal/src/main/java/org/litepal/util/Const.java @@ -0,0 +1,97 @@ +/* + * Copyright (C) Tony Green, LitePal Framework Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.litepal.util; + +public interface Const { + + public interface Model { + /** + * One2One constant value. + */ + public static final int ONE_TO_ONE = 1; + + /** + * Many2One constant value. + */ + public static final int MANY_TO_ONE = 2; + + /** + * Many2Many constant value. + */ + public static final int MANY_TO_MANY = 3; + } + + public interface Config { + /** + * The suffix for each database file. + */ + public static final String DB_NAME_SUFFIX = ".db"; + + /** + * Constant for upper case. + */ + public static final String CASES_UPPER = "upper"; + + /** + * Constant for lower case. + */ + public static final String CASES_LOWER = "lower"; + + /** + * Constant for keep case. + */ + public static final String CASES_KEEP = "keep"; + + /** + * Constant configuration file name. + */ + public static final String CONFIGURATION_FILE_NAME = "litepal.xml"; + } + + public interface TableSchema { + /** + * Table name in database. + */ + public static final String TABLE_NAME = "table_schema"; + + /** + * The name column in table_schema. + */ + public static final String COLUMN_NAME = "name"; + + /** + * The type column in table_schema. + */ + public static final String COLUMN_TYPE = "type"; + + /** + * Constant for normal table. + */ + public static final int NORMAL_TABLE = 0; + + /** + * Constant for intermediate join table. + */ + public static final int INTERMEDIATE_JOIN_TABLE = 1; + + /** + * Constant for generic table. + */ + public static final int GENERIC_TABLE = 2; + } + +} diff --git a/YFDXJ/litepal/src/main/java/org/litepal/util/DBUtility.java b/YFDXJ/litepal/src/main/java/org/litepal/util/DBUtility.java new file mode 100644 index 0000000..82ac901 --- /dev/null +++ b/YFDXJ/litepal/src/main/java/org/litepal/util/DBUtility.java @@ -0,0 +1,571 @@ +/* + * Copyright (C) Tony Green, LitePal Framework Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.litepal.util; + +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; +import android.text.TextUtils; + +import org.litepal.exceptions.DatabaseGenerateException; +import org.litepal.tablemanager.model.ColumnModel; +import org.litepal.tablemanager.model.TableModel; + +import java.util.ArrayList; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * A utility class to help LitePal with some database actions. These actions can + * help classes just do the jobs they care, and help them out of the trivial + * work. + * + * @author Tony + * @since 1.0 + */ +public class DBUtility { + + private static final String TAG = "DBUtility"; + + private static final String SQLITE_KEYWORDS = ",abort,add,after,all,alter,and,as,asc,autoincrement,before,begin,between,by,cascade,check,collate,column,commit,conflict,constraint,create,cross,database,deferrable,deferred,delete,desc,distinct,drop,each,end,escape,except,exclusive,exists,foreign,from,glob,group,having,in,index,inner,insert,intersect,into,is,isnull,join,like,limit,match,natural,not,notnull,null,of,offset,on,or,order,outer,plan,pragma,primary,query,raise,references,regexp,reindex,release,rename,replace,restrict,right,rollback,row,savepoint,select,set,table,temp,temporary,then,to,transaction,trigger,union,unique,update,using,vacuum,values,view,virtual,when,where,"; + + private static final String KEYWORDS_COLUMN_SUFFIX = "_lpcolumn"; + + private static final String REG_OPERATOR = "\\s*(=|!=|<>|<|>)"; + + private static final String REG_FUZZY = "\\s+(not\\s+)?(like|between)\\s+"; + + private static final String REG_COLLECTION = "\\s+(not\\s+)?(in|exists)\\s*\\("; + + /** + * Disable to create an instance of DBUtility. + */ + private DBUtility() { + } + + /** + * Get the corresponding table name by the full class name with package. It + * will only get the short class name without package name as table name. + * + * @param className + * Full class name with package. + * @return Return the table name by getting the short class name. Return + * null if the class name is null or invalid. + * + */ + public static String getTableNameByClassName(String className) { + if (!TextUtils.isEmpty(className)) { + if ('.' == className.charAt(className.length() - 1)) { + return null; + } else { + return className.substring(className.lastIndexOf(".") + 1); + } + } + return null; + } + + /** + * Get the corresponding table name list by the full class name list with + * package. Each table name will only get the short class name without + * package. + * + * @param classNames + * The list of full class name with package. + * @return Return the table name list. + */ + public static List getTableNameListByClassNameList(List classNames) { + List tableNames = new ArrayList(); + if (classNames != null && !classNames.isEmpty()) { + for (String className : classNames) { + tableNames.add(getTableNameByClassName(className)); + } + } + return tableNames; + } + + /** + * Get table name by the given foreign column name. + * + * @param foreignColumnName + * The foreign column name. Standard pattern is tablename_id. + * @return The table name. Return null if the given foreign column is null, + * empty or invalid. + */ + public static String getTableNameByForeignColumn(String foreignColumnName) { + if (!TextUtils.isEmpty(foreignColumnName)) { + if (foreignColumnName.toLowerCase().endsWith("_id")) { + return foreignColumnName.substring(0, foreignColumnName.length() - "_id".length()); + } + return null; + } + return null; + } + + /** + * Create intermediate join table name by the concatenation of the two + * target table names in alphabetical order with underline in the middle. + * + * @param tableName + * First table name. + * @param associatedTableName + * The associated table name. + * @return The table name by the concatenation of the two target table names + * in alphabetical order with underline in the middle. If the table + * name or associated table name is null of empty, return null. + */ + public static String getIntermediateTableName(String tableName, String associatedTableName) { + if (!(TextUtils.isEmpty(tableName) || TextUtils.isEmpty(associatedTableName))) { + String intermediateTableName; + if (tableName.toLowerCase().compareTo(associatedTableName.toLowerCase()) <= 0) { + intermediateTableName = tableName + "_" + associatedTableName; + } else { + intermediateTableName = associatedTableName + "_" + tableName; + } + return intermediateTableName; + } + return null; + } + + /** + * Create generic table name by the concatenation of the class model's table name and simple + * generic type name with underline in the middle. + * @param className + * Name of the class model. + * @param fieldName + * Name of the generic type field. + * @return Table name by the concatenation of the class model's table name and simple + * generic type name with underline in the middle. + */ + public static String getGenericTableName(String className, String fieldName) { + String tableName = getTableNameByClassName(className); + return BaseUtility.changeCase(tableName + "_" + fieldName); + } + + /** + * The column name for referenced id in generic table. + * @param className + * Name of the class model. + * @return The column name for referenced id in generic table. + */ + public static String getGenericValueIdColumnName(String className) { + return BaseUtility.changeCase(getTableNameByClassName(className) + "_id"); + } + + /** + * Judge the table name is an intermediate table or not. + * + * @param tableName + * Table name in database. + * @return Return true if the table name is an intermediate table. Otherwise + * return false. + */ + public static boolean isIntermediateTable(String tableName, SQLiteDatabase db) { + if (!TextUtils.isEmpty(tableName)) { + if (tableName.matches("[0-9a-zA-Z]+_[0-9a-zA-Z]+")) { + Cursor cursor = null; + try { + cursor = db.query(Const.TableSchema.TABLE_NAME, null, null, null, null, null, + null); + if (cursor.moveToFirst()) { + do { + String tableNameDB = cursor.getString(cursor + .getColumnIndexOrThrow(Const.TableSchema.COLUMN_NAME)); + if (tableName.equalsIgnoreCase(tableNameDB)) { + int tableType = cursor.getInt(cursor + .getColumnIndexOrThrow(Const.TableSchema.COLUMN_TYPE)); + if (tableType == Const.TableSchema.INTERMEDIATE_JOIN_TABLE) { + return true; + } + break; + } + } while (cursor.moveToNext()); + } + } catch (Exception e) { + e.printStackTrace(); + } finally { + if (cursor != null) { + cursor.close(); + } + } + } + } + return false; + } + + /** + * Judge the table name is an generic table or not. + * + * @param tableName + * Table name in database. + * @return Return true if the table name is an generic table. Otherwise + * return false. + */ + public static boolean isGenericTable(String tableName, SQLiteDatabase db) { + if (!TextUtils.isEmpty(tableName)) { + if (tableName.matches("[0-9a-zA-Z]+_[0-9a-zA-Z]+")) { + Cursor cursor = null; + try { + cursor = db.query(Const.TableSchema.TABLE_NAME, null, null, null, null, null, + null); + if (cursor.moveToFirst()) { + do { + String tableNameDB = cursor.getString(cursor + .getColumnIndexOrThrow(Const.TableSchema.COLUMN_NAME)); + if (tableName.equalsIgnoreCase(tableNameDB)) { + int tableType = cursor.getInt(cursor + .getColumnIndexOrThrow(Const.TableSchema.COLUMN_TYPE)); + if (tableType == Const.TableSchema.GENERIC_TABLE) { + return true; + } + break; + } + } while (cursor.moveToNext()); + } + } catch (Exception e) { + e.printStackTrace(); + } finally { + if (cursor != null) { + cursor.close(); + } + } + } + } + return false; + } + + /** + * Test if the table name passed in exists in the database. Cases are + * ignored. + * + * @param tableName + * The table name. + * @return Return true if there's already a same name table exist, otherwise + * return false. + */ + public static boolean isTableExists(String tableName, SQLiteDatabase db) { + boolean exist; + try { + exist = BaseUtility.containsIgnoreCases(findAllTableNames(db), tableName); + } catch (Exception e) { + e.printStackTrace(); + exist = false; + } + return exist; + } + + /** + * Test if a column exists in a table. Cases are ignored. + * + * @param columnName + * The column name. + * @param tableName + * The table name. + * @param db + * Instance of SQLiteDatabase. + * @return If there's a column named as the column name passed in, return + * true. Or return false. If any of the passed in parameters is null + * or empty, return false. + */ + public static boolean isColumnExists(String columnName, String tableName, SQLiteDatabase db) { + if (TextUtils.isEmpty(columnName) || TextUtils.isEmpty(tableName)) { + return false; + } + boolean exist = false; + Cursor cursor = null; + try { + String checkingColumnSQL = "pragma table_info(" + tableName + ")"; + cursor = db.rawQuery(checkingColumnSQL, null); + if (cursor.moveToFirst()) { + do { + String name = cursor.getString(cursor.getColumnIndexOrThrow("name")); + if (columnName.equalsIgnoreCase(name)) { + exist = true; + break; + } + } while (cursor.moveToNext()); + } + } catch (Exception e) { + e.printStackTrace(); + exist = false; + } finally { + if (cursor != null) { + cursor.close(); + } + } + return exist; + } + + /** + * Find all table names in the database. If there's some wrong happens when + * finding tables, it will throw exceptions. + * + * @param db + * Instance of SQLiteDatabase. + * @return A list with all table names. + * @throws org.litepal.exceptions.DatabaseGenerateException + */ + public static List findAllTableNames(SQLiteDatabase db) { + List tableNames = new ArrayList(); + Cursor cursor = null; + try { + cursor = db.rawQuery("select * from sqlite_master where type = ?", new String[] { "table" }); + if (cursor.moveToFirst()) { + do { + String tableName = cursor.getString(cursor.getColumnIndexOrThrow("tbl_name")); + if (!tableNames.contains(tableName)) { + tableNames.add(tableName); + } + } while (cursor.moveToNext()); + } + } catch (Exception e) { + e.printStackTrace(); + throw new DatabaseGenerateException(e.getMessage()); + } finally { + if (cursor != null) { + cursor.close(); + } + } + return tableNames; + } + + /** + * Look from the database to find a table named same as the table name in + * table model. Then iterate the columns and types of this table to create a + * new instance of table model. If there's no such a table in the database, + * then throw DatabaseGenerateException. + * + * @param tableName + * Table name. + * @param db + * Instance of SQLiteDatabase. + * @return A table model object with values from database table. + * @throws org.litepal.exceptions.DatabaseGenerateException + */ + public static TableModel findPragmaTableInfo(String tableName, SQLiteDatabase db) { + if (isTableExists(tableName, db)) { + List uniqueColumns = findUniqueColumns(tableName, db); + TableModel tableModelDB = new TableModel(); + tableModelDB.setTableName(tableName); + String checkingColumnSQL = "pragma table_info(" + tableName + ")"; + Cursor cursor = null; + try { + cursor = db.rawQuery(checkingColumnSQL, null); + if (cursor.moveToFirst()) { + do { + ColumnModel columnModel = new ColumnModel(); + String name = cursor.getString(cursor.getColumnIndexOrThrow("name")); + String type = cursor.getString(cursor.getColumnIndexOrThrow("type")); + boolean nullable = cursor.getInt(cursor.getColumnIndexOrThrow("notnull")) != 1; + boolean unique = uniqueColumns.contains(name); + String defaultValue = cursor.getString(cursor.getColumnIndexOrThrow("dflt_value")); + columnModel.setColumnName(name); + columnModel.setColumnType(type); + columnModel.setNullable(nullable); + columnModel.setUnique(unique); + if (defaultValue != null) { + defaultValue = defaultValue.replace("'", ""); + } else { + defaultValue = ""; + } + columnModel.setDefaultValue(defaultValue); + tableModelDB.addColumnModel(columnModel); + } while (cursor.moveToNext()); + } + } catch (Exception e) { + e.printStackTrace(); + throw new DatabaseGenerateException(e.getMessage()); + } finally { + if (cursor != null) { + cursor.close(); + } + } + return tableModelDB; + } else { + throw new DatabaseGenerateException( + DatabaseGenerateException.TABLE_DOES_NOT_EXIST_WHEN_EXECUTING + tableName); + } + } + + /** + * Find all unique column names of specified table. + * @param tableName + * The table to find unique columns. + * @param db + * Instance of SQLiteDatabase. + * @return A list with all unique column names of specified table. + */ + public static List findUniqueColumns(String tableName, SQLiteDatabase db) { + List columns = new ArrayList(); + Cursor cursor = null; + Cursor innerCursor = null; + try { + cursor = db.rawQuery("pragma index_list(" + tableName +")", null); + if (cursor.moveToFirst()) { + do { + int unique = cursor.getInt(cursor.getColumnIndexOrThrow("unique")); + if (unique == 1) { + String name = cursor.getString(cursor.getColumnIndexOrThrow("name")); + innerCursor = db.rawQuery("pragma index_info(" + name + ")", null); + if (innerCursor.moveToFirst()) { + String columnName = innerCursor.getString(innerCursor.getColumnIndexOrThrow("name")); + columns.add(columnName); + } + } + } while (cursor.moveToNext()); + } + } catch (Exception e) { + e.printStackTrace(); + throw new DatabaseGenerateException(e.getMessage()); + } finally { + if (cursor != null) { + cursor.close(); + } + if (innerCursor != null) { + innerCursor.close(); + } + } + return columns; + } + + /** + * If the field name is conflicted with SQLite keywords. Return true if conflicted, return false + * otherwise. + * @param fieldName + * Name of the field. + * @return True if conflicted, false otherwise. + */ + public static boolean isFieldNameConflictWithSQLiteKeywords(String fieldName) { + if (!TextUtils.isEmpty(fieldName)) { + String fieldNameWithComma = "," + fieldName.toLowerCase() + ","; + if (SQLITE_KEYWORDS.contains(fieldNameWithComma)) { + return true; + } + } + return false; + } + + /** + * Convert the passed in name to valid column name if the name is conflicted with SQLite keywords. + * The convert rule is to append {@link #KEYWORDS_COLUMN_SUFFIX} to the name as new column name. + * @param columnName + * Original column name. + * @return Converted name as new column name if conflicted with SQLite keywords. + */ + public static String convertToValidColumnName(String columnName) { + if (isFieldNameConflictWithSQLiteKeywords(columnName)) { + return columnName + KEYWORDS_COLUMN_SUFFIX; + } + return columnName; + } + + /** + * Convert the where clause if it contains invalid column names which conflict with SQLite keywords. + * @param whereClause + * where clause for query, update or delete. + * @return Converted where clause with valid column names. + */ + public static String convertWhereClauseToColumnName(String whereClause) { + try { + StringBuffer convertedWhereClause = new StringBuffer(); + Pattern p = Pattern.compile("(\\w+" + REG_OPERATOR + "|\\w+" + REG_FUZZY + "|\\w+" + REG_COLLECTION + ")"); + Matcher m = p.matcher(whereClause); + while (m.find()) { + String matches = m.group(); + String column = matches.replaceAll("(" + REG_OPERATOR + "|" + REG_FUZZY + "|" + REG_COLLECTION + ")", ""); + String rest = matches.replace(column, ""); + column = convertToValidColumnName(column); + m.appendReplacement(convertedWhereClause, column + rest); + } + m.appendTail(convertedWhereClause); + return convertedWhereClause.toString(); + } catch (Exception e) { + e.printStackTrace(); + } + return whereClause; + } + + /** + * Convert the select clause if it contains invalid column names which conflict with SQLite keywords. + * @param columns + * A String array of which columns to return. Passing null will + * return all columns. + * @return Converted select clause with valid column names. + */ + public static String[] convertSelectClauseToValidNames(String[] columns) { + if (columns != null && columns.length > 0) { + String[] convertedColumns = new String[columns.length]; + for (int i = 0; i < columns.length; i++) { + convertedColumns[i] = convertToValidColumnName(columns[i]); + } + return convertedColumns; + } + return null; + } + + /** + * Convert the order by clause if it contains invalid column names which conflict with SQLite keywords. + * @param orderBy + * How to order the rows, formatted as an SQL ORDER BY clause. Passing null will use + * the default sort order, which may be unordered. + * @return Converted order by clause with valid column names. + */ + public static String convertOrderByClauseToValidName(String orderBy) { + if (!TextUtils.isEmpty(orderBy)) { + orderBy = orderBy.trim().toLowerCase(); + if (orderBy.contains(",")) { + String[] orderByItems = orderBy.split(","); + StringBuilder builder = new StringBuilder(); + boolean needComma = false; + for (String orderByItem : orderByItems) { + if (needComma) { + builder.append(","); + } + builder.append(convertOrderByItem(orderByItem)); + needComma = true; + } + orderBy = builder.toString(); + } else { + orderBy = convertOrderByItem(orderBy); + } + return orderBy; + } + return null; + } + + /** + * Convert the order by item if it is invalid column name which conflict with SQLite keywords. + * @param orderByItem + * The single order by condition. + * @return Converted order by item with valid column name. + */ + private static String convertOrderByItem(String orderByItem) { + String column = null; + String append = null; + if (orderByItem.endsWith("asc")) { + column = orderByItem.replace("asc", "").trim(); + append = " asc"; + } else if (orderByItem.endsWith("desc")) { + column = orderByItem.replace("desc", "").trim(); + append = " desc"; + } else { + column = orderByItem; + append = ""; + } + return convertToValidColumnName(column) + append; + } + +} diff --git a/YFDXJ/litepal/src/main/java/org/litepal/util/LogUtil.java b/YFDXJ/litepal/src/main/java/org/litepal/util/LogUtil.java new file mode 100644 index 0000000..9714efd --- /dev/null +++ b/YFDXJ/litepal/src/main/java/org/litepal/util/LogUtil.java @@ -0,0 +1,41 @@ +/* + * Copyright (C) Tony Green, LitePal Framework Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.litepal.util; + +import android.util.Log; + +public final class LogUtil { + + public static final int DEBUG = 2; + + public static final int ERROR = 5; + + public static int level = ERROR; + + public static void d(String tagName, String message) { + if (level <= DEBUG) { + Log.d(tagName, message); + } + } + + public static void e(String tagName, Exception e){ + if (level <= ERROR) { + Log.e(tagName, e.getMessage(), e); + } + } + +} \ No newline at end of file diff --git a/YFDXJ/litepal/src/main/java/org/litepal/util/SharedUtil.java b/YFDXJ/litepal/src/main/java/org/litepal/util/SharedUtil.java new file mode 100644 index 0000000..97f5241 --- /dev/null +++ b/YFDXJ/litepal/src/main/java/org/litepal/util/SharedUtil.java @@ -0,0 +1,99 @@ +/* + * Copyright (C) Tony Green, LitePal Framework Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.litepal.util; + +import org.litepal.LitePalApplication; + +import android.content.Context; +import android.content.SharedPreferences; +import android.text.TextUtils; + +/** + * LitePal used shared preferences a lot for storing versions and a lot of other + * necessary values. This utility helps LitePal save and read each key-value + * pairs from shared preferences file. + * + * @author Tony Green + * @since 1.0 + */ +public class SharedUtil { + + private static final String VERSION = "litepal_version"; + + private static final String LITEPAL_PREPS = "litepal_prefs"; + + /** + * Each time database upgrade, the version of database stored in shared + * preference will update. + * @param extraKeyName + * Pass the name of the using database usually. Pass null if it's default database. + * @param newVersion + * new version of database + */ + public static void updateVersion(String extraKeyName, int newVersion) { + SharedPreferences.Editor sEditor = LitePalApplication.getContext() + .getSharedPreferences(LITEPAL_PREPS, Context.MODE_PRIVATE).edit(); + if (TextUtils.isEmpty(extraKeyName)) { + sEditor.putInt(VERSION, newVersion); + } else { + if (extraKeyName.endsWith(Const.Config.DB_NAME_SUFFIX)) { + extraKeyName = extraKeyName.replace(Const.Config.DB_NAME_SUFFIX, ""); + } + sEditor.putInt(VERSION + "_" + extraKeyName, newVersion); + } + sEditor.apply(); + } + + /** + * Get the last database version. + * @param extraKeyName + * Pass the name of the using database usually. Pass null if it's default database. + * @return the last database version + */ + public static int getLastVersion(String extraKeyName) { + SharedPreferences sPref = LitePalApplication.getContext().getSharedPreferences( + LITEPAL_PREPS, Context.MODE_PRIVATE); + if (TextUtils.isEmpty(extraKeyName)) { + return sPref.getInt(VERSION, 0); + } else { + if (extraKeyName.endsWith(Const.Config.DB_NAME_SUFFIX)) { + extraKeyName = extraKeyName.replace(Const.Config.DB_NAME_SUFFIX, ""); + } + return sPref.getInt(VERSION + "_" + extraKeyName, 0); + } + } + + /** + * Remove the version with specified extra key name. + * @param extraKeyName + * Pass the name of the using database usually. Pass null if it's default database. + */ + public static void removeVersion(String extraKeyName) { + SharedPreferences.Editor sEditor = LitePalApplication.getContext() + .getSharedPreferences(LITEPAL_PREPS, Context.MODE_PRIVATE).edit(); + if (TextUtils.isEmpty(extraKeyName)) { + sEditor.remove(VERSION); + } else { + if (extraKeyName.endsWith(Const.Config.DB_NAME_SUFFIX)) { + extraKeyName = extraKeyName.replace(Const.Config.DB_NAME_SUFFIX, ""); + } + sEditor.remove(VERSION + "_" + extraKeyName); + } + sEditor.apply(); + } + +} diff --git a/YFDXJ/litepal/src/main/res/values/strings.xml b/YFDXJ/litepal/src/main/res/values/strings.xml new file mode 100644 index 0000000..302c343 --- /dev/null +++ b/YFDXJ/litepal/src/main/res/values/strings.xml @@ -0,0 +1,3 @@ + + LitePal + diff --git a/YFDXJ/luban/build.gradle b/YFDXJ/luban/build.gradle new file mode 100644 index 0000000..4db413e --- /dev/null +++ b/YFDXJ/luban/build.gradle @@ -0,0 +1,11 @@ +apply plugin: 'com.android.library' + +android { + compileSdk 34 + defaultConfig { + minSdk 24 + targetSdk 34 + testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" + } + namespace 'top.zibin.luban' +} \ No newline at end of file diff --git a/YFDXJ/luban/src/androidTest/java/top/zibin/luban/ApplicationTest.java b/YFDXJ/luban/src/androidTest/java/top/zibin/luban/ApplicationTest.java new file mode 100644 index 0000000..9420579 --- /dev/null +++ b/YFDXJ/luban/src/androidTest/java/top/zibin/luban/ApplicationTest.java @@ -0,0 +1,13 @@ +package top.zibin.luban; + +import android.app.Application; +import android.test.ApplicationTestCase; + +/** + * Testing Fundamentals + */ +public class ApplicationTest extends ApplicationTestCase { + public ApplicationTest() { + super(Application.class); + } +} \ No newline at end of file diff --git a/YFDXJ/luban/src/main/AndroidManifest.xml b/YFDXJ/luban/src/main/AndroidManifest.xml new file mode 100644 index 0000000..0a0938a --- /dev/null +++ b/YFDXJ/luban/src/main/AndroidManifest.xml @@ -0,0 +1,3 @@ + + + diff --git a/YFDXJ/luban/src/main/java/top/zibin/luban/Checker.java b/YFDXJ/luban/src/main/java/top/zibin/luban/Checker.java new file mode 100644 index 0000000..698a4a8 --- /dev/null +++ b/YFDXJ/luban/src/main/java/top/zibin/luban/Checker.java @@ -0,0 +1,200 @@ +package top.zibin.luban; + +import android.graphics.BitmapFactory; +import android.util.Log; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.util.Arrays; + +enum Checker { + SINGLE; + + private static final String TAG = "Luban"; + + private static final String JPG = ".jpg"; + + private final byte[] JPEG_SIGNATURE = new byte[]{(byte) 0xFF, (byte) 0xD8, (byte) 0xFF}; + + /** + * Determine if it is JPG. + * + * @param is image file input stream + */ + boolean isJPG(InputStream is) { + return isJPG(toByteArray(is)); + } + + /** + * Returns the degrees in clockwise. Values are 0, 90, 180, or 270. + */ + int getOrientation(InputStream is) { + return getOrientation(toByteArray(is)); + } + + private boolean isJPG(byte[] data) { + if (data == null || data.length < 3) { + return false; + } + byte[] signatureB = new byte[]{data[0], data[1], data[2]}; + return Arrays.equals(JPEG_SIGNATURE, signatureB); + } + + private int getOrientation(byte[] jpeg) { + if (jpeg == null) { + return 0; + } + + int offset = 0; + int length = 0; + + // ISO/IEC 10918-1:1993(E) + while (offset + 3 < jpeg.length && (jpeg[offset++] & 0xFF) == 0xFF) { + int marker = jpeg[offset] & 0xFF; + + // Check if the marker is a padding. + if (marker == 0xFF) { + continue; + } + offset++; + + // Check if the marker is SOI or TEM. + if (marker == 0xD8 || marker == 0x01) { + continue; + } + // Check if the marker is EOI or SOS. + if (marker == 0xD9 || marker == 0xDA) { + break; + } + + // Get the length and check if it is reasonable. + length = pack(jpeg, offset, 2, false); + if (length < 2 || offset + length > jpeg.length) { + Log.e(TAG, "Invalid length"); + return 0; + } + + // Break if the marker is EXIF in APP1. + if (marker == 0xE1 && length >= 8 + && pack(jpeg, offset + 2, 4, false) == 0x45786966 + && pack(jpeg, offset + 6, 2, false) == 0) { + offset += 8; + length -= 8; + break; + } + + // Skip other markers. + offset += length; + length = 0; + } + + // JEITA CP-3451 Exif Version 2.2 + if (length > 8) { + // Identify the byte order. + int tag = pack(jpeg, offset, 4, false); + if (tag != 0x49492A00 && tag != 0x4D4D002A) { + Log.e(TAG, "Invalid byte order"); + return 0; + } + boolean littleEndian = (tag == 0x49492A00); + + // Get the offset and check if it is reasonable. + int count = pack(jpeg, offset + 4, 4, littleEndian) + 2; + if (count < 10 || count > length) { + Log.e(TAG, "Invalid offset"); + return 0; + } + offset += count; + length -= count; + + // Get the count and go through all the elements. + count = pack(jpeg, offset - 2, 2, littleEndian); + while (count-- > 0 && length >= 12) { + // Get the tag and check if it is orientation. + tag = pack(jpeg, offset, 2, littleEndian); + if (tag == 0x0112) { + int orientation = pack(jpeg, offset + 8, 2, littleEndian); + switch (orientation) { + case 1: + return 0; + case 3: + return 180; + case 6: + return 90; + case 8: + return 270; + } + Log.e(TAG, "Unsupported orientation"); + return 0; + } + offset += 12; + length -= 12; + } + } + + Log.e(TAG, "Orientation not found"); + return 0; + } + + String extSuffix(InputStreamProvider input) { + try { + BitmapFactory.Options options = new BitmapFactory.Options(); + options.inJustDecodeBounds = true; + BitmapFactory.decodeStream(input.open(), null, options); + return options.outMimeType.replace("image/", "."); + } catch (Exception e) { + return JPG; + } + } + + boolean needCompress(int leastCompressSize, String path) { + if (leastCompressSize > 0) { + File source = new File(path); + return source.exists() && source.length() > (leastCompressSize << 10); + } + return true; + } + + private int pack(byte[] bytes, int offset, int length, boolean littleEndian) { + int step = 1; + if (littleEndian) { + offset += length - 1; + step = -1; + } + + int value = 0; + while (length-- > 0) { + value = (value << 8) | (bytes[offset] & 0xFF); + offset += step; + } + return value; + } + + private byte[] toByteArray(InputStream is) { + if (is == null) { + return new byte[0]; + } + + ByteArrayOutputStream buffer = new ByteArrayOutputStream(); + + int read; + byte[] data = new byte[4096]; + + try { + while ((read = is.read(data, 0, data.length)) != -1) { + buffer.write(data, 0, read); + } + } catch (Exception ignored) { + return new byte[0]; + } finally { + try { + buffer.close(); + } catch (IOException ignored) { + } + } + + return buffer.toByteArray(); + } +} diff --git a/YFDXJ/luban/src/main/java/top/zibin/luban/CompressionPredicate.java b/YFDXJ/luban/src/main/java/top/zibin/luban/CompressionPredicate.java new file mode 100644 index 0000000..7d45bdc --- /dev/null +++ b/YFDXJ/luban/src/main/java/top/zibin/luban/CompressionPredicate.java @@ -0,0 +1,19 @@ +package top.zibin.luban; + +/** + * Created on 2018/1/3 19:43 + * + * @author andy + * + * A functional interface (callback) that returns true or false for the given input path should be compressed. + */ + +public interface CompressionPredicate { + + /** + * Determine the given input path should be compressed and return a boolean. + * @param path input path + * @return the boolean result + */ + boolean apply(String path); +} diff --git a/YFDXJ/luban/src/main/java/top/zibin/luban/Engine.java b/YFDXJ/luban/src/main/java/top/zibin/luban/Engine.java new file mode 100644 index 0000000..1d6b984 --- /dev/null +++ b/YFDXJ/luban/src/main/java/top/zibin/luban/Engine.java @@ -0,0 +1,90 @@ +package top.zibin.luban; + +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.Matrix; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; + +/** + * Responsible for starting compress and managing active and cached resources. + */ +class Engine { + private InputStreamProvider srcImg; + private File tagImg; + private int srcWidth; + private int srcHeight; + private boolean focusAlpha; + + Engine(InputStreamProvider srcImg, File tagImg, boolean focusAlpha) throws IOException { + this.tagImg = tagImg; + this.srcImg = srcImg; + this.focusAlpha = focusAlpha; + + BitmapFactory.Options options = new BitmapFactory.Options(); + options.inJustDecodeBounds = true; + options.inSampleSize = 1; + + BitmapFactory.decodeStream(srcImg.open(), null, options); + this.srcWidth = options.outWidth; + this.srcHeight = options.outHeight; + } + + private int computeSize() { + srcWidth = srcWidth % 2 == 1 ? srcWidth + 1 : srcWidth; + srcHeight = srcHeight % 2 == 1 ? srcHeight + 1 : srcHeight; + + int longSide = Math.max(srcWidth, srcHeight); + int shortSide = Math.min(srcWidth, srcHeight); + + float scale = ((float) shortSide / longSide); + if (scale <= 1 && scale > 0.5625) { + if (longSide < 1664) { + return 1; + } else if (longSide < 4990) { + return 2; + } else if (longSide > 4990 && longSide < 10240) { + return 4; + } else { + return longSide / 1280 == 0 ? 1 : longSide / 1280; + } + } else if (scale <= 0.5625 && scale > 0.5) { + return longSide / 1280 == 0 ? 1 : longSide / 1280; + } else { + return (int) Math.ceil(longSide / (1280.0 / scale)); + } + } + + private Bitmap rotatingImage(Bitmap bitmap, int angle) { + Matrix matrix = new Matrix(); + + matrix.postRotate(angle); + + return Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true); + } + + File compress() throws IOException { + BitmapFactory.Options options = new BitmapFactory.Options(); + options.inSampleSize = computeSize(); + + Bitmap tagBitmap = BitmapFactory.decodeStream(srcImg.open(), null, options); + ByteArrayOutputStream stream = new ByteArrayOutputStream(); + + if (Checker.SINGLE.isJPG(srcImg.open())) { + tagBitmap = rotatingImage(tagBitmap, Checker.SINGLE.getOrientation(srcImg.open())); + } + tagBitmap.compress(focusAlpha ? Bitmap.CompressFormat.PNG : Bitmap.CompressFormat.JPEG, 60, stream); + tagBitmap.recycle(); + + FileOutputStream fos = new FileOutputStream(tagImg); + fos.write(stream.toByteArray()); + fos.flush(); + fos.close(); + stream.close(); + + return tagImg; + } +} \ No newline at end of file diff --git a/YFDXJ/luban/src/main/java/top/zibin/luban/InputStreamAdapter.java b/YFDXJ/luban/src/main/java/top/zibin/luban/InputStreamAdapter.java new file mode 100644 index 0000000..210e982 --- /dev/null +++ b/YFDXJ/luban/src/main/java/top/zibin/luban/InputStreamAdapter.java @@ -0,0 +1,34 @@ +package top.zibin.luban; + +import java.io.IOException; +import java.io.InputStream; + +/** + * Automatically close the previous InputStream when opening a new InputStream, + * and finally need to manually call {@link #close()} to release the resource. + */ +public abstract class InputStreamAdapter implements InputStreamProvider { + + private InputStream inputStream; + + @Override + public InputStream open() throws IOException { + close(); + inputStream = openInternal(); + return inputStream; + } + + public abstract InputStream openInternal() throws IOException; + + @Override + public void close() { + if (inputStream != null) { + try { + inputStream.close(); + } catch (IOException ignore) { + }finally { + inputStream = null; + } + } + } +} \ No newline at end of file diff --git a/YFDXJ/luban/src/main/java/top/zibin/luban/InputStreamProvider.java b/YFDXJ/luban/src/main/java/top/zibin/luban/InputStreamProvider.java new file mode 100644 index 0000000..70105e9 --- /dev/null +++ b/YFDXJ/luban/src/main/java/top/zibin/luban/InputStreamProvider.java @@ -0,0 +1,18 @@ +package top.zibin.luban; + +import java.io.IOException; +import java.io.InputStream; + +/** + * 通过此接口获取输入流,以兼容文件、FileProvider方式获取到的图片 + *

+ * Get the input stream through this interface, and obtain the picture using compatible files and FileProvider + */ +public interface InputStreamProvider { + + InputStream open() throws IOException; + + void close(); + + String getPath(); +} diff --git a/YFDXJ/luban/src/main/java/top/zibin/luban/Luban.java b/YFDXJ/luban/src/main/java/top/zibin/luban/Luban.java new file mode 100644 index 0000000..758d4ab --- /dev/null +++ b/YFDXJ/luban/src/main/java/top/zibin/luban/Luban.java @@ -0,0 +1,388 @@ +package top.zibin.luban; + +import android.content.Context; +import android.net.Uri; +import android.os.AsyncTask; +import android.os.Handler; +import android.os.Looper; +import android.os.Message; +import android.text.TextUtils; +import android.util.Log; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +@SuppressWarnings("unused") +public class Luban implements Handler.Callback { + private static final String TAG = "Luban"; + private static final String DEFAULT_DISK_CACHE_DIR = "luban_disk_cache"; + + private static final int MSG_COMPRESS_SUCCESS = 0; + private static final int MSG_COMPRESS_START = 1; + private static final int MSG_COMPRESS_ERROR = 2; + + private String mTargetDir; + private boolean focusAlpha; + private int mLeastCompressSize; + private OnRenameListener mRenameListener; + private OnCompressListener mCompressListener; + private CompressionPredicate mCompressionPredicate; + private List mStreamProviders; + + private Handler mHandler; + + private Luban(Builder builder) { + this.mTargetDir = builder.mTargetDir; + this.mRenameListener = builder.mRenameListener; + this.mStreamProviders = builder.mStreamProviders; + this.mCompressListener = builder.mCompressListener; + this.mLeastCompressSize = builder.mLeastCompressSize; + this.mCompressionPredicate = builder.mCompressionPredicate; + mHandler = new Handler(Looper.getMainLooper(), this); + } + + public static Builder with(Context context) { + return new Builder(context); + } + + /** + * Returns a file with a cache image name in the private cache directory. + * + * @param context A context. + */ + private File getImageCacheFile(Context context, String suffix) { + if (TextUtils.isEmpty(mTargetDir)) { + mTargetDir = getImageCacheDir(context).getAbsolutePath(); + } + + String cacheBuilder = mTargetDir + "/" + + System.currentTimeMillis() + + (int) (Math.random() * 1000) + + (TextUtils.isEmpty(suffix) ? ".jpg" : suffix); + + return new File(cacheBuilder); + } + + private File getImageCustomFile(Context context, String filename) { + if (TextUtils.isEmpty(mTargetDir)) { + mTargetDir = getImageCacheDir(context).getAbsolutePath(); + } + + String cacheBuilder = mTargetDir + "/" + filename; + + return new File(cacheBuilder); + } + + /** + * Returns a directory with a default name in the private cache directory of the application to + * use to store retrieved audio. + * + * @param context A context. + * @see #getImageCacheDir(Context, String) + */ + private File getImageCacheDir(Context context) { + return getImageCacheDir(context, DEFAULT_DISK_CACHE_DIR); + } + + /** + * Returns a directory with the given name in the private cache directory of the application to + * use to store retrieved media and thumbnails. + * + * @param context A context. + * @param cacheName The name of the subdirectory in which to store the cache. + * @see #getImageCacheDir(Context) + */ + private static File getImageCacheDir(Context context, String cacheName) { + File cacheDir = context.getExternalCacheDir(); + if (cacheDir != null) { + File result = new File(cacheDir, cacheName); + if (!result.mkdirs() && (!result.exists() || !result.isDirectory())) { + // File wasn't able to create a directory, or the result exists but not a directory + return null; + } + return result; + } + if (Log.isLoggable(TAG, Log.ERROR)) { + Log.e(TAG, "default disk cache dir is null"); + } + return null; + } + + /** + * start asynchronous compress thread + */ + private void launch(final Context context) { + if (mStreamProviders == null || mStreamProviders.size() == 0 && mCompressListener != null) { + mCompressListener.onError(new NullPointerException("image file cannot be null")); + } + + Iterator iterator = mStreamProviders.iterator(); + + while (iterator.hasNext()) { + final InputStreamProvider path = iterator.next(); + + AsyncTask.SERIAL_EXECUTOR.execute(new Runnable() { + @Override + public void run() { + try { + mHandler.sendMessage(mHandler.obtainMessage(MSG_COMPRESS_START)); + + File result = compress(context, path); + + mHandler.sendMessage(mHandler.obtainMessage(MSG_COMPRESS_SUCCESS, result)); + } catch (IOException e) { + mHandler.sendMessage(mHandler.obtainMessage(MSG_COMPRESS_ERROR, e)); + } + } + }); + + iterator.remove(); + } + } + + /** + * start compress and return the file + */ + private File get(InputStreamProvider input, Context context) throws IOException { + try { + return new Engine(input, getImageCacheFile(context, Checker.SINGLE.extSuffix(input)), focusAlpha).compress(); + } finally { + input.close(); + } + } + + private List get(Context context) throws IOException { + List results = new ArrayList<>(); + Iterator iterator = mStreamProviders.iterator(); + + while (iterator.hasNext()) { + results.add(compress(context, iterator.next())); + iterator.remove(); + } + + return results; + } + + private File compress(Context context, InputStreamProvider path) throws IOException { + try { + return compressReal(context,path); + } finally { + path.close(); + } + } + + private File compressReal(Context context, InputStreamProvider path) throws IOException { + File result; + + File outFile = getImageCacheFile(context, Checker.SINGLE.extSuffix(path)); + + if (mRenameListener != null) { + String filename = mRenameListener.rename(path.getPath()); + outFile = getImageCustomFile(context, filename); + } + + if (mCompressionPredicate != null) { + if (mCompressionPredicate.apply(path.getPath()) + && Checker.SINGLE.needCompress(mLeastCompressSize, path.getPath())) { + result = new Engine(path, outFile, focusAlpha).compress(); + } else { + result = new File(path.getPath()); + } + } else { + result = Checker.SINGLE.needCompress(mLeastCompressSize, path.getPath()) ? + new Engine(path, outFile, focusAlpha).compress() : + new File(path.getPath()); + } + + return result; + } + + @Override + public boolean handleMessage(Message msg) { + if (mCompressListener == null) return false; + + switch (msg.what) { + case MSG_COMPRESS_START: + mCompressListener.onStart(); + break; + case MSG_COMPRESS_SUCCESS: + mCompressListener.onSuccess((File) msg.obj); + break; + case MSG_COMPRESS_ERROR: + mCompressListener.onError((Throwable) msg.obj); + break; + } + return false; + } + + public static class Builder { + private Context context; + private String mTargetDir; + private boolean focusAlpha; + private int mLeastCompressSize = 100; + private OnRenameListener mRenameListener; + private OnCompressListener mCompressListener; + private CompressionPredicate mCompressionPredicate; + private List mStreamProviders; + + Builder(Context context) { + this.context = context; + this.mStreamProviders = new ArrayList<>(); + } + + private Luban build() { + return new Luban(this); + } + + public Builder load(InputStreamProvider inputStreamProvider) { + mStreamProviders.add(inputStreamProvider); + return this; + } + + public Builder load(final File file) { + mStreamProviders.add(new InputStreamAdapter() { + @Override + public InputStream openInternal() throws IOException { + return new FileInputStream(file); + } + + @Override + public String getPath() { + return file.getAbsolutePath(); + } + }); + return this; + } + + public Builder load(final String string) { + mStreamProviders.add(new InputStreamAdapter() { + @Override + public InputStream openInternal() throws IOException { + return new FileInputStream(string); + } + + @Override + public String getPath() { + return string; + } + }); + return this; + } + + public Builder load(List list) { + for (T src : list) { + if (src instanceof String) { + load((String) src); + } else if (src instanceof File) { + load((File) src); + } else if (src instanceof Uri) { + load((Uri) src); + } else { + throw new IllegalArgumentException("Incoming data type exception, it must be String, File, Uri or Bitmap"); + } + } + return this; + } + + public Builder load(final Uri uri) { + mStreamProviders.add(new InputStreamAdapter() { + @Override + public InputStream openInternal() throws IOException { + return context.getContentResolver().openInputStream(uri); + } + + @Override + public String getPath() { + return uri.getPath(); + } + }); + return this; + } + + public Builder putGear(int gear) { + return this; + } + + public Builder setRenameListener(OnRenameListener listener) { + this.mRenameListener = listener; + return this; + } + + public Builder setCompressListener(OnCompressListener listener) { + this.mCompressListener = listener; + return this; + } + + public Builder setTargetDir(String targetDir) { + this.mTargetDir = targetDir; + return this; + } + + /** + * Do I need to keep the image's alpha channel + * + * @param focusAlpha

true - to keep alpha channel, the compress speed will be slow.

+ *

false - don't keep alpha channel, it might have a black background.

+ */ + public Builder setFocusAlpha(boolean focusAlpha) { + this.focusAlpha = focusAlpha; + return this; + } + + /** + * do not compress when the origin image file size less than one value + * + * @param size the value of file size, unit KB, default 100K + */ + public Builder ignoreBy(int size) { + this.mLeastCompressSize = size; + return this; + } + + /** + * do compress image when return value was true, otherwise, do not compress the image file + * + * @param compressionPredicate A predicate callback that returns true or false for the given input path should be compressed. + */ + public Builder filter(CompressionPredicate compressionPredicate) { + this.mCompressionPredicate = compressionPredicate; + return this; + } + + + /** + * begin compress image with asynchronous + */ + public void launch() { + build().launch(context); + } + + public File get(final String path) throws IOException { + return build().get(new InputStreamAdapter() { + @Override + public InputStream openInternal() throws IOException { + return new FileInputStream(path); + } + + @Override + public String getPath() { + return path; + } + }, context); + } + + /** + * begin compress image with synchronize + * + * @return the thumb image file list + */ + public List get() throws IOException { + return build().get(context); + } + } +} \ No newline at end of file diff --git a/YFDXJ/luban/src/main/java/top/zibin/luban/OnCompressListener.java b/YFDXJ/luban/src/main/java/top/zibin/luban/OnCompressListener.java new file mode 100644 index 0000000..e18c841 --- /dev/null +++ b/YFDXJ/luban/src/main/java/top/zibin/luban/OnCompressListener.java @@ -0,0 +1,21 @@ +package top.zibin.luban; + +import java.io.File; + +public interface OnCompressListener { + + /** + * Fired when the compression is started, override to handle in your own code + */ + void onStart(); + + /** + * Fired when a compression returns successfully, override to handle in your own code + */ + void onSuccess(File file); + + /** + * Fired when a compression fails to complete, override to handle in your own code + */ + void onError(Throwable e); +} diff --git a/YFDXJ/luban/src/main/java/top/zibin/luban/OnRenameListener.java b/YFDXJ/luban/src/main/java/top/zibin/luban/OnRenameListener.java new file mode 100644 index 0000000..ecedc9a --- /dev/null +++ b/YFDXJ/luban/src/main/java/top/zibin/luban/OnRenameListener.java @@ -0,0 +1,22 @@ +package top.zibin.luban; + +/** + * Author: zibin + * Datetime: 2018/5/18 + *

+ * 提供修改压缩图片命名接口 + *

+ * A functional interface (callback) that used to rename the file after compress. + */ +public interface OnRenameListener { + + /** + * 压缩前调用该方法用于修改压缩后文件名 + *

+ * Call before compression begins. + * + * @param filePath 传入文件路径/ file path + * @return 返回重命名后的字符串/ file name + */ + String rename(String filePath); +} diff --git a/YFDXJ/luban/src/main/res/values/strings.xml b/YFDXJ/luban/src/main/res/values/strings.xml new file mode 100644 index 0000000..d94d21e --- /dev/null +++ b/YFDXJ/luban/src/main/res/values/strings.xml @@ -0,0 +1,3 @@ + + Luban + diff --git a/YFDXJ/luban/src/test/java/top/zibin/luban/ExampleUnitTest.java b/YFDXJ/luban/src/test/java/top/zibin/luban/ExampleUnitTest.java new file mode 100644 index 0000000..15d1f9a --- /dev/null +++ b/YFDXJ/luban/src/test/java/top/zibin/luban/ExampleUnitTest.java @@ -0,0 +1,15 @@ +package top.zibin.luban; + +import org.junit.Test; + +import static org.junit.Assert.*; + +/** + * To work on unit tests, switch the Test Artifact in the Build Variants view. + */ +public class ExampleUnitTest { + @Test + public void addition_isCorrect() throws Exception { + assertEquals(4, 2 + 2); + } +} \ No newline at end of file diff --git a/YFDXJ/matisse/build.gradle b/YFDXJ/matisse/build.gradle new file mode 100644 index 0000000..e012cf5 --- /dev/null +++ b/YFDXJ/matisse/build.gradle @@ -0,0 +1,31 @@ +apply plugin: 'com.android.library' + + +android { + compileSdk 34 + defaultConfig { + minSdk 24 + targetSdk 34 + testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" + } + namespace 'com.zhihu.matisse' +} + +dependencies { + implementation "androidx.appcompat:appcompat:1.1.0" + implementation "androidx.annotation:annotation:1.1.0" + implementation "androidx.recyclerview:recyclerview:1.0.0" + implementation 'it.sephiroth.android.library.imagezoom:library:1.0.4' + compileOnly 'com.github.bumptech.glide:glide:4.9.0' + compileOnly 'com.squareup.picasso:picasso:2.5.2' +} + + +//publish { +// userOrg = 'zhihu' +// groupId = 'com.zhihu.android' +// artifactId = 'matisse' +// publishVersion = '0.5.3-beta3' +// desc = 'A well-designed local image selector for Android' +// website = 'https://www.zhihu.com/' +//} diff --git a/YFDXJ/matisse/src/main/AndroidManifest.xml b/YFDXJ/matisse/src/main/AndroidManifest.xml new file mode 100644 index 0000000..2507aa1 --- /dev/null +++ b/YFDXJ/matisse/src/main/AndroidManifest.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/YFDXJ/matisse/src/main/java/com/zhihu/matisse/Matisse.java b/YFDXJ/matisse/src/main/java/com/zhihu/matisse/Matisse.java new file mode 100644 index 0000000..0de5938 --- /dev/null +++ b/YFDXJ/matisse/src/main/java/com/zhihu/matisse/Matisse.java @@ -0,0 +1,151 @@ +/* + * Copyright 2017 Zhihu Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.zhihu.matisse; + +import android.app.Activity; +import android.content.Intent; +import android.net.Uri; +import androidx.annotation.Nullable; +import androidx.fragment.app.Fragment; + +import com.zhihu.matisse.ui.MatisseActivity; + +import java.lang.ref.WeakReference; +import java.util.List; +import java.util.Set; + +/** + * Entry for Matisse's media selection. + */ +public final class Matisse { + + private final WeakReference mContext; + private final WeakReference mFragment; + + private Matisse(Activity activity) { + this(activity, null); + } + + private Matisse(Fragment fragment) { + this(fragment.getActivity(), fragment); + } + + private Matisse(Activity activity, Fragment fragment) { + mContext = new WeakReference<>(activity); + mFragment = new WeakReference<>(fragment); + } + + /** + * Start Matisse from an Activity. + *

+ * This Activity's {@link Activity#onActivityResult(int, int, Intent)} will be called when user + * finishes selecting. + * + * @param activity Activity instance. + * @return Matisse instance. + */ + public static Matisse from(Activity activity) { + return new Matisse(activity); + } + + /** + * Start Matisse from a Fragment. + *

+ * This Fragment's {@link Fragment#onActivityResult(int, int, Intent)} will be called when user + * finishes selecting. + * + * @param fragment Fragment instance. + * @return Matisse instance. + */ + public static Matisse from(Fragment fragment) { + return new Matisse(fragment); + } + + /** + * Obtain user selected media' {@link Uri} list in the starting Activity or Fragment. + * + * @param data Intent passed by {@link Activity#onActivityResult(int, int, Intent)} or + * {@link Fragment#onActivityResult(int, int, Intent)}. + * @return User selected media' {@link Uri} list. + */ + public static List obtainResult(Intent data) { + return data.getParcelableArrayListExtra(MatisseActivity.EXTRA_RESULT_SELECTION); + } + + /** + * Obtain user selected media path list in the starting Activity or Fragment. + * + * @param data Intent passed by {@link Activity#onActivityResult(int, int, Intent)} or + * {@link Fragment#onActivityResult(int, int, Intent)}. + * @return User selected media path list. + */ + public static List obtainPathResult(Intent data) { + return data.getStringArrayListExtra(MatisseActivity.EXTRA_RESULT_SELECTION_PATH); + } + + /** + * Obtain state whether user decide to use selected media in original + * + * @param data Intent passed by {@link Activity#onActivityResult(int, int, Intent)} or + * {@link Fragment#onActivityResult(int, int, Intent)}. + * @return Whether use original photo + */ + public static boolean obtainOriginalState(Intent data) { + return data.getBooleanExtra(MatisseActivity.EXTRA_RESULT_ORIGINAL_ENABLE, false); + } + + /** + * MIME types the selection constrains on. + *

+ * Types not included in the set will still be shown in the grid but can't be chosen. + * + * @param mimeTypes MIME types set user can choose from. + * @return {@link SelectionCreator} to build select specifications. + * @see MimeType + * @see SelectionCreator + */ + public SelectionCreator choose(Set mimeTypes) { + return this.choose(mimeTypes, true); + } + + /** + * MIME types the selection constrains on. + *

+ * Types not included in the set will still be shown in the grid but can't be chosen. + * + * @param mimeTypes MIME types set user can choose from. + * @param mediaTypeExclusive Whether can choose images and videos at the same time during one single choosing + * process. true corresponds to not being able to choose images and videos at the same + * time, and false corresponds to being able to do this. + * @return {@link SelectionCreator} to build select specifications. + * @see MimeType + * @see SelectionCreator + */ + public SelectionCreator choose(Set mimeTypes, boolean mediaTypeExclusive) { + return new SelectionCreator(this, mimeTypes, mediaTypeExclusive); + } + + @Nullable + Activity getActivity() { + return mContext.get(); + } + + @Nullable + Fragment getFragment() { + return mFragment != null ? mFragment.get() : null; + } + +} diff --git a/YFDXJ/matisse/src/main/java/com/zhihu/matisse/MimeType.java b/YFDXJ/matisse/src/main/java/com/zhihu/matisse/MimeType.java new file mode 100644 index 0000000..edbcced --- /dev/null +++ b/YFDXJ/matisse/src/main/java/com/zhihu/matisse/MimeType.java @@ -0,0 +1,178 @@ +/* + * Copyright (C) 2014 nohana, Inc. + * Copyright 2017 Zhihu Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.zhihu.matisse; + +import android.content.ContentResolver; +import android.net.Uri; +import android.text.TextUtils; + +import androidx.collection.ArraySet; + +import android.webkit.MimeTypeMap; + +import com.zhihu.matisse.internal.utils.PhotoMetadataUtils; + +import java.util.Arrays; +import java.util.EnumSet; +import java.util.Locale; +import java.util.Set; + +/** + * MIME Type enumeration to restrict selectable media on the selection activity. Matisse only supports images and + * videos. + *

+ * Good example of mime types Android supports: + * https://android.googlesource.com/platform/frameworks/base/+/refs/heads/master/media/java/android/media/MediaFile.java + */ +@SuppressWarnings("unused") +public enum MimeType { + + // ============== images ============== + JPEG("image/jpeg", arraySetOf( + "jpg", + "jpeg" + )), + PNG("image/png", arraySetOf( + "png" + )), + GIF("image/gif", arraySetOf( + "gif" + )), + BMP("image/x-ms-bmp", arraySetOf( + "bmp" + )), + WEBP("image/webp", arraySetOf( + "webp" + )), + + // ============== videos ============== + MPEG("video/mpeg", arraySetOf( + "mpeg", + "mpg" + )), + MP4("video/mp4", arraySetOf( + "mp4", + "m4v" + )), + QUICKTIME("video/quicktime", arraySetOf( + "mov" + )), + THREEGPP("video/3gpp", arraySetOf( + "3gp", + "3gpp" + )), + THREEGPP2("video/3gpp2", arraySetOf( + "3g2", + "3gpp2" + )), + MKV("video/x-matroska", arraySetOf( + "mkv" + )), + WEBM("video/webm", arraySetOf( + "webm" + )), + TS("video/mp2ts", arraySetOf( + "ts" + )), + AVI("video/avi", arraySetOf( + "avi" + )); + + private final String mMimeTypeName; + private final Set mExtensions; + + MimeType(String mimeTypeName, Set extensions) { + mMimeTypeName = mimeTypeName; + mExtensions = extensions; + } + + public static Set ofAll() { + return EnumSet.allOf(MimeType.class); + } + + public static Set of(MimeType type, MimeType... rest) { + return EnumSet.of(type, rest); + } + + public static Set ofImage() { + return EnumSet.of(JPEG, PNG, GIF, BMP, WEBP); + } + + public static Set ofImage(boolean onlyGif) { + return EnumSet.of(GIF); + } + + public static Set ofGif() { + return ofImage(true); + } + + public static Set ofVideo() { + return EnumSet.of(MPEG, MP4, QUICKTIME, THREEGPP, THREEGPP2, MKV, WEBM, TS, AVI); + } + + public static boolean isImage(String mimeType) { + if (mimeType == null) return false; + return mimeType.startsWith("image"); + } + + public static boolean isVideo(String mimeType) { + if (mimeType == null) return false; + return mimeType.startsWith("video"); + } + + public static boolean isGif(String mimeType) { + if (mimeType == null) return false; + return mimeType.equals(MimeType.GIF.toString()); + } + + private static Set arraySetOf(String... suffixes) { + return new ArraySet<>(Arrays.asList(suffixes)); + } + + @Override + public String toString() { + return mMimeTypeName; + } + + public boolean checkType(ContentResolver resolver, Uri uri) { + MimeTypeMap map = MimeTypeMap.getSingleton(); + if (uri == null) { + return false; + } + String type = map.getExtensionFromMimeType(resolver.getType(uri)); + String path = null; + // lazy load the path and prevent resolve for multiple times + boolean pathParsed = false; + for (String extension : mExtensions) { + if (extension.equals(type)) { + return true; + } + if (!pathParsed) { + // we only resolve the path for one time + path = PhotoMetadataUtils.getPath(resolver, uri); + if (!TextUtils.isEmpty(path)) { + path = path.toLowerCase(Locale.US); + } + pathParsed = true; + } + if (path != null && path.endsWith(extension)) { + return true; + } + } + return false; + } +} diff --git a/YFDXJ/matisse/src/main/java/com/zhihu/matisse/SelectionCreator.java b/YFDXJ/matisse/src/main/java/com/zhihu/matisse/SelectionCreator.java new file mode 100644 index 0000000..66aeadf --- /dev/null +++ b/YFDXJ/matisse/src/main/java/com/zhihu/matisse/SelectionCreator.java @@ -0,0 +1,371 @@ +/* + * Copyright (C) 2014 nohana, Inc. + * Copyright 2017 Zhihu Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.zhihu.matisse; + +import android.app.Activity; +import android.content.Intent; +import android.os.Build; +import androidx.annotation.IntDef; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.annotation.RequiresApi; +import androidx.annotation.StyleRes; +import androidx.fragment.app.Fragment; + +import com.zhihu.matisse.engine.ImageEngine; +import com.zhihu.matisse.filter.Filter; +import com.zhihu.matisse.internal.entity.CaptureStrategy; +import com.zhihu.matisse.internal.entity.SelectionSpec; +import com.zhihu.matisse.listener.OnCheckedListener; +import com.zhihu.matisse.listener.OnSelectedListener; +import com.zhihu.matisse.ui.MatisseActivity; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.ArrayList; +import java.util.Set; + +import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_BEHIND; +import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_FULL_SENSOR; +import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_FULL_USER; +import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE; +import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LOCKED; +import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_NOSENSOR; +import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT; +import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE; +import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT; +import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_SENSOR; +import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE; +import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT; +import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED; +import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_USER; +import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_USER_LANDSCAPE; +import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_USER_PORTRAIT; + +/** + * Fluent API for building media select specification. + */ +@SuppressWarnings("unused") +public final class SelectionCreator { + private final Matisse mMatisse; + private final SelectionSpec mSelectionSpec; + + @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR2) + @IntDef({ + SCREEN_ORIENTATION_UNSPECIFIED, + SCREEN_ORIENTATION_LANDSCAPE, + SCREEN_ORIENTATION_PORTRAIT, + SCREEN_ORIENTATION_USER, + SCREEN_ORIENTATION_BEHIND, + SCREEN_ORIENTATION_SENSOR, + SCREEN_ORIENTATION_NOSENSOR, + SCREEN_ORIENTATION_SENSOR_LANDSCAPE, + SCREEN_ORIENTATION_SENSOR_PORTRAIT, + SCREEN_ORIENTATION_REVERSE_LANDSCAPE, + SCREEN_ORIENTATION_REVERSE_PORTRAIT, + SCREEN_ORIENTATION_FULL_SENSOR, + SCREEN_ORIENTATION_USER_LANDSCAPE, + SCREEN_ORIENTATION_USER_PORTRAIT, + SCREEN_ORIENTATION_FULL_USER, + SCREEN_ORIENTATION_LOCKED + }) + @Retention(RetentionPolicy.SOURCE) + @interface ScreenOrientation { + } + + /** + * Constructs a new specification builder on the context. + * + * @param matisse a requester context wrapper. + * @param mimeTypes MIME type set to select. + */ + SelectionCreator(Matisse matisse, @NonNull Set mimeTypes, boolean mediaTypeExclusive) { + mMatisse = matisse; + mSelectionSpec = SelectionSpec.getCleanInstance(); + mSelectionSpec.mimeTypeSet = mimeTypes; + mSelectionSpec.mediaTypeExclusive = mediaTypeExclusive; + mSelectionSpec.orientation = SCREEN_ORIENTATION_UNSPECIFIED; + } + + /** + * Whether to show only one media type if choosing medias are only images or videos. + * + * @param showSingleMediaType whether to show only one media type, either images or videos. + * @return {@link SelectionCreator} for fluent API. + * @see SelectionSpec#onlyShowImages() + * @see SelectionSpec#onlyShowVideos() + */ + public SelectionCreator showSingleMediaType(boolean showSingleMediaType) { + mSelectionSpec.showSingleMediaType = showSingleMediaType; + return this; + } + + /** + * Theme for media selecting Activity. + *

+ * There are two built-in themes: + * 1. com.zhihu.matisse.R.style.Matisse_Zhihu; + * 2. com.zhihu.matisse.R.style.Matisse_Dracula + * you can define a custom theme derived from the above ones or other themes. + * + * @param themeId theme resource id. Default value is com.zhihu.matisse.R.style.Matisse_Zhihu. + * @return {@link SelectionCreator} for fluent API. + */ + public SelectionCreator theme(@StyleRes int themeId) { + mSelectionSpec.themeId = themeId; + return this; + } + + /** + * Show a auto-increased number or a check mark when user select media. + * + * @param countable true for a auto-increased number from 1, false for a check mark. Default + * value is false. + * @return {@link SelectionCreator} for fluent API. + */ + public SelectionCreator countable(boolean countable) { + mSelectionSpec.countable = countable; + return this; + } + + /** + * Maximum selectable count. + * + * @param maxSelectable Maximum selectable count. Default value is 1. + * @return {@link SelectionCreator} for fluent API. + */ + public SelectionCreator maxSelectable(int maxSelectable) { + if (maxSelectable < 1) + throw new IllegalArgumentException("maxSelectable must be greater than or equal to one"); + if (mSelectionSpec.maxImageSelectable > 0 || mSelectionSpec.maxVideoSelectable > 0) + throw new IllegalStateException("already set maxImageSelectable and maxVideoSelectable"); + mSelectionSpec.maxSelectable = maxSelectable; + return this; + } + + /** + * Only useful when {@link SelectionSpec#mediaTypeExclusive} set true and you want to set different maximum + * selectable files for image and video media types. + * + * @param maxImageSelectable Maximum selectable count for image. + * @param maxVideoSelectable Maximum selectable count for video. + * @return {@link SelectionCreator} for fluent API. + */ + public SelectionCreator maxSelectablePerMediaType(int maxImageSelectable, int maxVideoSelectable) { + if (maxImageSelectable < 1 || maxVideoSelectable < 1) + throw new IllegalArgumentException(("max selectable must be greater than or equal to one")); + mSelectionSpec.maxSelectable = -1; + mSelectionSpec.maxImageSelectable = maxImageSelectable; + mSelectionSpec.maxVideoSelectable = maxVideoSelectable; + return this; + } + + /** + * Add filter to filter each selecting item. + * + * @param filter {@link Filter} + * @return {@link SelectionCreator} for fluent API. + */ + public SelectionCreator addFilter(@NonNull Filter filter) { + if (mSelectionSpec.filters == null) { + mSelectionSpec.filters = new ArrayList<>(); + } + if (filter == null) throw new IllegalArgumentException("filter cannot be null"); + mSelectionSpec.filters.add(filter); + return this; + } + + /** + * Determines whether the photo capturing is enabled or not on the media grid view. + *

+ * If this value is set true, photo capturing entry will appear only on All Media's page. + * + * @param enable Whether to enable capturing or not. Default value is false; + * @return {@link SelectionCreator} for fluent API. + */ + public SelectionCreator capture(boolean enable) { + mSelectionSpec.capture = enable; + return this; + } + + /** + * Show a original photo check options.Let users decide whether use original photo after select + * + * @param enable Whether to enable original photo or not + * @return {@link SelectionCreator} for fluent API. + */ + public SelectionCreator originalEnable(boolean enable) { + mSelectionSpec.originalable = enable; + return this; + } + + + /** + * Determines Whether to hide top and bottom toolbar in PreView mode ,when user tap the picture + * @param enable + * @return {@link SelectionCreator} for fluent API. + */ + public SelectionCreator autoHideToolbarOnSingleTap(boolean enable) { + mSelectionSpec.autoHideToobar = enable; + return this; + } + + /** + * Maximum original size,the unit is MB. Only useful when {link@originalEnable} set true + * + * @param size Maximum original size. Default value is Integer.MAX_VALUE + * @return {@link SelectionCreator} for fluent API. + */ + public SelectionCreator maxOriginalSize(int size) { + mSelectionSpec.originalMaxSize = size; + return this; + } + + /** + * Capture strategy provided for the location to save photos including internal and external + * storage and also a authority for {@link androidx.core.content.FileProvider}. + * + * @param captureStrategy {@link CaptureStrategy}, needed only when capturing is enabled. + * @return {@link SelectionCreator} for fluent API. + */ + public SelectionCreator captureStrategy(CaptureStrategy captureStrategy) { + mSelectionSpec.captureStrategy = captureStrategy; + return this; + } + + /** + * Set the desired orientation of this activity. + * + * @param orientation An orientation constant as used in {@link ScreenOrientation}. + * Default value is {@link android.content.pm.ActivityInfo#SCREEN_ORIENTATION_PORTRAIT}. + * @return {@link SelectionCreator} for fluent API. + * @see Activity#setRequestedOrientation(int) + */ + public SelectionCreator restrictOrientation(@ScreenOrientation int orientation) { + mSelectionSpec.orientation = orientation; + return this; + } + + /** + * Set a fixed span count for the media grid. Same for different screen orientations. + *

+ * This will be ignored when {@link #gridExpectedSize(int)} is set. + * + * @param spanCount Requested span count. + * @return {@link SelectionCreator} for fluent API. + */ + public SelectionCreator spanCount(int spanCount) { + if (spanCount < 1) throw new IllegalArgumentException("spanCount cannot be less than 1"); + mSelectionSpec.spanCount = spanCount; + return this; + } + + /** + * Set expected size for media grid to adapt to different screen sizes. This won't necessarily + * be applied cause the media grid should fill the view container. The measured media grid's + * size will be as close to this value as possible. + * + * @param size Expected media grid size in pixel. + * @return {@link SelectionCreator} for fluent API. + */ + public SelectionCreator gridExpectedSize(int size) { + mSelectionSpec.gridExpectedSize = size; + return this; + } + + /** + * Photo thumbnail's scale compared to the View's size. It should be a float value in (0.0, + * 1.0]. + * + * @param scale Thumbnail's scale in (0.0, 1.0]. Default value is 0.5. + * @return {@link SelectionCreator} for fluent API. + */ + public SelectionCreator thumbnailScale(float scale) { + if (scale <= 0f || scale > 1f) + throw new IllegalArgumentException("Thumbnail scale must be between (0.0, 1.0]"); + mSelectionSpec.thumbnailScale = scale; + return this; + } + + /** + * Provide an image engine. + *

+ * There are two built-in image engines: + * 1. {@link com.zhihu.matisse.engine.impl.GlideEngine} + * 2. {@link com.zhihu.matisse.engine.impl.PicassoEngine} + * And you can implement your own image engine. + * + * @param imageEngine {@link ImageEngine} + * @return {@link SelectionCreator} for fluent API. + */ + public SelectionCreator imageEngine(ImageEngine imageEngine) { + mSelectionSpec.imageEngine = imageEngine; + return this; + } + + /** + * Set listener for callback immediately when user select or unselect something. + *

+ * It's a redundant API with {@link Matisse#obtainResult(Intent)}, + * we only suggest you to use this API when you need to do something immediately. + * + * @param listener {@link OnSelectedListener} + * @return {@link SelectionCreator} for fluent API. + */ + @NonNull + public SelectionCreator setOnSelectedListener(@Nullable OnSelectedListener listener) { + mSelectionSpec.onSelectedListener = listener; + return this; + } + + /** + * Set listener for callback immediately when user check or uncheck original. + * + * @param listener {@link OnSelectedListener} + * @return {@link SelectionCreator} for fluent API. + */ + public SelectionCreator setOnCheckedListener(@Nullable OnCheckedListener listener) { + mSelectionSpec.onCheckedListener = listener; + return this; + } + + /** + * Start to select media and wait for result. + * + * @param requestCode Identity of the request Activity or Fragment. + */ + public void forResult(int requestCode) { + Activity activity = mMatisse.getActivity(); + if (activity == null) { + return; + } + + Intent intent = new Intent(activity, MatisseActivity.class); + + Fragment fragment = mMatisse.getFragment(); + if (fragment != null) { + fragment.startActivityForResult(intent, requestCode); + } else { + activity.startActivityForResult(intent, requestCode); + } + } + + public SelectionCreator showPreview(boolean showPreview) { + mSelectionSpec.showPreview = showPreview; + return this; + } +} diff --git a/YFDXJ/matisse/src/main/java/com/zhihu/matisse/engine/ImageEngine.java b/YFDXJ/matisse/src/main/java/com/zhihu/matisse/engine/ImageEngine.java new file mode 100644 index 0000000..84e5c96 --- /dev/null +++ b/YFDXJ/matisse/src/main/java/com/zhihu/matisse/engine/ImageEngine.java @@ -0,0 +1,82 @@ +/* + * Copyright 2017 Zhihu Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.zhihu.matisse.engine; + +import android.content.Context; +import android.graphics.drawable.Drawable; +import android.net.Uri; +import android.widget.ImageView; + +/** + * Image loader interface. There are predefined {@link com.zhihu.matisse.engine.impl.GlideEngine} + * and {@link com.zhihu.matisse.engine.impl.PicassoEngine}. + */ +@SuppressWarnings("unused") +public interface ImageEngine { + + /** + * Load thumbnail of a static image resource. + * + * @param context Context + * @param resize Desired size of the origin image + * @param placeholder Placeholder drawable when image is not loaded yet + * @param imageView ImageView widget + * @param uri Uri of the loaded image + */ + void loadThumbnail(Context context, int resize, Drawable placeholder, ImageView imageView, Uri uri); + + /** + * Load thumbnail of a gif image resource. You don't have to load an animated gif when it's only + * a thumbnail tile. + * + * @param context Context + * @param resize Desired size of the origin image + * @param placeholder Placeholder drawable when image is not loaded yet + * @param imageView ImageView widget + * @param uri Uri of the loaded image + */ + void loadGifThumbnail(Context context, int resize, Drawable placeholder, ImageView imageView, Uri uri); + + /** + * Load a static image resource. + * + * @param context Context + * @param resizeX Desired x-size of the origin image + * @param resizeY Desired y-size of the origin image + * @param imageView ImageView widget + * @param uri Uri of the loaded image + */ + void loadImage(Context context, int resizeX, int resizeY, ImageView imageView, Uri uri); + + /** + * Load a gif image resource. + * + * @param context Context + * @param resizeX Desired x-size of the origin image + * @param resizeY Desired y-size of the origin image + * @param imageView ImageView widget + * @param uri Uri of the loaded image + */ + void loadGifImage(Context context, int resizeX, int resizeY, ImageView imageView, Uri uri); + + /** + * Whether this implementation supports animated gif. + * Just knowledge of it, convenient for users. + * + * @return true support animated gif, false do not support animated gif. + */ + boolean supportAnimatedGif(); +} diff --git a/YFDXJ/matisse/src/main/java/com/zhihu/matisse/engine/impl/GlideEngine.java b/YFDXJ/matisse/src/main/java/com/zhihu/matisse/engine/impl/GlideEngine.java new file mode 100644 index 0000000..51157ae --- /dev/null +++ b/YFDXJ/matisse/src/main/java/com/zhihu/matisse/engine/impl/GlideEngine.java @@ -0,0 +1,87 @@ +/* + * Copyright 2017 Zhihu Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.zhihu.matisse.engine.impl; + +import android.content.Context; +import android.graphics.drawable.Drawable; +import android.net.Uri; +import android.widget.ImageView; + +import com.bumptech.glide.Glide; +import com.bumptech.glide.Priority; +import com.bumptech.glide.request.RequestOptions; +import com.zhihu.matisse.engine.ImageEngine; + +/** + * {@link ImageEngine} implementation using Glide. + */ + +public class GlideEngine implements ImageEngine { + + @Override + public void loadThumbnail(Context context, int resize, Drawable placeholder, ImageView imageView, Uri uri) { + Glide.with(context) + .asBitmap() // some .jpeg files are actually gif + .load(uri) + .apply(new RequestOptions() + .override(resize, resize) + .placeholder(placeholder) + .centerCrop()) + .into(imageView); + } + + @Override + public void loadGifThumbnail(Context context, int resize, Drawable placeholder, ImageView imageView, + Uri uri) { + Glide.with(context) + .asBitmap() // some .jpeg files are actually gif + .load(uri) + .apply(new RequestOptions() + .override(resize, resize) + .placeholder(placeholder) + .centerCrop()) + .into(imageView); + } + + @Override + public void loadImage(Context context, int resizeX, int resizeY, ImageView imageView, Uri uri) { + Glide.with(context) + .load(uri) + .apply(new RequestOptions() + .override(resizeX, resizeY) + .priority(Priority.HIGH) + .fitCenter()) + .into(imageView); + } + + @Override + public void loadGifImage(Context context, int resizeX, int resizeY, ImageView imageView, Uri uri) { + Glide.with(context) + .asGif() + .load(uri) + .apply(new RequestOptions() + .override(resizeX, resizeY) + .priority(Priority.HIGH) + .fitCenter()) + .into(imageView); + } + + @Override + public boolean supportAnimatedGif() { + return true; + } + +} diff --git a/YFDXJ/matisse/src/main/java/com/zhihu/matisse/engine/impl/PicassoEngine.java b/YFDXJ/matisse/src/main/java/com/zhihu/matisse/engine/impl/PicassoEngine.java new file mode 100644 index 0000000..4ee33ea --- /dev/null +++ b/YFDXJ/matisse/src/main/java/com/zhihu/matisse/engine/impl/PicassoEngine.java @@ -0,0 +1,61 @@ +/* + * Copyright 2017 Zhihu Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.zhihu.matisse.engine.impl; + +import android.content.Context; +import android.graphics.drawable.Drawable; +import android.net.Uri; +import android.widget.ImageView; + +import com.squareup.picasso.Picasso; +import com.zhihu.matisse.engine.ImageEngine; + +/** + * {@link ImageEngine} implementation using Picasso. + */ + +public class PicassoEngine implements ImageEngine { + + @Override + public void loadThumbnail(Context context, int resize, Drawable placeholder, ImageView imageView, Uri uri) { + Picasso.with(context).load(uri).placeholder(placeholder) + .resize(resize, resize) + .centerCrop() + .into(imageView); + } + + @Override + public void loadGifThumbnail(Context context, int resize, Drawable placeholder, ImageView imageView, + Uri uri) { + loadThumbnail(context, resize, placeholder, imageView, uri); + } + + @Override + public void loadImage(Context context, int resizeX, int resizeY, ImageView imageView, Uri uri) { + Picasso.with(context).load(uri).resize(resizeX, resizeY).priority(Picasso.Priority.HIGH) + .centerInside().into(imageView); + } + + @Override + public void loadGifImage(Context context, int resizeX, int resizeY, ImageView imageView, Uri uri) { + loadImage(context, resizeX, resizeY, imageView, uri); + } + + @Override + public boolean supportAnimatedGif() { + return false; + } +} diff --git a/YFDXJ/matisse/src/main/java/com/zhihu/matisse/filter/Filter.java b/YFDXJ/matisse/src/main/java/com/zhihu/matisse/filter/Filter.java new file mode 100644 index 0000000..0942364 --- /dev/null +++ b/YFDXJ/matisse/src/main/java/com/zhihu/matisse/filter/Filter.java @@ -0,0 +1,69 @@ +/* + * Copyright 2017 Zhihu Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.zhihu.matisse.filter; + +import android.content.Context; + +import com.zhihu.matisse.MimeType; +import com.zhihu.matisse.SelectionCreator; +import com.zhihu.matisse.internal.entity.Item; +import com.zhihu.matisse.internal.entity.IncapableCause; + +import java.util.Set; + +/** + * Filter for choosing a {@link Item}. You can add multiple Filters through + * {@link SelectionCreator#addFilter(Filter)}. + */ +@SuppressWarnings("unused") +public abstract class Filter { + /** + * Convenient constant for a minimum value. + */ + public static final int MIN = 0; + /** + * Convenient constant for a maximum value. + */ + public static final int MAX = Integer.MAX_VALUE; + /** + * Convenient constant for 1024. + */ + public static final int K = 1024; + + /** + * Against what mime types this filter applies. + */ + protected abstract Set constraintTypes(); + + /** + * Invoked for filtering each item. + * + * @return null if selectable, {@link IncapableCause} if not selectable. + */ + public abstract IncapableCause filter(Context context, Item item); + + /** + * Whether an {@link Item} need filtering. + */ + protected boolean needFiltering(Context context, Item item) { + for (MimeType type : constraintTypes()) { + if (type.checkType(context.getContentResolver(), item.getContentUri())) { + return true; + } + } + return false; + } +} diff --git a/YFDXJ/matisse/src/main/java/com/zhihu/matisse/internal/entity/Album.java b/YFDXJ/matisse/src/main/java/com/zhihu/matisse/internal/entity/Album.java new file mode 100644 index 0000000..35490e9 --- /dev/null +++ b/YFDXJ/matisse/src/main/java/com/zhihu/matisse/internal/entity/Album.java @@ -0,0 +1,122 @@ +/* + * Copyright (C) 2014 nohana, Inc. + * Copyright 2017 Zhihu Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.zhihu.matisse.internal.entity; + +import android.content.Context; +import android.database.Cursor; +import android.net.Uri; +import android.os.Parcel; +import android.os.Parcelable; + +import androidx.annotation.Nullable; + +import com.zhihu.matisse.R; +import com.zhihu.matisse.internal.loader.AlbumLoader; + +public class Album implements Parcelable { + public static final Creator CREATOR = new Creator() { + @Nullable + @Override + public Album createFromParcel(Parcel source) { + return new Album(source); + } + + @Override + public Album[] newArray(int size) { + return new Album[size]; + } + }; + public static final String ALBUM_ID_ALL = String.valueOf(-1); + public static final String ALBUM_NAME_ALL = "All"; + + private final String mId; + private final Uri mCoverUri; + private final String mDisplayName; + private long mCount; + + public Album(String id, Uri coverUri, String albumName, long count) { + mId = id; + mCoverUri = coverUri; + mDisplayName = albumName; + mCount = count; + } + + private Album(Parcel source) { + mId = source.readString(); + mCoverUri = source.readParcelable(Uri.class.getClassLoader()); + mDisplayName = source.readString(); + mCount = source.readLong(); + } + + /** + * Constructs a new {@link Album} entity from the {@link Cursor}. + * This method is not responsible for managing cursor resource, such as close, iterate, and so on. + */ + public static Album valueOf(Cursor cursor) { + String clumn = cursor.getString(cursor.getColumnIndex(AlbumLoader.COLUMN_URI)); + return new Album( + cursor.getString(cursor.getColumnIndex("bucket_id")), + Uri.parse(clumn != null ? clumn : ""), + cursor.getString(cursor.getColumnIndex("bucket_display_name")), + cursor.getLong(cursor.getColumnIndex(AlbumLoader.COLUMN_COUNT))); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(mId); + dest.writeParcelable(mCoverUri, 0); + dest.writeString(mDisplayName); + dest.writeLong(mCount); + } + + public String getId() { + return mId; + } + + public Uri getCoverUri() { + return mCoverUri; + } + + public long getCount() { + return mCount; + } + + public void addCaptureCount() { + mCount++; + } + + public String getDisplayName(Context context) { + if (isAll()) { + return context.getString(R.string.album_name_all); + } + return mDisplayName; + } + + public boolean isAll() { + return ALBUM_ID_ALL.equals(mId); + } + + public boolean isEmpty() { + return mCount == 0; + } + +} \ No newline at end of file diff --git a/YFDXJ/matisse/src/main/java/com/zhihu/matisse/internal/entity/CaptureStrategy.java b/YFDXJ/matisse/src/main/java/com/zhihu/matisse/internal/entity/CaptureStrategy.java new file mode 100644 index 0000000..c4de7ca --- /dev/null +++ b/YFDXJ/matisse/src/main/java/com/zhihu/matisse/internal/entity/CaptureStrategy.java @@ -0,0 +1,33 @@ +/* + * Copyright 2017 Zhihu Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.zhihu.matisse.internal.entity; + +public class CaptureStrategy { + + public final boolean isPublic; + public final String authority; + public final String directory; + + public CaptureStrategy(boolean isPublic, String authority) { + this(isPublic, authority, null); + } + + public CaptureStrategy(boolean isPublic, String authority, String directory) { + this.isPublic = isPublic; + this.authority = authority; + this.directory = directory; + } +} diff --git a/YFDXJ/matisse/src/main/java/com/zhihu/matisse/internal/entity/IncapableCause.java b/YFDXJ/matisse/src/main/java/com/zhihu/matisse/internal/entity/IncapableCause.java new file mode 100644 index 0000000..ff5ec46 --- /dev/null +++ b/YFDXJ/matisse/src/main/java/com/zhihu/matisse/internal/entity/IncapableCause.java @@ -0,0 +1,83 @@ +/* + * Copyright 2017 Zhihu Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.zhihu.matisse.internal.entity; + +import android.content.Context; +import androidx.annotation.IntDef; +import androidx.fragment.app.FragmentActivity; +import android.widget.Toast; + +import com.zhihu.matisse.internal.ui.widget.IncapableDialog; + +import java.lang.annotation.Retention; + +import static java.lang.annotation.RetentionPolicy.SOURCE; + +@SuppressWarnings("unused") +public class IncapableCause { + public static final int TOAST = 0x00; + public static final int DIALOG = 0x01; + public static final int NONE = 0x02; + + @Retention(SOURCE) + @IntDef({TOAST, DIALOG, NONE}) + public @interface Form { + } + + private int mForm = TOAST; + private String mTitle; + private String mMessage; + + public IncapableCause(String message) { + mMessage = message; + } + + public IncapableCause(String title, String message) { + mTitle = title; + mMessage = message; + } + + public IncapableCause(@Form int form, String message) { + mForm = form; + mMessage = message; + } + + public IncapableCause(@Form int form, String title, String message) { + mForm = form; + mTitle = title; + mMessage = message; + } + + public static void handleCause(Context context, IncapableCause cause) { + if (cause == null) + return; + + switch (cause.mForm) { + case NONE: + // do nothing. + break; + case DIALOG: + IncapableDialog incapableDialog = IncapableDialog.newInstance(cause.mTitle, cause.mMessage); + incapableDialog.show(((FragmentActivity) context).getSupportFragmentManager(), + IncapableDialog.class.getName()); + break; + case TOAST: + default: + Toast.makeText(context, cause.mMessage, Toast.LENGTH_SHORT).show(); + break; + } + } +} diff --git a/YFDXJ/matisse/src/main/java/com/zhihu/matisse/internal/entity/Item.java b/YFDXJ/matisse/src/main/java/com/zhihu/matisse/internal/entity/Item.java new file mode 100644 index 0000000..eda9bb5 --- /dev/null +++ b/YFDXJ/matisse/src/main/java/com/zhihu/matisse/internal/entity/Item.java @@ -0,0 +1,144 @@ +/* + * Copyright (C) 2014 nohana, Inc. + * Copyright 2017 Zhihu Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.zhihu.matisse.internal.entity; + +import android.content.ContentUris; +import android.database.Cursor; +import android.net.Uri; +import android.os.Parcel; +import android.os.Parcelable; +import android.provider.MediaStore; +import androidx.annotation.Nullable; + +import com.zhihu.matisse.MimeType; + +public class Item implements Parcelable { + public static final Creator CREATOR = new Creator() { + @Override + @Nullable + public Item createFromParcel(Parcel source) { + return new Item(source); + } + + @Override + public Item[] newArray(int size) { + return new Item[size]; + } + }; + public static final long ITEM_ID_CAPTURE = -1; + public static final String ITEM_DISPLAY_NAME_CAPTURE = "Capture"; + public final long id; + public final String mimeType; + public final Uri uri; + public final long size; + public final long duration; // only for video, in ms + + private Item(long id, String mimeType, long size, long duration) { + this.id = id; + this.mimeType = mimeType; + Uri contentUri; + if (isImage()) { + contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI; + } else if (isVideo()) { + contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI; + } else { + // ? + contentUri = MediaStore.Files.getContentUri("external"); + } + this.uri = ContentUris.withAppendedId(contentUri, id); + this.size = size; + this.duration = duration; + } + + private Item(Parcel source) { + id = source.readLong(); + mimeType = source.readString(); + uri = source.readParcelable(Uri.class.getClassLoader()); + size = source.readLong(); + duration = source.readLong(); + } + + public static Item valueOf(Cursor cursor) { + return new Item(cursor.getLong(cursor.getColumnIndex(MediaStore.Files.FileColumns._ID)), + cursor.getString(cursor.getColumnIndex(MediaStore.MediaColumns.MIME_TYPE)), + cursor.getLong(cursor.getColumnIndex(MediaStore.MediaColumns.SIZE)), + cursor.getLong(cursor.getColumnIndex("duration"))); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeLong(id); + dest.writeString(mimeType); + dest.writeParcelable(uri, 0); + dest.writeLong(size); + dest.writeLong(duration); + } + + public Uri getContentUri() { + return uri; + } + + public boolean isCapture() { + return id == ITEM_ID_CAPTURE; + } + + public boolean isImage() { + return MimeType.isImage(mimeType); + } + + public boolean isGif() { + return MimeType.isGif(mimeType); + } + + public boolean isVideo() { + return MimeType.isVideo(mimeType); + } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof Item)) { + return false; + } + + Item other = (Item) obj; + return id == other.id + && (mimeType != null && mimeType.equals(other.mimeType) + || (mimeType == null && other.mimeType == null)) + && (uri != null && uri.equals(other.uri) + || (uri == null && other.uri == null)) + && size == other.size + && duration == other.duration; + } + + @Override + public int hashCode() { + int result = 1; + result = 31 * result + Long.valueOf(id).hashCode(); + if (mimeType != null) { + result = 31 * result + mimeType.hashCode(); + } + result = 31 * result + uri.hashCode(); + result = 31 * result + Long.valueOf(size).hashCode(); + result = 31 * result + Long.valueOf(duration).hashCode(); + return result; + } +} diff --git a/YFDXJ/matisse/src/main/java/com/zhihu/matisse/internal/entity/SelectionSpec.java b/YFDXJ/matisse/src/main/java/com/zhihu/matisse/internal/entity/SelectionSpec.java new file mode 100644 index 0000000..80b7d10 --- /dev/null +++ b/YFDXJ/matisse/src/main/java/com/zhihu/matisse/internal/entity/SelectionSpec.java @@ -0,0 +1,121 @@ +/* + * Copyright (C) 2014 nohana, Inc. + * Copyright 2017 Zhihu Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.zhihu.matisse.internal.entity; + +import android.content.pm.ActivityInfo; + +import androidx.annotation.StyleRes; + +import com.zhihu.matisse.MimeType; +import com.zhihu.matisse.R; +import com.zhihu.matisse.engine.ImageEngine; +import com.zhihu.matisse.engine.impl.GlideEngine; +import com.zhihu.matisse.filter.Filter; +import com.zhihu.matisse.listener.OnCheckedListener; +import com.zhihu.matisse.listener.OnSelectedListener; + +import java.util.List; +import java.util.Set; + +public final class SelectionSpec { + + public Set mimeTypeSet; + public boolean mediaTypeExclusive; + public boolean showSingleMediaType; + @StyleRes + public int themeId; + public int orientation; + public boolean countable; + public int maxSelectable; + public int maxImageSelectable; + public int maxVideoSelectable; + public List filters; + public boolean capture; + public CaptureStrategy captureStrategy; + public int spanCount; + public int gridExpectedSize; + public float thumbnailScale; + public ImageEngine imageEngine; + public boolean hasInited; + public OnSelectedListener onSelectedListener; + public boolean originalable; + public boolean autoHideToobar; + public int originalMaxSize; + public OnCheckedListener onCheckedListener; + public boolean showPreview; + + private SelectionSpec() { + } + + public static SelectionSpec getInstance() { + return InstanceHolder.INSTANCE; + } + + public static SelectionSpec getCleanInstance() { + SelectionSpec selectionSpec = getInstance(); + selectionSpec.reset(); + return selectionSpec; + } + + private void reset() { + mimeTypeSet = null; + mediaTypeExclusive = true; + showSingleMediaType = false; + themeId = R.style.Matisse_Zhihu; + orientation = 0; + countable = false; + maxSelectable = 1; + maxImageSelectable = 0; + maxVideoSelectable = 0; + filters = null; + capture = false; + captureStrategy = null; + spanCount = 3; + gridExpectedSize = 0; + thumbnailScale = 0.5f; + imageEngine = new GlideEngine(); + hasInited = true; + originalable = false; + autoHideToobar = false; + originalMaxSize = Integer.MAX_VALUE; + showPreview = true; + } + + public boolean singleSelectionModeEnabled() { + return !countable && (maxSelectable == 1 || (maxImageSelectable == 1 && maxVideoSelectable == 1)); + } + + public boolean needOrientationRestriction() { + return orientation != ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED; + } + + public boolean onlyShowImages() { + return showSingleMediaType && MimeType.ofImage().containsAll(mimeTypeSet); + } + + public boolean onlyShowVideos() { + return showSingleMediaType && MimeType.ofVideo().containsAll(mimeTypeSet); + } + + public boolean onlyShowGif() { + return showSingleMediaType && MimeType.ofGif().equals(mimeTypeSet); + } + + private static final class InstanceHolder { + private static final SelectionSpec INSTANCE = new SelectionSpec(); + } +} diff --git a/YFDXJ/matisse/src/main/java/com/zhihu/matisse/internal/loader/AlbumLoader.java b/YFDXJ/matisse/src/main/java/com/zhihu/matisse/internal/loader/AlbumLoader.java new file mode 100644 index 0000000..157a563 --- /dev/null +++ b/YFDXJ/matisse/src/main/java/com/zhihu/matisse/internal/loader/AlbumLoader.java @@ -0,0 +1,293 @@ +/* + * Copyright (C) 2014 nohana, Inc. + * Copyright 2017 Zhihu Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.zhihu.matisse.internal.loader; + +import android.content.ContentUris; +import android.content.Context; +import android.database.Cursor; +import android.database.MatrixCursor; +import android.database.MergeCursor; +import android.net.Uri; +import android.os.Build; +import android.provider.MediaStore; + +import androidx.loader.content.CursorLoader; + +import com.zhihu.matisse.MimeType; +import com.zhihu.matisse.internal.entity.Album; +import com.zhihu.matisse.internal.entity.SelectionSpec; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +/** + * Load all albums (grouped by bucket_id) into a single cursor. + */ +public class AlbumLoader extends CursorLoader { + + private static final String COLUMN_BUCKET_ID = "bucket_id"; + private static final String COLUMN_BUCKET_DISPLAY_NAME = "bucket_display_name"; + public static final String COLUMN_URI = "uri"; + public static final String COLUMN_COUNT = "count"; + private static final Uri QUERY_URI = MediaStore.Files.getContentUri("external"); + + private static final String[] COLUMNS = { + MediaStore.Files.FileColumns._ID, + COLUMN_BUCKET_ID, + COLUMN_BUCKET_DISPLAY_NAME, + MediaStore.MediaColumns.MIME_TYPE, + COLUMN_URI, + COLUMN_COUNT}; + + private static final String[] PROJECTION = { + MediaStore.Files.FileColumns._ID, + COLUMN_BUCKET_ID, + COLUMN_BUCKET_DISPLAY_NAME, + MediaStore.MediaColumns.MIME_TYPE, + "COUNT(*) AS " + COLUMN_COUNT}; + + private static final String[] PROJECTION_29 = { + MediaStore.Files.FileColumns._ID, + COLUMN_BUCKET_ID, + COLUMN_BUCKET_DISPLAY_NAME, + MediaStore.MediaColumns.MIME_TYPE}; + + // === params for showSingleMediaType: false === + private static final String SELECTION = + "(" + MediaStore.Files.FileColumns.MEDIA_TYPE + "=?" + + " OR " + + MediaStore.Files.FileColumns.MEDIA_TYPE + "=?)" + + " AND " + MediaStore.MediaColumns.SIZE + ">0" + + ") GROUP BY (bucket_id"; + private static final String SELECTION_29 = + "(" + MediaStore.Files.FileColumns.MEDIA_TYPE + "=?" + + " OR " + + MediaStore.Files.FileColumns.MEDIA_TYPE + "=?)" + + " AND " + MediaStore.MediaColumns.SIZE + ">0"; + private static final String[] SELECTION_ARGS = { + String.valueOf(MediaStore.Files.FileColumns.MEDIA_TYPE_IMAGE), + String.valueOf(MediaStore.Files.FileColumns.MEDIA_TYPE_VIDEO), + }; + // ============================================= + + // === params for showSingleMediaType: true === + private static final String SELECTION_FOR_SINGLE_MEDIA_TYPE = + MediaStore.Files.FileColumns.MEDIA_TYPE + "=?" + + " AND " + MediaStore.MediaColumns.SIZE + ">0" + + ") GROUP BY (bucket_id"; + private static final String SELECTION_FOR_SINGLE_MEDIA_TYPE_29 = + MediaStore.Files.FileColumns.MEDIA_TYPE + "=?" + + " AND " + MediaStore.MediaColumns.SIZE + ">0"; + + private static String[] getSelectionArgsForSingleMediaType(int mediaType) { + return new String[]{String.valueOf(mediaType)}; + } + // ============================================= + + // === params for showSingleMediaType: true === + private static final String SELECTION_FOR_SINGLE_MEDIA_GIF_TYPE = + MediaStore.Files.FileColumns.MEDIA_TYPE + "=?" + + " AND " + MediaStore.MediaColumns.SIZE + ">0" + + " AND " + MediaStore.MediaColumns.MIME_TYPE + "=?" + + ") GROUP BY (bucket_id"; + private static final String SELECTION_FOR_SINGLE_MEDIA_GIF_TYPE_29 = + MediaStore.Files.FileColumns.MEDIA_TYPE + "=?" + + " AND " + MediaStore.MediaColumns.SIZE + ">0" + + " AND " + MediaStore.MediaColumns.MIME_TYPE + "=?"; + + private static String[] getSelectionArgsForSingleMediaGifType(int mediaType) { + return new String[]{String.valueOf(mediaType), "image/gif"}; + } + // ============================================= + + private static final String BUCKET_ORDER_BY = "datetaken DESC"; + + private AlbumLoader(Context context, String selection, String[] selectionArgs) { + super( + context, + QUERY_URI, + beforeAndroidTen() ? PROJECTION : PROJECTION_29, + selection, + selectionArgs, + BUCKET_ORDER_BY + ); + } + + public static CursorLoader newInstance(Context context) { + String selection; + String[] selectionArgs; + if (SelectionSpec.getInstance().onlyShowGif()) { + selection = beforeAndroidTen() + ? SELECTION_FOR_SINGLE_MEDIA_GIF_TYPE : SELECTION_FOR_SINGLE_MEDIA_GIF_TYPE_29; + selectionArgs = getSelectionArgsForSingleMediaGifType( + MediaStore.Files.FileColumns.MEDIA_TYPE_IMAGE); + } else if (SelectionSpec.getInstance().onlyShowImages()) { + selection = beforeAndroidTen() + ? SELECTION_FOR_SINGLE_MEDIA_TYPE : SELECTION_FOR_SINGLE_MEDIA_TYPE_29; + selectionArgs = getSelectionArgsForSingleMediaType( + MediaStore.Files.FileColumns.MEDIA_TYPE_IMAGE); + } else if (SelectionSpec.getInstance().onlyShowVideos()) { + selection = beforeAndroidTen() + ? SELECTION_FOR_SINGLE_MEDIA_TYPE : SELECTION_FOR_SINGLE_MEDIA_TYPE_29; + selectionArgs = getSelectionArgsForSingleMediaType( + MediaStore.Files.FileColumns.MEDIA_TYPE_VIDEO); + } else { + selection = beforeAndroidTen() ? SELECTION : SELECTION_29; + selectionArgs = SELECTION_ARGS; + } + return new AlbumLoader(context, selection, selectionArgs); + } + + @Override + public Cursor loadInBackground() { + Cursor albums = super.loadInBackground(); + MatrixCursor allAlbum = new MatrixCursor(COLUMNS); + + if (beforeAndroidTen()) { + int totalCount = 0; + Uri allAlbumCoverUri = null; + MatrixCursor otherAlbums = new MatrixCursor(COLUMNS); + if (albums != null) { + while (albums.moveToNext()) { + long fileId = albums.getLong( + albums.getColumnIndex(MediaStore.Files.FileColumns._ID)); + long bucketId = albums.getLong( + albums.getColumnIndex(COLUMN_BUCKET_ID)); + String bucketDisplayName = albums.getString( + albums.getColumnIndex(COLUMN_BUCKET_DISPLAY_NAME)); + String mimeType = albums.getString( + albums.getColumnIndex(MediaStore.MediaColumns.MIME_TYPE)); + Uri uri = getUri(albums); + int count = albums.getInt(albums.getColumnIndex(COLUMN_COUNT)); + + otherAlbums.addRow(new String[]{ + Long.toString(fileId), + Long.toString(bucketId), bucketDisplayName, mimeType, uri.toString(), + String.valueOf(count)}); + totalCount += count; + } + if (albums.moveToFirst()) { + allAlbumCoverUri = getUri(albums); + } + } + + allAlbum.addRow(new String[]{ + Album.ALBUM_ID_ALL, Album.ALBUM_ID_ALL, Album.ALBUM_NAME_ALL, null, + allAlbumCoverUri == null ? null : allAlbumCoverUri.toString(), + String.valueOf(totalCount)}); + + return new MergeCursor(new Cursor[]{allAlbum, otherAlbums}); + } else { + int totalCount = 0; + Uri allAlbumCoverUri = null; + + // Pseudo GROUP BY + Map countMap = new HashMap<>(); + if (albums != null) { + while (albums.moveToNext()) { + long bucketId = albums.getLong(albums.getColumnIndex(COLUMN_BUCKET_ID)); + + Long count = countMap.get(bucketId); + if (count == null) { + count = 1L; + } else { + count++; + } + countMap.put(bucketId, count); + } + } + + MatrixCursor otherAlbums = new MatrixCursor(COLUMNS); + if (albums != null) { + if (albums.moveToFirst()) { + allAlbumCoverUri = getUri(albums); + + Set done = new HashSet<>(); + + do { + long bucketId = albums.getLong(albums.getColumnIndex(COLUMN_BUCKET_ID)); + + if (done.contains(bucketId)) { + continue; + } + + long fileId = albums.getLong( + albums.getColumnIndex(MediaStore.Files.FileColumns._ID)); + String bucketDisplayName = albums.getString( + albums.getColumnIndex(COLUMN_BUCKET_DISPLAY_NAME)); + String mimeType = albums.getString( + albums.getColumnIndex(MediaStore.MediaColumns.MIME_TYPE)); + Uri uri = getUri(albums); + long count = countMap.get(bucketId); + + otherAlbums.addRow(new String[]{ + Long.toString(fileId), + Long.toString(bucketId), + bucketDisplayName, + mimeType, + uri.toString(), + String.valueOf(count)}); + done.add(bucketId); + + totalCount += count; + } while (albums.moveToNext()); + } + } + + allAlbum.addRow(new String[]{ + Album.ALBUM_ID_ALL, + Album.ALBUM_ID_ALL, Album.ALBUM_NAME_ALL, null, + allAlbumCoverUri == null ? null : allAlbumCoverUri.toString(), + String.valueOf(totalCount)}); + + return new MergeCursor(new Cursor[]{allAlbum, otherAlbums}); + } + } + + private static Uri getUri(Cursor cursor) { + long id = cursor.getLong(cursor.getColumnIndex(MediaStore.Files.FileColumns._ID)); + String mimeType = cursor.getString( + cursor.getColumnIndex(MediaStore.MediaColumns.MIME_TYPE)); + Uri contentUri; + + if (MimeType.isImage(mimeType)) { + contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI; + } else if (MimeType.isVideo(mimeType)) { + contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI; + } else { + // ? + contentUri = MediaStore.Files.getContentUri("external"); + } + + Uri uri = ContentUris.withAppendedId(contentUri, id); + return uri; + } + + @Override + public void onContentChanged() { + // FIXME a dirty way to fix loading multiple times + } + + /** + * @return 是否是 Android 10 (Q) 之前的版本 + */ + private static boolean beforeAndroidTen() { + return android.os.Build.VERSION.SDK_INT < Build.VERSION_CODES.Q; + } +} \ No newline at end of file diff --git a/YFDXJ/matisse/src/main/java/com/zhihu/matisse/internal/loader/AlbumMediaLoader.java b/YFDXJ/matisse/src/main/java/com/zhihu/matisse/internal/loader/AlbumMediaLoader.java new file mode 100644 index 0000000..7bba0d6 --- /dev/null +++ b/YFDXJ/matisse/src/main/java/com/zhihu/matisse/internal/loader/AlbumMediaLoader.java @@ -0,0 +1,197 @@ +/* + * Copyright (C) 2014 nohana, Inc. + * Copyright 2017 Zhihu Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.zhihu.matisse.internal.loader; + +import android.content.Context; +import android.database.Cursor; +import android.database.MatrixCursor; +import android.database.MergeCursor; +import android.net.Uri; +import android.provider.MediaStore; + +import androidx.loader.content.CursorLoader; + +import com.zhihu.matisse.internal.entity.Album; +import com.zhihu.matisse.internal.entity.Item; +import com.zhihu.matisse.internal.entity.SelectionSpec; +import com.zhihu.matisse.internal.utils.MediaStoreCompat; + +/** + * Load images and videos into a single cursor. + */ +public class AlbumMediaLoader extends CursorLoader { + private static final Uri QUERY_URI = MediaStore.Files.getContentUri("external"); + private static final String[] PROJECTION = { + MediaStore.Files.FileColumns._ID, + MediaStore.MediaColumns.DISPLAY_NAME, + MediaStore.MediaColumns.MIME_TYPE, + MediaStore.MediaColumns.SIZE, + "duration"}; + + // === params for album ALL && showSingleMediaType: false === + private static final String SELECTION_ALL = + "(" + MediaStore.Files.FileColumns.MEDIA_TYPE + "=?" + + " OR " + + MediaStore.Files.FileColumns.MEDIA_TYPE + "=?)" + + " AND " + MediaStore.MediaColumns.SIZE + ">0"; + private static final String[] SELECTION_ALL_ARGS = { + String.valueOf(MediaStore.Files.FileColumns.MEDIA_TYPE_IMAGE), + String.valueOf(MediaStore.Files.FileColumns.MEDIA_TYPE_VIDEO), + }; + // =========================================================== + + // === params for album ALL && showSingleMediaType: true === + private static final String SELECTION_ALL_FOR_SINGLE_MEDIA_TYPE = + MediaStore.Files.FileColumns.MEDIA_TYPE + "=?" + + " AND " + MediaStore.MediaColumns.SIZE + ">0"; + + private static String[] getSelectionArgsForSingleMediaType(int mediaType) { + return new String[]{String.valueOf(mediaType)}; + } + // ========================================================= + + // === params for ordinary album && showSingleMediaType: false === + private static final String SELECTION_ALBUM = + "(" + MediaStore.Files.FileColumns.MEDIA_TYPE + "=?" + + " OR " + + MediaStore.Files.FileColumns.MEDIA_TYPE + "=?)" + + " AND " + + " bucket_id=?" + + " AND " + MediaStore.MediaColumns.SIZE + ">0"; + + private static String[] getSelectionAlbumArgs(String albumId) { + return new String[]{ + String.valueOf(MediaStore.Files.FileColumns.MEDIA_TYPE_IMAGE), + String.valueOf(MediaStore.Files.FileColumns.MEDIA_TYPE_VIDEO), + albumId + }; + } + // =============================================================== + + // === params for ordinary album && showSingleMediaType: true === + private static final String SELECTION_ALBUM_FOR_SINGLE_MEDIA_TYPE = + MediaStore.Files.FileColumns.MEDIA_TYPE + "=?" + + " AND " + + " bucket_id=?" + + " AND " + MediaStore.MediaColumns.SIZE + ">0"; + + private static String[] getSelectionAlbumArgsForSingleMediaType(int mediaType, String albumId) { + return new String[]{String.valueOf(mediaType), albumId}; + } + // =============================================================== + + // === params for album ALL && showSingleMediaType: true && MineType=="image/gif" + private static final String SELECTION_ALL_FOR_GIF = + MediaStore.Files.FileColumns.MEDIA_TYPE + "=?" + + " AND " + + MediaStore.MediaColumns.MIME_TYPE + "=?" + + " AND " + MediaStore.MediaColumns.SIZE + ">0"; + + private static String[] getSelectionArgsForGifType(int mediaType) { + return new String[]{String.valueOf(mediaType), "image/gif"}; + } + // =============================================================== + + // === params for ordinary album && showSingleMediaType: true && MineType=="image/gif" === + private static final String SELECTION_ALBUM_FOR_GIF = + MediaStore.Files.FileColumns.MEDIA_TYPE + "=?" + + " AND " + + " bucket_id=?" + + " AND " + + MediaStore.MediaColumns.MIME_TYPE + "=?" + + " AND " + MediaStore.MediaColumns.SIZE + ">0"; + + private static String[] getSelectionAlbumArgsForGifType(int mediaType, String albumId) { + return new String[]{String.valueOf(mediaType), albumId, "image/gif"}; + } + // =============================================================== + + private static final String ORDER_BY = MediaStore.Images.Media.DATE_TAKEN + " DESC"; + private final boolean mEnableCapture; + + private AlbumMediaLoader(Context context, String selection, String[] selectionArgs, boolean capture) { + super(context, QUERY_URI, PROJECTION, selection, selectionArgs, ORDER_BY); + mEnableCapture = capture; + } + + public static CursorLoader newInstance(Context context, Album album, boolean capture) { + String selection; + String[] selectionArgs; + boolean enableCapture; + + if (album.isAll()) { + if (SelectionSpec.getInstance().onlyShowGif()) { + selection = SELECTION_ALL_FOR_GIF; + selectionArgs = getSelectionArgsForGifType( + MediaStore.Files.FileColumns.MEDIA_TYPE_IMAGE); + } else if (SelectionSpec.getInstance().onlyShowImages()) { + selection = SELECTION_ALL_FOR_SINGLE_MEDIA_TYPE; + selectionArgs = + getSelectionArgsForSingleMediaType( + MediaStore.Files.FileColumns.MEDIA_TYPE_IMAGE); + } else if (SelectionSpec.getInstance().onlyShowVideos()) { + selection = SELECTION_ALL_FOR_SINGLE_MEDIA_TYPE; + selectionArgs = + getSelectionArgsForSingleMediaType( + MediaStore.Files.FileColumns.MEDIA_TYPE_VIDEO); + } else { + selection = SELECTION_ALL; + selectionArgs = SELECTION_ALL_ARGS; + } + enableCapture = capture; + } else { + if (SelectionSpec.getInstance().onlyShowGif()) { + selection = SELECTION_ALBUM_FOR_GIF; + selectionArgs = + getSelectionAlbumArgsForGifType( + MediaStore.Files.FileColumns.MEDIA_TYPE_IMAGE, album.getId()); + } else if (SelectionSpec.getInstance().onlyShowImages()) { + selection = SELECTION_ALBUM_FOR_SINGLE_MEDIA_TYPE; + selectionArgs = + getSelectionAlbumArgsForSingleMediaType( + MediaStore.Files.FileColumns.MEDIA_TYPE_IMAGE, + album.getId()); + } else if (SelectionSpec.getInstance().onlyShowVideos()) { + selection = SELECTION_ALBUM_FOR_SINGLE_MEDIA_TYPE; + selectionArgs = getSelectionAlbumArgsForSingleMediaType( + MediaStore.Files.FileColumns.MEDIA_TYPE_VIDEO, + album.getId()); + } else { + selection = SELECTION_ALBUM; + selectionArgs = getSelectionAlbumArgs(album.getId()); + } + enableCapture = false; + } + return new AlbumMediaLoader(context, selection, selectionArgs, enableCapture); + } + + @Override + public Cursor loadInBackground() { + Cursor result = super.loadInBackground(); + if (!mEnableCapture || !MediaStoreCompat.hasCameraFeature(getContext())) { + return result; + } + MatrixCursor dummy = new MatrixCursor(PROJECTION); + dummy.addRow(new Object[]{Item.ITEM_ID_CAPTURE, Item.ITEM_DISPLAY_NAME_CAPTURE, "", 0, 0}); + return new MergeCursor(new Cursor[]{dummy, result}); + } + + @Override + public void onContentChanged() { + // FIXME a dirty way to fix loading multiple times + } +} diff --git a/YFDXJ/matisse/src/main/java/com/zhihu/matisse/internal/model/AlbumCollection.java b/YFDXJ/matisse/src/main/java/com/zhihu/matisse/internal/model/AlbumCollection.java new file mode 100644 index 0000000..ceabef6 --- /dev/null +++ b/YFDXJ/matisse/src/main/java/com/zhihu/matisse/internal/model/AlbumCollection.java @@ -0,0 +1,114 @@ +/* + * Copyright (C) 2014 nohana, Inc. + * Copyright 2017 Zhihu Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.zhihu.matisse.internal.model; + +import android.content.Context; +import android.database.Cursor; +import android.os.Bundle; +import androidx.fragment.app.FragmentActivity; +import androidx.loader.app.LoaderManager; +import androidx.loader.content.Loader; + +import com.zhihu.matisse.internal.loader.AlbumLoader; + +import java.lang.ref.WeakReference; + +public class AlbumCollection implements LoaderManager.LoaderCallbacks { + private static final int LOADER_ID = 1; + private static final String STATE_CURRENT_SELECTION = "state_current_selection"; + private WeakReference mContext; + private LoaderManager mLoaderManager; + private AlbumCallbacks mCallbacks; + private int mCurrentSelection; + private boolean mLoadFinished; + + @Override + public Loader onCreateLoader(int id, Bundle args) { + Context context = mContext.get(); + if (context == null) { + return null; + } + mLoadFinished = false; + return AlbumLoader.newInstance(context); + } + + @Override + public void onLoadFinished(Loader loader, Cursor data) { + Context context = mContext.get(); + if (context == null) { + return; + } + + if (!mLoadFinished) { + mLoadFinished = true; + mCallbacks.onAlbumLoad(data); + } + } + + @Override + public void onLoaderReset(Loader loader) { + Context context = mContext.get(); + if (context == null) { + return; + } + + mCallbacks.onAlbumReset(); + } + + public void onCreate(FragmentActivity activity, AlbumCallbacks callbacks) { + mContext = new WeakReference(activity); + mLoaderManager = activity.getSupportLoaderManager(); + mCallbacks = callbacks; + } + + public void onRestoreInstanceState(Bundle savedInstanceState) { + if (savedInstanceState == null) { + return; + } + + mCurrentSelection = savedInstanceState.getInt(STATE_CURRENT_SELECTION); + } + + public void onSaveInstanceState(Bundle outState) { + outState.putInt(STATE_CURRENT_SELECTION, mCurrentSelection); + } + + public void onDestroy() { + if (mLoaderManager != null) { + mLoaderManager.destroyLoader(LOADER_ID); + } + mCallbacks = null; + } + + public void loadAlbums() { + mLoaderManager.initLoader(LOADER_ID, null, this); + } + + public int getCurrentSelection() { + return mCurrentSelection; + } + + public void setStateCurrentSelection(int currentSelection) { + mCurrentSelection = currentSelection; + } + + public interface AlbumCallbacks { + void onAlbumLoad(Cursor cursor); + + void onAlbumReset(); + } +} diff --git a/YFDXJ/matisse/src/main/java/com/zhihu/matisse/internal/model/AlbumMediaCollection.java b/YFDXJ/matisse/src/main/java/com/zhihu/matisse/internal/model/AlbumMediaCollection.java new file mode 100644 index 0000000..fa25939 --- /dev/null +++ b/YFDXJ/matisse/src/main/java/com/zhihu/matisse/internal/model/AlbumMediaCollection.java @@ -0,0 +1,107 @@ +/* + * Copyright (C) 2014 nohana, Inc. + * Copyright 2017 Zhihu Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.zhihu.matisse.internal.model; + +import android.content.Context; +import android.database.Cursor; +import android.os.Bundle; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.fragment.app.FragmentActivity; +import androidx.loader.app.LoaderManager; +import androidx.loader.content.Loader; + +import com.zhihu.matisse.internal.entity.Album; +import com.zhihu.matisse.internal.loader.AlbumMediaLoader; + +import java.lang.ref.WeakReference; + +public class AlbumMediaCollection implements LoaderManager.LoaderCallbacks { + private static final int LOADER_ID = 2; + private static final String ARGS_ALBUM = "args_album"; + private static final String ARGS_ENABLE_CAPTURE = "args_enable_capture"; + private WeakReference mContext; + private LoaderManager mLoaderManager; + private AlbumMediaCallbacks mCallbacks; + + @Override + public Loader onCreateLoader(int id, Bundle args) { + Context context = mContext.get(); + if (context == null) { + return null; + } + + Album album = args.getParcelable(ARGS_ALBUM); + if (album == null) { + return null; + } + + return AlbumMediaLoader.newInstance(context, album, + album.isAll() && args.getBoolean(ARGS_ENABLE_CAPTURE, false)); + } + + @Override + public void onLoadFinished(Loader loader, Cursor data) { + Context context = mContext.get(); + if (context == null) { + return; + } + + mCallbacks.onAlbumMediaLoad(data); + } + + @Override + public void onLoaderReset(Loader loader) { + Context context = mContext.get(); + if (context == null) { + return; + } + + mCallbacks.onAlbumMediaReset(); + } + + public void onCreate(@NonNull FragmentActivity context, @NonNull AlbumMediaCallbacks callbacks) { + mContext = new WeakReference(context); + mLoaderManager = context.getSupportLoaderManager(); + mCallbacks = callbacks; + } + + public void onDestroy() { + if (mLoaderManager != null) { + mLoaderManager.destroyLoader(LOADER_ID); + } + mCallbacks = null; + } + + public void load(@Nullable Album target) { + load(target, false); + } + + public void load(@Nullable Album target, boolean enableCapture) { + Bundle args = new Bundle(); + args.putParcelable(ARGS_ALBUM, target); + args.putBoolean(ARGS_ENABLE_CAPTURE, enableCapture); + mLoaderManager.initLoader(LOADER_ID, args, this); + } + + public interface AlbumMediaCallbacks { + + void onAlbumMediaLoad(Cursor cursor); + + void onAlbumMediaReset(); + } +} diff --git a/YFDXJ/matisse/src/main/java/com/zhihu/matisse/internal/model/SelectedItemCollection.java b/YFDXJ/matisse/src/main/java/com/zhihu/matisse/internal/model/SelectedItemCollection.java new file mode 100644 index 0000000..31b12c5 --- /dev/null +++ b/YFDXJ/matisse/src/main/java/com/zhihu/matisse/internal/model/SelectedItemCollection.java @@ -0,0 +1,255 @@ +/* + * Copyright 2017 Zhihu Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.zhihu.matisse.internal.model; + +import android.content.Context; +import android.content.res.Resources; +import android.net.Uri; +import android.os.Bundle; + +import com.zhihu.matisse.R; +import com.zhihu.matisse.internal.entity.IncapableCause; +import com.zhihu.matisse.internal.entity.Item; +import com.zhihu.matisse.internal.entity.SelectionSpec; +import com.zhihu.matisse.internal.ui.widget.CheckView; +import com.zhihu.matisse.internal.utils.PathUtils; +import com.zhihu.matisse.internal.utils.PhotoMetadataUtils; + +import java.util.ArrayList; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; + +@SuppressWarnings("unused") +public class SelectedItemCollection { + + public static final String STATE_SELECTION = "state_selection"; + public static final String STATE_COLLECTION_TYPE = "state_collection_type"; + /** + * Empty collection + */ + public static final int COLLECTION_UNDEFINED = 0x00; + /** + * Collection only with images + */ + public static final int COLLECTION_IMAGE = 0x01; + /** + * Collection only with videos + */ + public static final int COLLECTION_VIDEO = 0x01 << 1; + /** + * Collection with images and videos. + */ + public static final int COLLECTION_MIXED = COLLECTION_IMAGE | COLLECTION_VIDEO; + private final Context mContext; + private Set mItems; + private int mCollectionType = COLLECTION_UNDEFINED; + + public SelectedItemCollection(Context context) { + mContext = context; + } + + public void onCreate(Bundle bundle) { + if (bundle == null) { + mItems = new LinkedHashSet<>(); + } else { + List saved = bundle.getParcelableArrayList(STATE_SELECTION); + mItems = new LinkedHashSet<>(saved); + mCollectionType = bundle.getInt(STATE_COLLECTION_TYPE, COLLECTION_UNDEFINED); + } + } + + public void setDefaultSelection(List uris) { + mItems.addAll(uris); + } + + public void onSaveInstanceState(Bundle outState) { + outState.putParcelableArrayList(STATE_SELECTION, new ArrayList<>(mItems)); + outState.putInt(STATE_COLLECTION_TYPE, mCollectionType); + } + + public Bundle getDataWithBundle() { + Bundle bundle = new Bundle(); + bundle.putParcelableArrayList(STATE_SELECTION, new ArrayList<>(mItems)); + bundle.putInt(STATE_COLLECTION_TYPE, mCollectionType); + return bundle; + } + + public boolean add(Item item) { + if (typeConflict(item)) { + throw new IllegalArgumentException("Can't select images and videos at the same time."); + } + boolean added = mItems.add(item); + if (added) { + if (mCollectionType == COLLECTION_UNDEFINED) { + if (item.isImage()) { + mCollectionType = COLLECTION_IMAGE; + } else if (item.isVideo()) { + mCollectionType = COLLECTION_VIDEO; + } + } else if (mCollectionType == COLLECTION_IMAGE) { + if (item.isVideo()) { + mCollectionType = COLLECTION_MIXED; + } + } else if (mCollectionType == COLLECTION_VIDEO) { + if (item.isImage()) { + mCollectionType = COLLECTION_MIXED; + } + } + } + return added; + } + + public boolean remove(Item item) { + boolean removed = mItems.remove(item); + if (removed) { + if (mItems.size() == 0) { + mCollectionType = COLLECTION_UNDEFINED; + } else { + if (mCollectionType == COLLECTION_MIXED) { + refineCollectionType(); + } + } + } + return removed; + } + + public void overwrite(ArrayList items, int collectionType) { + if (items.size() == 0) { + mCollectionType = COLLECTION_UNDEFINED; + } else { + mCollectionType = collectionType; + } + mItems.clear(); + mItems.addAll(items); + } + + + public List asList() { + return new ArrayList<>(mItems); + } + + public List asListOfUri() { + List uris = new ArrayList<>(); + for (Item item : mItems) { + uris.add(item.getContentUri()); + } + return uris; + } + + public List asListOfString() { + List paths = new ArrayList<>(); + for (Item item : mItems) { + paths.add(PathUtils.getPath(mContext, item.getContentUri())); + } + return paths; + } + + public boolean isEmpty() { + return mItems == null || mItems.isEmpty(); + } + + public boolean isSelected(Item item) { + return mItems.contains(item); + } + + public IncapableCause isAcceptable(Item item) { + if (maxSelectableReached()) { + int maxSelectable = currentMaxSelectable(); + String cause; + + try { + cause = mContext.getString( + R.string.error_over_count, + maxSelectable + ); + } catch (Resources.NotFoundException e) { + cause = mContext.getString( + R.string.error_over_count, + maxSelectable + ); + } catch (NoClassDefFoundError e) { + cause = mContext.getString( + R.string.error_over_count, + maxSelectable + ); + } + + return new IncapableCause(cause); + } else if (typeConflict(item)) { + return new IncapableCause(mContext.getString(R.string.error_type_conflict)); + } + + return PhotoMetadataUtils.isAcceptable(mContext, item); + } + + public boolean maxSelectableReached() { + return mItems.size() == currentMaxSelectable(); + } + + // depends + private int currentMaxSelectable() { + SelectionSpec spec = SelectionSpec.getInstance(); + if (spec.maxSelectable > 0) { + return spec.maxSelectable; + } else if (mCollectionType == COLLECTION_IMAGE) { + return spec.maxImageSelectable; + } else if (mCollectionType == COLLECTION_VIDEO) { + return spec.maxVideoSelectable; + } else { + return spec.maxSelectable; + } + } + + public int getCollectionType() { + return mCollectionType; + } + + private void refineCollectionType() { + boolean hasImage = false; + boolean hasVideo = false; + for (Item i : mItems) { + if (i.isImage() && !hasImage) hasImage = true; + if (i.isVideo() && !hasVideo) hasVideo = true; + } + if (hasImage && hasVideo) { + mCollectionType = COLLECTION_MIXED; + } else if (hasImage) { + mCollectionType = COLLECTION_IMAGE; + } else if (hasVideo) { + mCollectionType = COLLECTION_VIDEO; + } + } + + /** + * Determine whether there will be conflict media types. A user can only select images and videos at the same time + * while {@link SelectionSpec#mediaTypeExclusive} is set to false. + */ + public boolean typeConflict(Item item) { + return SelectionSpec.getInstance().mediaTypeExclusive + && ((item.isImage() && (mCollectionType == COLLECTION_VIDEO || mCollectionType == COLLECTION_MIXED)) + || (item.isVideo() && (mCollectionType == COLLECTION_IMAGE || mCollectionType == COLLECTION_MIXED))); + } + + public int count() { + return mItems.size(); + } + + public int checkedNumOf(Item item) { + int index = new ArrayList<>(mItems).indexOf(item); + return index == -1 ? CheckView.UNCHECKED : index + 1; + } +} diff --git a/YFDXJ/matisse/src/main/java/com/zhihu/matisse/internal/ui/AlbumPreviewActivity.java b/YFDXJ/matisse/src/main/java/com/zhihu/matisse/internal/ui/AlbumPreviewActivity.java new file mode 100644 index 0000000..01c82e3 --- /dev/null +++ b/YFDXJ/matisse/src/main/java/com/zhihu/matisse/internal/ui/AlbumPreviewActivity.java @@ -0,0 +1,97 @@ +/* + * Copyright 2017 Zhihu Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.zhihu.matisse.internal.ui; + +import android.database.Cursor; +import android.os.Bundle; +import androidx.annotation.Nullable; + +import com.zhihu.matisse.internal.entity.Album; +import com.zhihu.matisse.internal.entity.Item; +import com.zhihu.matisse.internal.entity.SelectionSpec; +import com.zhihu.matisse.internal.model.AlbumMediaCollection; +import com.zhihu.matisse.internal.ui.adapter.PreviewPagerAdapter; + +import java.util.ArrayList; +import java.util.List; + +public class AlbumPreviewActivity extends BasePreviewActivity implements + AlbumMediaCollection.AlbumMediaCallbacks { + + public static final String EXTRA_ALBUM = "extra_album"; + public static final String EXTRA_ITEM = "extra_item"; + + private AlbumMediaCollection mCollection = new AlbumMediaCollection(); + + private boolean mIsAlreadySetPosition; + + @Override + protected void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + if (!SelectionSpec.getInstance().hasInited) { + setResult(RESULT_CANCELED); + finish(); + return; + } + mCollection.onCreate(this, this); + Album album = getIntent().getParcelableExtra(EXTRA_ALBUM); + mCollection.load(album); + + Item item = getIntent().getParcelableExtra(EXTRA_ITEM); + if (mSpec.countable) { + mCheckView.setCheckedNum(mSelectedCollection.checkedNumOf(item)); + } else { + mCheckView.setChecked(mSelectedCollection.isSelected(item)); + } + updateSize(item); + } + + @Override + protected void onDestroy() { + super.onDestroy(); + mCollection.onDestroy(); + } + + @Override + public void onAlbumMediaLoad(Cursor cursor) { + List items = new ArrayList<>(); + while (cursor.moveToNext()) { + items.add(Item.valueOf(cursor)); + } +// cursor.close(); + + if (items.isEmpty()) { + return; + } + + PreviewPagerAdapter adapter = (PreviewPagerAdapter) mPager.getAdapter(); + adapter.addAll(items); + adapter.notifyDataSetChanged(); + if (!mIsAlreadySetPosition) { + //onAlbumMediaLoad is called many times.. + mIsAlreadySetPosition = true; + Item selected = getIntent().getParcelableExtra(EXTRA_ITEM); + int selectedIndex = items.indexOf(selected); + mPager.setCurrentItem(selectedIndex, false); + mPreviousPos = selectedIndex; + } + } + + @Override + public void onAlbumMediaReset() { + + } +} diff --git a/YFDXJ/matisse/src/main/java/com/zhihu/matisse/internal/ui/BasePreviewActivity.java b/YFDXJ/matisse/src/main/java/com/zhihu/matisse/internal/ui/BasePreviewActivity.java new file mode 100644 index 0000000..770dea9 --- /dev/null +++ b/YFDXJ/matisse/src/main/java/com/zhihu/matisse/internal/ui/BasePreviewActivity.java @@ -0,0 +1,358 @@ +/* + * Copyright 2017 Zhihu Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.zhihu.matisse.internal.ui; + +import android.app.Activity; +import android.content.Intent; +import android.graphics.Color; +import android.os.Bundle; +import androidx.annotation.Nullable; +import androidx.viewpager.widget.ViewPager; +import androidx.interpolator.view.animation.FastOutSlowInInterpolator; +import androidx.appcompat.app.AppCompatActivity; +import android.view.View; +import android.view.WindowManager; +import android.widget.FrameLayout; +import android.widget.LinearLayout; +import android.widget.TextView; + +import com.zhihu.matisse.R; +import com.zhihu.matisse.internal.entity.IncapableCause; +import com.zhihu.matisse.internal.entity.Item; +import com.zhihu.matisse.internal.entity.SelectionSpec; +import com.zhihu.matisse.internal.model.SelectedItemCollection; +import com.zhihu.matisse.internal.ui.adapter.PreviewPagerAdapter; +import com.zhihu.matisse.internal.ui.widget.CheckRadioView; +import com.zhihu.matisse.internal.ui.widget.CheckView; +import com.zhihu.matisse.internal.ui.widget.IncapableDialog; +import com.zhihu.matisse.internal.utils.PhotoMetadataUtils; +import com.zhihu.matisse.internal.utils.Platform; +import com.zhihu.matisse.listener.OnFragmentInteractionListener; + +public abstract class BasePreviewActivity extends AppCompatActivity implements View.OnClickListener, + ViewPager.OnPageChangeListener, OnFragmentInteractionListener { + + public static final String EXTRA_DEFAULT_BUNDLE = "extra_default_bundle"; + public static final String EXTRA_RESULT_BUNDLE = "extra_result_bundle"; + public static final String EXTRA_RESULT_APPLY = "extra_result_apply"; + public static final String EXTRA_RESULT_ORIGINAL_ENABLE = "extra_result_original_enable"; + public static final String CHECK_STATE = "checkState"; + + protected final SelectedItemCollection mSelectedCollection = new SelectedItemCollection(this); + protected SelectionSpec mSpec; + protected ViewPager mPager; + + protected PreviewPagerAdapter mAdapter; + + protected CheckView mCheckView; + protected TextView mButtonBack; + protected TextView mButtonApply; + protected TextView mSize; + + protected int mPreviousPos = -1; + + private LinearLayout mOriginalLayout; + private CheckRadioView mOriginal; + protected boolean mOriginalEnable; + + private FrameLayout mBottomToolbar; + private FrameLayout mTopToolbar; + private boolean mIsToolbarHide = false; + + @Override + protected void onCreate(@Nullable Bundle savedInstanceState) { + setTheme(SelectionSpec.getInstance().themeId); + super.onCreate(savedInstanceState); + if (!SelectionSpec.getInstance().hasInited) { + setResult(RESULT_CANCELED); + finish(); + return; + } + setContentView(R.layout.activity_media_preview); + if (Platform.hasKitKat()) { + getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); + } + + mSpec = SelectionSpec.getInstance(); + if (mSpec.needOrientationRestriction()) { + setRequestedOrientation(mSpec.orientation); + } + + if (savedInstanceState == null) { + mSelectedCollection.onCreate(getIntent().getBundleExtra(EXTRA_DEFAULT_BUNDLE)); + mOriginalEnable = getIntent().getBooleanExtra(EXTRA_RESULT_ORIGINAL_ENABLE, false); + } else { + mSelectedCollection.onCreate(savedInstanceState); + mOriginalEnable = savedInstanceState.getBoolean(CHECK_STATE); + } + mButtonBack = (TextView) findViewById(R.id.button_back); + mButtonApply = (TextView) findViewById(R.id.button_apply); + mSize = (TextView) findViewById(R.id.size); + mButtonBack.setOnClickListener(this); + mButtonApply.setOnClickListener(this); + + mPager = (ViewPager) findViewById(R.id.pager); + mPager.addOnPageChangeListener(this); + mAdapter = new PreviewPagerAdapter(getSupportFragmentManager(), null); + mPager.setAdapter(mAdapter); + mCheckView = (CheckView) findViewById(R.id.check_view); + mCheckView.setCountable(mSpec.countable); + mBottomToolbar = findViewById(R.id.bottom_toolbar); + mTopToolbar = findViewById(R.id.top_toolbar); + + mCheckView.setOnClickListener(new View.OnClickListener() { + + @Override + public void onClick(View v) { + Item item = mAdapter.getMediaItem(mPager.getCurrentItem()); + if (mSelectedCollection.isSelected(item)) { + mSelectedCollection.remove(item); + if (mSpec.countable) { + mCheckView.setCheckedNum(CheckView.UNCHECKED); + } else { + mCheckView.setChecked(false); + } + } else { + if (assertAddSelection(item)) { + mSelectedCollection.add(item); + if (mSpec.countable) { + mCheckView.setCheckedNum(mSelectedCollection.checkedNumOf(item)); + } else { + mCheckView.setChecked(true); + } + } + } + updateApplyButton(); + + if (mSpec.onSelectedListener != null) { + mSpec.onSelectedListener.onSelected( + mSelectedCollection.asListOfUri(), mSelectedCollection.asListOfString()); + } + } + }); + + + mOriginalLayout = findViewById(R.id.originalLayout); + mOriginal = findViewById(R.id.original); + mOriginalLayout.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + + int count = countOverMaxSize(); + if (count > 0) { + IncapableDialog incapableDialog = IncapableDialog.newInstance("", + getString(R.string.error_over_original_count, count, mSpec.originalMaxSize)); + incapableDialog.show(getSupportFragmentManager(), + IncapableDialog.class.getName()); + return; + } + + mOriginalEnable = !mOriginalEnable; + mOriginal.setChecked(mOriginalEnable); + if (!mOriginalEnable) { + mOriginal.setColor(Color.WHITE); + } + + + if (mSpec.onCheckedListener != null) { + mSpec.onCheckedListener.onCheck(mOriginalEnable); + } + } + }); + + updateApplyButton(); + } + + @Override + protected void onSaveInstanceState(Bundle outState) { + mSelectedCollection.onSaveInstanceState(outState); + outState.putBoolean("checkState", mOriginalEnable); + super.onSaveInstanceState(outState); + } + + @Override + public void onBackPressed() { + sendBackResult(false); + super.onBackPressed(); + } + + @Override + public void onClick(View v) { + if (v.getId() == R.id.button_back) { + onBackPressed(); + } else if (v.getId() == R.id.button_apply) { + sendBackResult(true); + finish(); + } + } + + @Override + public void onClick() { + if (!mSpec.autoHideToobar) { + return; + } + + if (mIsToolbarHide) { + mTopToolbar.animate() + .setInterpolator(new FastOutSlowInInterpolator()) + .translationYBy(mTopToolbar.getMeasuredHeight()) + .start(); + mBottomToolbar.animate() + .translationYBy(-mBottomToolbar.getMeasuredHeight()) + .setInterpolator(new FastOutSlowInInterpolator()) + .start(); + } else { + mTopToolbar.animate() + .setInterpolator(new FastOutSlowInInterpolator()) + .translationYBy(-mTopToolbar.getMeasuredHeight()) + .start(); + mBottomToolbar.animate() + .setInterpolator(new FastOutSlowInInterpolator()) + .translationYBy(mBottomToolbar.getMeasuredHeight()) + .start(); + } + + mIsToolbarHide = !mIsToolbarHide; + + } + + @Override + public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { + + } + + @Override + public void onPageSelected(int position) { + PreviewPagerAdapter adapter = (PreviewPagerAdapter) mPager.getAdapter(); + if (mPreviousPos != -1 && mPreviousPos != position) { + ((PreviewItemFragment) adapter.instantiateItem(mPager, mPreviousPos)).resetView(); + + Item item = adapter.getMediaItem(position); + if (mSpec.countable) { + int checkedNum = mSelectedCollection.checkedNumOf(item); + mCheckView.setCheckedNum(checkedNum); + if (checkedNum > 0) { + mCheckView.setEnabled(true); + } else { + mCheckView.setEnabled(!mSelectedCollection.maxSelectableReached()); + } + } else { + boolean checked = mSelectedCollection.isSelected(item); + mCheckView.setChecked(checked); + if (checked) { + mCheckView.setEnabled(true); + } else { + mCheckView.setEnabled(!mSelectedCollection.maxSelectableReached()); + } + } + updateSize(item); + } + mPreviousPos = position; + } + + @Override + public void onPageScrollStateChanged(int state) { + + } + + private void updateApplyButton() { + int selectedCount = mSelectedCollection.count(); + if (selectedCount == 0) { + mButtonApply.setText(R.string.button_apply_default); + mButtonApply.setEnabled(false); + } else if (selectedCount == 1 && mSpec.singleSelectionModeEnabled()) { + mButtonApply.setText(R.string.button_apply_default); + mButtonApply.setEnabled(true); + } else { + mButtonApply.setEnabled(true); + mButtonApply.setText(getString(R.string.button_apply, selectedCount)); + } + + if (mSpec.originalable) { + mOriginalLayout.setVisibility(View.VISIBLE); + updateOriginalState(); + } else { + mOriginalLayout.setVisibility(View.GONE); + } + } + + + private void updateOriginalState() { + mOriginal.setChecked(mOriginalEnable); + if (!mOriginalEnable) { + mOriginal.setColor(Color.WHITE); + } + + if (countOverMaxSize() > 0) { + + if (mOriginalEnable) { + IncapableDialog incapableDialog = IncapableDialog.newInstance("", + getString(R.string.error_over_original_size, mSpec.originalMaxSize)); + incapableDialog.show(getSupportFragmentManager(), + IncapableDialog.class.getName()); + + mOriginal.setChecked(false); + mOriginal.setColor(Color.WHITE); + mOriginalEnable = false; + } + } + } + + + private int countOverMaxSize() { + int count = 0; + int selectedCount = mSelectedCollection.count(); + for (int i = 0; i < selectedCount; i++) { + Item item = mSelectedCollection.asList().get(i); + if (item.isImage()) { + float size = PhotoMetadataUtils.getSizeInMB(item.size); + if (size > mSpec.originalMaxSize) { + count++; + } + } + } + return count; + } + + protected void updateSize(Item item) { + if (item.isGif()) { + mSize.setVisibility(View.VISIBLE); + mSize.setText(PhotoMetadataUtils.getSizeInMB(item.size) + "M"); + } else { + mSize.setVisibility(View.GONE); + } + + if (item.isVideo()) { + mOriginalLayout.setVisibility(View.GONE); + } else if (mSpec.originalable) { + mOriginalLayout.setVisibility(View.VISIBLE); + } + } + + protected void sendBackResult(boolean apply) { + Intent intent = new Intent(); + intent.putExtra(EXTRA_RESULT_BUNDLE, mSelectedCollection.getDataWithBundle()); + intent.putExtra(EXTRA_RESULT_APPLY, apply); + intent.putExtra(EXTRA_RESULT_ORIGINAL_ENABLE, mOriginalEnable); + setResult(Activity.RESULT_OK, intent); + } + + private boolean assertAddSelection(Item item) { + IncapableCause cause = mSelectedCollection.isAcceptable(item); + IncapableCause.handleCause(this, cause); + return cause == null; + } +} diff --git a/YFDXJ/matisse/src/main/java/com/zhihu/matisse/internal/ui/MediaSelectionFragment.java b/YFDXJ/matisse/src/main/java/com/zhihu/matisse/internal/ui/MediaSelectionFragment.java new file mode 100644 index 0000000..86c46e2 --- /dev/null +++ b/YFDXJ/matisse/src/main/java/com/zhihu/matisse/internal/ui/MediaSelectionFragment.java @@ -0,0 +1,159 @@ +/* + * Copyright 2017 Zhihu Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.zhihu.matisse.internal.ui; + +import android.content.Context; +import android.database.Cursor; +import android.os.Bundle; +import androidx.annotation.Nullable; +import androidx.fragment.app.Fragment; +import androidx.recyclerview.widget.GridLayoutManager; +import androidx.recyclerview.widget.RecyclerView; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import com.zhihu.matisse.R; +import com.zhihu.matisse.internal.entity.Album; +import com.zhihu.matisse.internal.entity.Item; +import com.zhihu.matisse.internal.entity.SelectionSpec; +import com.zhihu.matisse.internal.model.AlbumMediaCollection; +import com.zhihu.matisse.internal.model.SelectedItemCollection; +import com.zhihu.matisse.internal.ui.adapter.AlbumMediaAdapter; +import com.zhihu.matisse.internal.ui.widget.MediaGridInset; +import com.zhihu.matisse.internal.utils.UIUtils; + +public class MediaSelectionFragment extends Fragment implements + AlbumMediaCollection.AlbumMediaCallbacks, AlbumMediaAdapter.CheckStateListener, + AlbumMediaAdapter.OnMediaClickListener { + + public static final String EXTRA_ALBUM = "extra_album"; + + private final AlbumMediaCollection mAlbumMediaCollection = new AlbumMediaCollection(); + private RecyclerView mRecyclerView; + private AlbumMediaAdapter mAdapter; + private SelectionProvider mSelectionProvider; + private AlbumMediaAdapter.CheckStateListener mCheckStateListener; + private AlbumMediaAdapter.OnMediaClickListener mOnMediaClickListener; + + public static MediaSelectionFragment newInstance(Album album) { + MediaSelectionFragment fragment = new MediaSelectionFragment(); + Bundle args = new Bundle(); + args.putParcelable(EXTRA_ALBUM, album); + fragment.setArguments(args); + return fragment; + } + + @Override + public void onAttach(Context context) { + super.onAttach(context); + if (context instanceof SelectionProvider) { + mSelectionProvider = (SelectionProvider) context; + } else { + throw new IllegalStateException("Context must implement SelectionProvider."); + } + if (context instanceof AlbumMediaAdapter.CheckStateListener) { + mCheckStateListener = (AlbumMediaAdapter.CheckStateListener) context; + } + if (context instanceof AlbumMediaAdapter.OnMediaClickListener) { + mOnMediaClickListener = (AlbumMediaAdapter.OnMediaClickListener) context; + } + } + + @Nullable + @Override + public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, + @Nullable Bundle savedInstanceState) { + return inflater.inflate(R.layout.fragment_media_selection, container, false); + } + + @Override + public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + mRecyclerView = (RecyclerView) view.findViewById(R.id.recyclerview); + } + + @Override + public void onActivityCreated(@Nullable Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + Album album = getArguments().getParcelable(EXTRA_ALBUM); + + mAdapter = new AlbumMediaAdapter(getContext(), + mSelectionProvider.provideSelectedItemCollection(), mRecyclerView); + mAdapter.registerCheckStateListener(this); + mAdapter.registerOnMediaClickListener(this); + mRecyclerView.setHasFixedSize(true); + + int spanCount; + SelectionSpec selectionSpec = SelectionSpec.getInstance(); + if (selectionSpec.gridExpectedSize > 0) { + spanCount = UIUtils.spanCount(getContext(), selectionSpec.gridExpectedSize); + } else { + spanCount = selectionSpec.spanCount; + } + mRecyclerView.setLayoutManager(new GridLayoutManager(getContext(), spanCount)); + + int spacing = getResources().getDimensionPixelSize(R.dimen.media_grid_spacing); + mRecyclerView.addItemDecoration(new MediaGridInset(spanCount, spacing, false)); + mRecyclerView.setAdapter(mAdapter); + mAlbumMediaCollection.onCreate(getActivity(), this); + mAlbumMediaCollection.load(album, selectionSpec.capture); + } + + @Override + public void onDestroyView() { + super.onDestroyView(); + mAlbumMediaCollection.onDestroy(); + } + + public void refreshMediaGrid() { + mAdapter.notifyDataSetChanged(); + } + + public void refreshSelection() { + mAdapter.refreshSelection(); + } + + @Override + public void onAlbumMediaLoad(Cursor cursor) { + mAdapter.swapCursor(cursor); + } + + @Override + public void onAlbumMediaReset() { + mAdapter.swapCursor(null); + } + + @Override + public void onUpdate() { + // notify outer Activity that check state changed + if (mCheckStateListener != null) { + mCheckStateListener.onUpdate(); + } + } + + @Override + public void onMediaClick(Album album, Item item, int adapterPosition) { + if (mOnMediaClickListener != null) { + mOnMediaClickListener.onMediaClick((Album) getArguments().getParcelable(EXTRA_ALBUM), + item, adapterPosition); + } + } + + public interface SelectionProvider { + SelectedItemCollection provideSelectedItemCollection(); + } +} diff --git a/YFDXJ/matisse/src/main/java/com/zhihu/matisse/internal/ui/PreviewItemFragment.java b/YFDXJ/matisse/src/main/java/com/zhihu/matisse/internal/ui/PreviewItemFragment.java new file mode 100644 index 0000000..47240b6 --- /dev/null +++ b/YFDXJ/matisse/src/main/java/com/zhihu/matisse/internal/ui/PreviewItemFragment.java @@ -0,0 +1,129 @@ +/* + * Copyright 2017 Zhihu Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.zhihu.matisse.internal.ui; + +import android.content.ActivityNotFoundException; +import android.content.Context; +import android.content.Intent; +import android.graphics.Point; +import android.os.Bundle; +import androidx.annotation.Nullable; +import androidx.fragment.app.Fragment; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.Toast; + +import com.zhihu.matisse.R; +import com.zhihu.matisse.internal.entity.Item; +import com.zhihu.matisse.internal.entity.SelectionSpec; +import com.zhihu.matisse.internal.utils.PhotoMetadataUtils; +import com.zhihu.matisse.listener.OnFragmentInteractionListener; + +import it.sephiroth.android.library.imagezoom.ImageViewTouch; +import it.sephiroth.android.library.imagezoom.ImageViewTouchBase; + +public class PreviewItemFragment extends Fragment { + + private static final String ARGS_ITEM = "args_item"; + private OnFragmentInteractionListener mListener; + + public static PreviewItemFragment newInstance(Item item) { + PreviewItemFragment fragment = new PreviewItemFragment(); + Bundle bundle = new Bundle(); + bundle.putParcelable(ARGS_ITEM, item); + fragment.setArguments(bundle); + return fragment; + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + return inflater.inflate(R.layout.fragment_preview_item, container, false); + } + + @Override + public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + final Item item = getArguments().getParcelable(ARGS_ITEM); + if (item == null) { + return; + } + + View videoPlayButton = view.findViewById(R.id.video_play_button); + if (item.isVideo()) { + videoPlayButton.setVisibility(View.VISIBLE); + videoPlayButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + Intent intent = new Intent(Intent.ACTION_VIEW); + intent.setDataAndType(item.uri, "video/*"); + try { + startActivity(intent); + } catch (ActivityNotFoundException e) { + Toast.makeText(getContext(), R.string.error_no_video_activity, Toast.LENGTH_SHORT).show(); + } + } + }); + } else { + videoPlayButton.setVisibility(View.GONE); + } + + ImageViewTouch image = (ImageViewTouch) view.findViewById(R.id.image_view); + image.setDisplayType(ImageViewTouchBase.DisplayType.FIT_TO_SCREEN); + + image.setSingleTapListener(new ImageViewTouch.OnImageViewTouchSingleTapListener() { + @Override + public void onSingleTapConfirmed() { + if (mListener != null) { + mListener.onClick(); + } + } + }); + + Point size = PhotoMetadataUtils.getBitmapSize(item.getContentUri(), getActivity()); + if (item.isGif()) { + SelectionSpec.getInstance().imageEngine.loadGifImage(getContext(), size.x, size.y, image, + item.getContentUri()); + } else { + SelectionSpec.getInstance().imageEngine.loadImage(getContext(), size.x, size.y, image, + item.getContentUri()); + } + } + + public void resetView() { + if (getView() != null) { + ((ImageViewTouch) getView().findViewById(R.id.image_view)).resetMatrix(); + } + } + + + @Override + public void onAttach(Context context) { + super.onAttach(context); + if (context instanceof OnFragmentInteractionListener) { + mListener = (OnFragmentInteractionListener) context; + } else { + throw new RuntimeException(context.toString() + + " must implement OnFragmentInteractionListener"); + } + } + + @Override + public void onDetach() { + super.onDetach(); + mListener = null; + } +} diff --git a/YFDXJ/matisse/src/main/java/com/zhihu/matisse/internal/ui/SelectedPreviewActivity.java b/YFDXJ/matisse/src/main/java/com/zhihu/matisse/internal/ui/SelectedPreviewActivity.java new file mode 100644 index 0000000..8b934c7 --- /dev/null +++ b/YFDXJ/matisse/src/main/java/com/zhihu/matisse/internal/ui/SelectedPreviewActivity.java @@ -0,0 +1,51 @@ +/* + * Copyright 2017 Zhihu Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.zhihu.matisse.internal.ui; + +import android.os.Bundle; +import androidx.annotation.Nullable; + +import com.zhihu.matisse.internal.entity.Item; +import com.zhihu.matisse.internal.entity.SelectionSpec; +import com.zhihu.matisse.internal.model.SelectedItemCollection; + +import java.util.List; + +public class SelectedPreviewActivity extends BasePreviewActivity { + + @Override + protected void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + if (!SelectionSpec.getInstance().hasInited) { + setResult(RESULT_CANCELED); + finish(); + return; + } + + Bundle bundle = getIntent().getBundleExtra(EXTRA_DEFAULT_BUNDLE); + List selected = bundle.getParcelableArrayList(SelectedItemCollection.STATE_SELECTION); + mAdapter.addAll(selected); + mAdapter.notifyDataSetChanged(); + if (mSpec.countable) { + mCheckView.setCheckedNum(1); + } else { + mCheckView.setChecked(true); + } + mPreviousPos = 0; + updateSize(selected.get(0)); + } + +} diff --git a/YFDXJ/matisse/src/main/java/com/zhihu/matisse/internal/ui/adapter/AlbumMediaAdapter.java b/YFDXJ/matisse/src/main/java/com/zhihu/matisse/internal/ui/adapter/AlbumMediaAdapter.java new file mode 100644 index 0000000..f0eb971 --- /dev/null +++ b/YFDXJ/matisse/src/main/java/com/zhihu/matisse/internal/ui/adapter/AlbumMediaAdapter.java @@ -0,0 +1,300 @@ +/* + * Copyright 2017 Zhihu Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.zhihu.matisse.internal.ui.adapter; + +import android.content.Context; +import android.content.res.TypedArray; +import android.database.Cursor; +import android.graphics.PorterDuff; +import android.graphics.drawable.Drawable; +import androidx.recyclerview.widget.GridLayoutManager; +import androidx.recyclerview.widget.RecyclerView; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.TextView; + +import com.zhihu.matisse.R; +import com.zhihu.matisse.internal.entity.Album; +import com.zhihu.matisse.internal.entity.Item; +import com.zhihu.matisse.internal.entity.SelectionSpec; +import com.zhihu.matisse.internal.entity.IncapableCause; +import com.zhihu.matisse.internal.model.SelectedItemCollection; +import com.zhihu.matisse.internal.ui.widget.CheckView; +import com.zhihu.matisse.internal.ui.widget.MediaGrid; + +public class AlbumMediaAdapter extends + RecyclerViewCursorAdapter implements + MediaGrid.OnMediaGridClickListener { + + private static final int VIEW_TYPE_CAPTURE = 0x01; + private static final int VIEW_TYPE_MEDIA = 0x02; + private final SelectedItemCollection mSelectedCollection; + private final Drawable mPlaceholder; + private SelectionSpec mSelectionSpec; + private CheckStateListener mCheckStateListener; + private OnMediaClickListener mOnMediaClickListener; + private RecyclerView mRecyclerView; + private int mImageResize; + + public AlbumMediaAdapter(Context context, SelectedItemCollection selectedCollection, RecyclerView recyclerView) { + super(null); + mSelectionSpec = SelectionSpec.getInstance(); + mSelectedCollection = selectedCollection; + + TypedArray ta = context.getTheme().obtainStyledAttributes(new int[]{R.attr.item_placeholder}); + mPlaceholder = ta.getDrawable(0); + ta.recycle(); + + mRecyclerView = recyclerView; + } + + @Override + public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + if (viewType == VIEW_TYPE_CAPTURE) { + View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.photo_capture_item, parent, false); + CaptureViewHolder holder = new CaptureViewHolder(v); + holder.itemView.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + if (v.getContext() instanceof OnPhotoCapture) { + ((OnPhotoCapture) v.getContext()).capture(); + } + } + }); + return holder; + } else if (viewType == VIEW_TYPE_MEDIA) { + View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.media_grid_item, parent, false); + return new MediaViewHolder(v); + } + return null; + } + + @Override + protected void onBindViewHolder(final RecyclerView.ViewHolder holder, Cursor cursor) { + if (holder instanceof CaptureViewHolder) { + CaptureViewHolder captureViewHolder = (CaptureViewHolder) holder; + Drawable[] drawables = captureViewHolder.mHint.getCompoundDrawables(); + TypedArray ta = holder.itemView.getContext().getTheme().obtainStyledAttributes( + new int[]{R.attr.capture_textColor}); + int color = ta.getColor(0, 0); + ta.recycle(); + + for (int i = 0; i < drawables.length; i++) { + Drawable drawable = drawables[i]; + if (drawable != null) { + final Drawable.ConstantState state = drawable.getConstantState(); + if (state == null) { + continue; + } + + Drawable newDrawable = state.newDrawable().mutate(); + newDrawable.setColorFilter(color, PorterDuff.Mode.SRC_IN); + newDrawable.setBounds(drawable.getBounds()); + drawables[i] = newDrawable; + } + } + captureViewHolder.mHint.setCompoundDrawables(drawables[0], drawables[1], drawables[2], drawables[3]); + } else if (holder instanceof MediaViewHolder) { + MediaViewHolder mediaViewHolder = (MediaViewHolder) holder; + + final Item item = Item.valueOf(cursor); + mediaViewHolder.mMediaGrid.preBindMedia(new MediaGrid.PreBindInfo( + getImageResize(mediaViewHolder.mMediaGrid.getContext()), + mPlaceholder, + mSelectionSpec.countable, + holder + )); + mediaViewHolder.mMediaGrid.bindMedia(item); + mediaViewHolder.mMediaGrid.setOnMediaGridClickListener(this); + setCheckStatus(item, mediaViewHolder.mMediaGrid); + } + } + + private void setCheckStatus(Item item, MediaGrid mediaGrid) { + if (mSelectionSpec.countable) { + int checkedNum = mSelectedCollection.checkedNumOf(item); + if (checkedNum > 0) { + mediaGrid.setCheckEnabled(true); + mediaGrid.setCheckedNum(checkedNum); + } else { + if (mSelectedCollection.maxSelectableReached()) { + mediaGrid.setCheckEnabled(false); + mediaGrid.setCheckedNum(CheckView.UNCHECKED); + } else { + mediaGrid.setCheckEnabled(true); + mediaGrid.setCheckedNum(checkedNum); + } + } + } else { + boolean selected = mSelectedCollection.isSelected(item); + if (selected) { + mediaGrid.setCheckEnabled(true); + mediaGrid.setChecked(true); + } else { + if (mSelectedCollection.maxSelectableReached()) { + mediaGrid.setCheckEnabled(false); + mediaGrid.setChecked(false); + } else { + mediaGrid.setCheckEnabled(true); + mediaGrid.setChecked(false); + } + } + } + } + + @Override + public void onThumbnailClicked(ImageView thumbnail, Item item, RecyclerView.ViewHolder holder) { + if (mSelectionSpec.showPreview) { + if (mOnMediaClickListener != null) { + mOnMediaClickListener.onMediaClick(null, item, holder.getAdapterPosition()); + } + } else { + updateSelectedItem(item, holder); + } + } + + @Override + public void onCheckViewClicked(CheckView checkView, Item item, RecyclerView.ViewHolder holder) { + updateSelectedItem(item, holder); + } + + private void updateSelectedItem(Item item, RecyclerView.ViewHolder holder) { + if (mSelectionSpec.countable) { + int checkedNum = mSelectedCollection.checkedNumOf(item); + if (checkedNum == CheckView.UNCHECKED) { + if (assertAddSelection(holder.itemView.getContext(), item)) { + mSelectedCollection.add(item); + notifyCheckStateChanged(); + } + } else { + mSelectedCollection.remove(item); + notifyCheckStateChanged(); + } + } else { + if (mSelectedCollection.isSelected(item)) { + mSelectedCollection.remove(item); + notifyCheckStateChanged(); + } else { + if (assertAddSelection(holder.itemView.getContext(), item)) { + mSelectedCollection.add(item); + notifyCheckStateChanged(); + } + } + } + } + + private void notifyCheckStateChanged() { + notifyDataSetChanged(); + if (mCheckStateListener != null) { + mCheckStateListener.onUpdate(); + } + } + + @Override + public int getItemViewType(int position, Cursor cursor) { + return Item.valueOf(cursor).isCapture() ? VIEW_TYPE_CAPTURE : VIEW_TYPE_MEDIA; + } + + private boolean assertAddSelection(Context context, Item item) { + IncapableCause cause = mSelectedCollection.isAcceptable(item); + IncapableCause.handleCause(context, cause); + return cause == null; + } + + + public void registerCheckStateListener(CheckStateListener listener) { + mCheckStateListener = listener; + } + + public void unregisterCheckStateListener() { + mCheckStateListener = null; + } + + public void registerOnMediaClickListener(OnMediaClickListener listener) { + mOnMediaClickListener = listener; + } + + public void unregisterOnMediaClickListener() { + mOnMediaClickListener = null; + } + + public void refreshSelection() { + GridLayoutManager layoutManager = (GridLayoutManager) mRecyclerView.getLayoutManager(); + int first = layoutManager.findFirstVisibleItemPosition(); + int last = layoutManager.findLastVisibleItemPosition(); + if (first == -1 || last == -1) { + return; + } + Cursor cursor = getCursor(); + for (int i = first; i <= last; i++) { + RecyclerView.ViewHolder holder = mRecyclerView.findViewHolderForAdapterPosition(first); + if (holder instanceof MediaViewHolder) { + if (cursor.moveToPosition(i)) { + setCheckStatus(Item.valueOf(cursor), ((MediaViewHolder) holder).mMediaGrid); + } + } + } + } + + private int getImageResize(Context context) { + if (mImageResize == 0) { + RecyclerView.LayoutManager lm = mRecyclerView.getLayoutManager(); + int spanCount = ((GridLayoutManager) lm).getSpanCount(); + int screenWidth = context.getResources().getDisplayMetrics().widthPixels; + int availableWidth = screenWidth - context.getResources().getDimensionPixelSize( + R.dimen.media_grid_spacing) * (spanCount - 1); + mImageResize = availableWidth / spanCount; + mImageResize = (int) (mImageResize * mSelectionSpec.thumbnailScale); + } + return mImageResize; + } + + public interface CheckStateListener { + void onUpdate(); + } + + public interface OnMediaClickListener { + void onMediaClick(Album album, Item item, int adapterPosition); + } + + public interface OnPhotoCapture { + void capture(); + } + + private static class MediaViewHolder extends RecyclerView.ViewHolder { + + private MediaGrid mMediaGrid; + + MediaViewHolder(View itemView) { + super(itemView); + mMediaGrid = (MediaGrid) itemView; + } + } + + private static class CaptureViewHolder extends RecyclerView.ViewHolder { + + private TextView mHint; + + CaptureViewHolder(View itemView) { + super(itemView); + + mHint = (TextView) itemView.findViewById(R.id.hint); + } + } + +} diff --git a/YFDXJ/matisse/src/main/java/com/zhihu/matisse/internal/ui/adapter/AlbumsAdapter.java b/YFDXJ/matisse/src/main/java/com/zhihu/matisse/internal/ui/adapter/AlbumsAdapter.java new file mode 100644 index 0000000..8e809c9 --- /dev/null +++ b/YFDXJ/matisse/src/main/java/com/zhihu/matisse/internal/ui/adapter/AlbumsAdapter.java @@ -0,0 +1,71 @@ +/* + * Copyright 2017 Zhihu Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.zhihu.matisse.internal.ui.adapter; + +import android.content.Context; +import android.content.res.TypedArray; +import android.database.Cursor; +import android.graphics.drawable.Drawable; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.CursorAdapter; +import android.widget.ImageView; +import android.widget.TextView; + +import com.zhihu.matisse.R; +import com.zhihu.matisse.internal.entity.Album; +import com.zhihu.matisse.internal.entity.SelectionSpec; + +public class AlbumsAdapter extends CursorAdapter { + + private final Drawable mPlaceholder; + + public AlbumsAdapter(Context context, Cursor c, boolean autoRequery) { + super(context, c, autoRequery); + + TypedArray ta = context.getTheme().obtainStyledAttributes( + new int[]{R.attr.album_thumbnail_placeholder}); + mPlaceholder = ta.getDrawable(0); + ta.recycle(); + } + + public AlbumsAdapter(Context context, Cursor c, int flags) { + super(context, c, flags); + + TypedArray ta = context.getTheme().obtainStyledAttributes( + new int[]{R.attr.album_thumbnail_placeholder}); + mPlaceholder = ta.getDrawable(0); + ta.recycle(); + } + + @Override + public View newView(Context context, Cursor cursor, ViewGroup parent) { + return LayoutInflater.from(context).inflate(R.layout.album_list_item, parent, false); + } + + @Override + public void bindView(View view, Context context, Cursor cursor) { + Album album = Album.valueOf(cursor); + ((TextView) view.findViewById(R.id.album_name)).setText(album.getDisplayName(context)); + ((TextView) view.findViewById(R.id.album_media_count)).setText(String.valueOf(album.getCount())); + + // do not need to load animated Gif + SelectionSpec.getInstance().imageEngine.loadThumbnail(context, context.getResources().getDimensionPixelSize(R + .dimen.media_grid_size), mPlaceholder, + (ImageView) view.findViewById(R.id.album_cover), album.getCoverUri()); + } +} diff --git a/YFDXJ/matisse/src/main/java/com/zhihu/matisse/internal/ui/adapter/PreviewPagerAdapter.java b/YFDXJ/matisse/src/main/java/com/zhihu/matisse/internal/ui/adapter/PreviewPagerAdapter.java new file mode 100644 index 0000000..b75abc1 --- /dev/null +++ b/YFDXJ/matisse/src/main/java/com/zhihu/matisse/internal/ui/adapter/PreviewPagerAdapter.java @@ -0,0 +1,70 @@ +/* + * Copyright 2017 Zhihu Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.zhihu.matisse.internal.ui.adapter; + +import androidx.fragment.app.Fragment; +import androidx.fragment.app.FragmentManager; +import androidx.fragment.app.FragmentPagerAdapter; +import android.view.ViewGroup; + +import com.zhihu.matisse.internal.entity.Item; +import com.zhihu.matisse.internal.ui.PreviewItemFragment; + +import java.util.ArrayList; +import java.util.List; + +public class PreviewPagerAdapter extends FragmentPagerAdapter { + + private ArrayList mItems = new ArrayList<>(); + private OnPrimaryItemSetListener mListener; + + public PreviewPagerAdapter(FragmentManager manager, OnPrimaryItemSetListener listener) { + super(manager); + mListener = listener; + } + + @Override + public Fragment getItem(int position) { + return PreviewItemFragment.newInstance(mItems.get(position)); + } + + @Override + public int getCount() { + return mItems.size(); + } + + @Override + public void setPrimaryItem(ViewGroup container, int position, Object object) { + super.setPrimaryItem(container, position, object); + if (mListener != null) { + mListener.onPrimaryItemSet(position); + } + } + + public Item getMediaItem(int position) { + return mItems.get(position); + } + + public void addAll(List items) { + mItems.addAll(items); + } + + interface OnPrimaryItemSetListener { + + void onPrimaryItemSet(int position); + } + +} diff --git a/YFDXJ/matisse/src/main/java/com/zhihu/matisse/internal/ui/adapter/RecyclerViewCursorAdapter.java b/YFDXJ/matisse/src/main/java/com/zhihu/matisse/internal/ui/adapter/RecyclerViewCursorAdapter.java new file mode 100644 index 0000000..48203b0 --- /dev/null +++ b/YFDXJ/matisse/src/main/java/com/zhihu/matisse/internal/ui/adapter/RecyclerViewCursorAdapter.java @@ -0,0 +1,105 @@ +/* + * Copyright 2017 Zhihu Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.zhihu.matisse.internal.ui.adapter; + +import android.database.Cursor; +import android.provider.MediaStore; +import androidx.recyclerview.widget.RecyclerView; + +public abstract class RecyclerViewCursorAdapter extends + RecyclerView.Adapter { + + private Cursor mCursor; + private int mRowIDColumn; + + RecyclerViewCursorAdapter(Cursor c) { + setHasStableIds(true); + swapCursor(c); + } + + protected abstract void onBindViewHolder(VH holder, Cursor cursor); + + @Override + public void onBindViewHolder(VH holder, int position) { + if (!isDataValid(mCursor)) { + throw new IllegalStateException("Cannot bind view holder when cursor is in invalid state."); + } + if (!mCursor.moveToPosition(position)) { + throw new IllegalStateException("Could not move cursor to position " + position + + " when trying to bind view holder"); + } + + onBindViewHolder(holder, mCursor); + } + + @Override + public int getItemViewType(int position) { + if (!mCursor.moveToPosition(position)) { + throw new IllegalStateException("Could not move cursor to position " + position + + " when trying to get item view type."); + } + return getItemViewType(position, mCursor); + } + + protected abstract int getItemViewType(int position, Cursor cursor); + + @Override + public int getItemCount() { + if (isDataValid(mCursor)) { + return mCursor.getCount(); + } else { + return 0; + } + } + + @Override + public long getItemId(int position) { + if (!isDataValid(mCursor)) { + throw new IllegalStateException("Cannot lookup item id when cursor is in invalid state."); + } + if (!mCursor.moveToPosition(position)) { + throw new IllegalStateException("Could not move cursor to position " + position + + " when trying to get an item id"); + } + + return mCursor.getLong(mRowIDColumn); + } + + public void swapCursor(Cursor newCursor) { + if (newCursor == mCursor) { + return; + } + + if (newCursor != null) { + mCursor = newCursor; + mRowIDColumn = mCursor.getColumnIndexOrThrow(MediaStore.Files.FileColumns._ID); + // notify the observers about the new cursor + notifyDataSetChanged(); + } else { + notifyItemRangeRemoved(0, getItemCount()); + mCursor = null; + mRowIDColumn = -1; + } + } + + public Cursor getCursor() { + return mCursor; + } + + private boolean isDataValid(Cursor cursor) { + return cursor != null && !cursor.isClosed(); + } +} diff --git a/YFDXJ/matisse/src/main/java/com/zhihu/matisse/internal/ui/widget/AlbumsSpinner.java b/YFDXJ/matisse/src/main/java/com/zhihu/matisse/internal/ui/widget/AlbumsSpinner.java new file mode 100644 index 0000000..92dfa27 --- /dev/null +++ b/YFDXJ/matisse/src/main/java/com/zhihu/matisse/internal/ui/widget/AlbumsSpinner.java @@ -0,0 +1,129 @@ +/* + * Copyright 2017 Zhihu Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.zhihu.matisse.internal.ui.widget; + +import android.content.Context; +import android.content.res.TypedArray; +import android.database.Cursor; +import android.graphics.PorterDuff; +import android.graphics.drawable.Drawable; +import androidx.annotation.NonNull; +import androidx.appcompat.widget.ListPopupWindow; +import android.view.View; +import android.widget.AdapterView; +import android.widget.CursorAdapter; +import android.widget.TextView; + +import com.zhihu.matisse.R; +import com.zhihu.matisse.internal.entity.Album; +import com.zhihu.matisse.internal.utils.Platform; + +public class AlbumsSpinner { + + private static final int MAX_SHOWN_COUNT = 6; + private CursorAdapter mAdapter; + private TextView mSelected; + private ListPopupWindow mListPopupWindow; + private AdapterView.OnItemSelectedListener mOnItemSelectedListener; + + public AlbumsSpinner(@NonNull Context context) { + mListPopupWindow = new ListPopupWindow(context, null, R.attr.listPopupWindowStyle); + mListPopupWindow.setModal(true); + float density = context.getResources().getDisplayMetrics().density; + mListPopupWindow.setContentWidth((int) (216 * density)); + mListPopupWindow.setHorizontalOffset((int) (16 * density)); + mListPopupWindow.setVerticalOffset((int) (-48 * density)); + + mListPopupWindow.setOnItemClickListener(new AdapterView.OnItemClickListener() { + + @Override + public void onItemClick(AdapterView parent, View view, int position, long id) { + AlbumsSpinner.this.onItemSelected(parent.getContext(), position); + if (mOnItemSelectedListener != null) { + mOnItemSelectedListener.onItemSelected(parent, view, position, id); + } + } + }); + } + + public void setOnItemSelectedListener(AdapterView.OnItemSelectedListener listener) { + mOnItemSelectedListener = listener; + } + + public void setSelection(Context context, int position) { + mListPopupWindow.setSelection(position); + onItemSelected(context, position); + } + + private void onItemSelected(Context context, int position) { + mListPopupWindow.dismiss(); + Cursor cursor = mAdapter.getCursor(); + cursor.moveToPosition(position); + Album album = Album.valueOf(cursor); + String displayName = album.getDisplayName(context); + if (mSelected.getVisibility() == View.VISIBLE) { + mSelected.setText(displayName); + } else { + if (Platform.hasICS()) { + mSelected.setAlpha(0.0f); + mSelected.setVisibility(View.VISIBLE); + mSelected.setText(displayName); + mSelected.animate().alpha(1.0f).setDuration(context.getResources().getInteger( + android.R.integer.config_longAnimTime)).start(); + } else { + mSelected.setVisibility(View.VISIBLE); + mSelected.setText(displayName); + } + + } + } + + public void setAdapter(CursorAdapter adapter) { + mListPopupWindow.setAdapter(adapter); + mAdapter = adapter; + } + + public void setSelectedTextView(TextView textView) { + mSelected = textView; + // tint dropdown arrow icon + Drawable[] drawables = mSelected.getCompoundDrawables(); + Drawable right = drawables[2]; + TypedArray ta = mSelected.getContext().getTheme().obtainStyledAttributes( + new int[]{R.attr.album_element_color}); + int color = ta.getColor(0, 0); + ta.recycle(); + right.setColorFilter(color, PorterDuff.Mode.SRC_IN); + + mSelected.setVisibility(View.GONE); + mSelected.setOnClickListener(new View.OnClickListener() { + + @Override + public void onClick(View v) { + int itemHeight = v.getResources().getDimensionPixelSize(R.dimen.album_item_height); + mListPopupWindow.setHeight( + mAdapter.getCount() > MAX_SHOWN_COUNT ? itemHeight * MAX_SHOWN_COUNT + : itemHeight * mAdapter.getCount()); + mListPopupWindow.show(); + } + }); + mSelected.setOnTouchListener(mListPopupWindow.createDragToOpenListener(mSelected)); + } + + public void setPopupAnchorView(View view) { + mListPopupWindow.setAnchorView(view); + } + +} diff --git a/YFDXJ/matisse/src/main/java/com/zhihu/matisse/internal/ui/widget/CheckRadioView.java b/YFDXJ/matisse/src/main/java/com/zhihu/matisse/internal/ui/widget/CheckRadioView.java new file mode 100644 index 0000000..1d71b72 --- /dev/null +++ b/YFDXJ/matisse/src/main/java/com/zhihu/matisse/internal/ui/widget/CheckRadioView.java @@ -0,0 +1,60 @@ +package com.zhihu.matisse.internal.ui.widget; + +import android.content.Context; +import android.graphics.PorterDuff; +import android.graphics.drawable.Drawable; +import androidx.core.content.res.ResourcesCompat; +import androidx.appcompat.widget.AppCompatImageView; +import android.util.AttributeSet; + +import com.zhihu.matisse.R; + +public class CheckRadioView extends AppCompatImageView { + + private Drawable mDrawable; + + private int mSelectedColor; + private int mUnSelectUdColor; + + public CheckRadioView(Context context) { + super(context); + init(); + } + + + + public CheckRadioView(Context context, AttributeSet attrs) { + super(context, attrs); + init(); + } + + private void init() { + mSelectedColor = ResourcesCompat.getColor( + getResources(), R.color.zhihu_item_checkCircle_backgroundColor, + getContext().getTheme()); + mUnSelectUdColor = ResourcesCompat.getColor( + getResources(), R.color.zhihu_check_original_radio_disable, + getContext().getTheme()); + setChecked(false); + } + + public void setChecked(boolean enable) { + if (enable) { + setImageResource(R.drawable.ic_preview_radio_on); + mDrawable = getDrawable(); + mDrawable.setColorFilter(mSelectedColor, PorterDuff.Mode.SRC_IN); + } else { + setImageResource(R.drawable.ic_preview_radio_off); + mDrawable = getDrawable(); + mDrawable.setColorFilter(mUnSelectUdColor, PorterDuff.Mode.SRC_IN); + } + } + + + public void setColor(int color) { + if (mDrawable == null) { + mDrawable = getDrawable(); + } + mDrawable.setColorFilter(color, PorterDuff.Mode.SRC_IN); + } +} diff --git a/YFDXJ/matisse/src/main/java/com/zhihu/matisse/internal/ui/widget/CheckView.java b/YFDXJ/matisse/src/main/java/com/zhihu/matisse/internal/ui/widget/CheckView.java new file mode 100644 index 0000000..7407325 --- /dev/null +++ b/YFDXJ/matisse/src/main/java/com/zhihu/matisse/internal/ui/widget/CheckView.java @@ -0,0 +1,229 @@ +/* + * Copyright 2017 Zhihu Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.zhihu.matisse.internal.ui.widget; + +import android.content.Context; +import android.content.res.TypedArray; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Paint; +import android.graphics.PorterDuff; +import android.graphics.PorterDuffXfermode; +import android.graphics.RadialGradient; +import android.graphics.Rect; +import android.graphics.Shader; +import android.graphics.Typeface; +import android.graphics.drawable.Drawable; +import androidx.core.content.res.ResourcesCompat; +import android.text.TextPaint; +import android.util.AttributeSet; +import android.view.View; + +import com.zhihu.matisse.R; + +public class CheckView extends View { + + public static final int UNCHECKED = Integer.MIN_VALUE; + private static final float STROKE_WIDTH = 3.0f; // dp + private static final float SHADOW_WIDTH = 6.0f; // dp + private static final int SIZE = 48; // dp + private static final float STROKE_RADIUS = 11.5f; // dp + private static final float BG_RADIUS = 11.0f; // dp + private static final int CONTENT_SIZE = 16; // dp + private boolean mCountable; + private boolean mChecked; + private int mCheckedNum; + private Paint mStrokePaint; + private Paint mBackgroundPaint; + private TextPaint mTextPaint; + private Paint mShadowPaint; + private Drawable mCheckDrawable; + private float mDensity; + private Rect mCheckRect; + private boolean mEnabled = true; + + public CheckView(Context context) { + super(context); + init(context); + } + + public CheckView(Context context, AttributeSet attrs) { + super(context, attrs); + init(context); + } + + public CheckView(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + init(context); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + // fixed size 48dp x 48dp + int sizeSpec = MeasureSpec.makeMeasureSpec((int) (SIZE * mDensity), MeasureSpec.EXACTLY); + super.onMeasure(sizeSpec, sizeSpec); + } + + private void init(Context context) { + mDensity = context.getResources().getDisplayMetrics().density; + + mStrokePaint = new Paint(); + mStrokePaint.setAntiAlias(true); + mStrokePaint.setStyle(Paint.Style.STROKE); + mStrokePaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_OVER)); + mStrokePaint.setStrokeWidth(STROKE_WIDTH * mDensity); + TypedArray ta = getContext().getTheme().obtainStyledAttributes(new int[]{R.attr.item_checkCircle_borderColor}); + int defaultColor = ResourcesCompat.getColor( + getResources(), R.color.zhihu_item_checkCircle_borderColor, + getContext().getTheme()); + int color = ta.getColor(0, defaultColor); + ta.recycle(); + mStrokePaint.setColor(color); + + mCheckDrawable = ResourcesCompat.getDrawable(context.getResources(), + R.drawable.ic_check_white_18dp, context.getTheme()); + } + + public void setChecked(boolean checked) { + if (mCountable) { + throw new IllegalStateException("CheckView is countable, call setCheckedNum() instead."); + } + mChecked = checked; + invalidate(); + } + + public void setCountable(boolean countable) { + mCountable = countable; + } + + public void setCheckedNum(int checkedNum) { + if (!mCountable) { + throw new IllegalStateException("CheckView is not countable, call setChecked() instead."); + } + if (checkedNum != UNCHECKED && checkedNum <= 0) { + throw new IllegalArgumentException("checked num can't be negative."); + } + mCheckedNum = checkedNum; + invalidate(); + } + + public void setEnabled(boolean enabled) { + if (mEnabled != enabled) { + mEnabled = enabled; + invalidate(); + } + } + + @Override + protected void onDraw(Canvas canvas) { + super.onDraw(canvas); + + // draw outer and inner shadow + initShadowPaint(); + canvas.drawCircle((float) SIZE * mDensity / 2, (float) SIZE * mDensity / 2, + (STROKE_RADIUS + STROKE_WIDTH / 2 + SHADOW_WIDTH) * mDensity, mShadowPaint); + + // draw white stroke + canvas.drawCircle((float) SIZE * mDensity / 2, (float) SIZE * mDensity / 2, + STROKE_RADIUS * mDensity, mStrokePaint); + + // draw content + if (mCountable) { + if (mCheckedNum != UNCHECKED) { + initBackgroundPaint(); + canvas.drawCircle((float) SIZE * mDensity / 2, (float) SIZE * mDensity / 2, + BG_RADIUS * mDensity, mBackgroundPaint); + initTextPaint(); + String text = String.valueOf(mCheckedNum); + int baseX = (int) (canvas.getWidth() - mTextPaint.measureText(text)) / 2; + int baseY = (int) (canvas.getHeight() - mTextPaint.descent() - mTextPaint.ascent()) / 2; + canvas.drawText(text, baseX, baseY, mTextPaint); + } + } else { + if (mChecked) { + initBackgroundPaint(); + canvas.drawCircle((float) SIZE * mDensity / 2, (float) SIZE * mDensity / 2, + BG_RADIUS * mDensity, mBackgroundPaint); + + mCheckDrawable.setBounds(getCheckRect()); + mCheckDrawable.draw(canvas); + } + } + + // enable hint + setAlpha(mEnabled ? 1.0f : 0.5f); + } + + private void initShadowPaint() { + if (mShadowPaint == null) { + mShadowPaint = new Paint(); + mShadowPaint.setAntiAlias(true); + // all in dp + float outerRadius = STROKE_RADIUS + STROKE_WIDTH / 2; + float innerRadius = outerRadius - STROKE_WIDTH; + float gradientRadius = outerRadius + SHADOW_WIDTH; + float stop0 = (innerRadius - SHADOW_WIDTH) / gradientRadius; + float stop1 = innerRadius / gradientRadius; + float stop2 = outerRadius / gradientRadius; + float stop3 = 1.0f; + mShadowPaint.setShader( + new RadialGradient((float) SIZE * mDensity / 2, + (float) SIZE * mDensity / 2, + gradientRadius * mDensity, + new int[]{Color.parseColor("#00000000"), Color.parseColor("#0D000000"), + Color.parseColor("#0D000000"), Color.parseColor("#00000000")}, + new float[]{stop0, stop1, stop2, stop3}, + Shader.TileMode.CLAMP)); + } + } + + private void initBackgroundPaint() { + if (mBackgroundPaint == null) { + mBackgroundPaint = new Paint(); + mBackgroundPaint.setAntiAlias(true); + mBackgroundPaint.setStyle(Paint.Style.FILL); + TypedArray ta = getContext().getTheme() + .obtainStyledAttributes(new int[]{R.attr.item_checkCircle_backgroundColor}); + int defaultColor = ResourcesCompat.getColor( + getResources(), R.color.zhihu_item_checkCircle_backgroundColor, + getContext().getTheme()); + int color = ta.getColor(0, defaultColor); + ta.recycle(); + mBackgroundPaint.setColor(color); + } + } + + private void initTextPaint() { + if (mTextPaint == null) { + mTextPaint = new TextPaint(); + mTextPaint.setAntiAlias(true); + mTextPaint.setColor(Color.WHITE); + mTextPaint.setTypeface(Typeface.create(Typeface.DEFAULT, Typeface.BOLD)); + mTextPaint.setTextSize(12.0f * mDensity); + } + } + + // rect for drawing checked number or mark + private Rect getCheckRect() { + if (mCheckRect == null) { + int rectPadding = (int) (SIZE * mDensity / 2 - CONTENT_SIZE * mDensity / 2); + mCheckRect = new Rect(rectPadding, rectPadding, + (int) (SIZE * mDensity - rectPadding), (int) (SIZE * mDensity - rectPadding)); + } + + return mCheckRect; + } +} diff --git a/YFDXJ/matisse/src/main/java/com/zhihu/matisse/internal/ui/widget/IncapableDialog.java b/YFDXJ/matisse/src/main/java/com/zhihu/matisse/internal/ui/widget/IncapableDialog.java new file mode 100644 index 0000000..a1b7201 --- /dev/null +++ b/YFDXJ/matisse/src/main/java/com/zhihu/matisse/internal/ui/widget/IncapableDialog.java @@ -0,0 +1,64 @@ +/* + * Copyright 2017 Zhihu Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.zhihu.matisse.internal.ui.widget; + +import android.app.Dialog; +import android.content.DialogInterface; +import android.os.Bundle; +import androidx.annotation.NonNull; +import androidx.fragment.app.DialogFragment; +import androidx.appcompat.app.AlertDialog; +import android.text.TextUtils; + +import com.zhihu.matisse.R; + +public class IncapableDialog extends DialogFragment { + + public static final String EXTRA_TITLE = "extra_title"; + public static final String EXTRA_MESSAGE = "extra_message"; + + public static IncapableDialog newInstance(String title, String message) { + IncapableDialog dialog = new IncapableDialog(); + Bundle args = new Bundle(); + args.putString(EXTRA_TITLE, title); + args.putString(EXTRA_MESSAGE, message); + dialog.setArguments(args); + return dialog; + } + + @NonNull + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) { + String title = getArguments().getString(EXTRA_TITLE); + String message = getArguments().getString(EXTRA_MESSAGE); + + AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); + if (!TextUtils.isEmpty(title)) { + builder.setTitle(title); + } + if (!TextUtils.isEmpty(message)) { + builder.setMessage(message); + } + builder.setPositiveButton(R.string.button_ok, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + dialog.dismiss(); + } + }); + + return builder.create(); + } +} diff --git a/YFDXJ/matisse/src/main/java/com/zhihu/matisse/internal/ui/widget/MediaGrid.java b/YFDXJ/matisse/src/main/java/com/zhihu/matisse/internal/ui/widget/MediaGrid.java new file mode 100644 index 0000000..0abb367 --- /dev/null +++ b/YFDXJ/matisse/src/main/java/com/zhihu/matisse/internal/ui/widget/MediaGrid.java @@ -0,0 +1,161 @@ +/* + * Copyright 2017 Zhihu Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.zhihu.matisse.internal.ui.widget; + +import android.content.Context; +import android.graphics.drawable.Drawable; +import androidx.recyclerview.widget.RecyclerView; +import android.text.format.DateUtils; +import android.util.AttributeSet; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.ImageView; +import android.widget.TextView; + +import com.zhihu.matisse.R; +import com.zhihu.matisse.internal.entity.Item; +import com.zhihu.matisse.internal.entity.SelectionSpec; + +public class MediaGrid extends SquareFrameLayout implements View.OnClickListener { + + private ImageView mThumbnail; + private CheckView mCheckView; + private ImageView mGifTag; + private TextView mVideoDuration; + + private Item mMedia; + private PreBindInfo mPreBindInfo; + private OnMediaGridClickListener mListener; + + public MediaGrid(Context context) { + super(context); + init(context); + } + + public MediaGrid(Context context, AttributeSet attrs) { + super(context, attrs); + init(context); + } + + private void init(Context context) { + LayoutInflater.from(context).inflate(R.layout.media_grid_content, this, true); + + mThumbnail = (ImageView) findViewById(R.id.media_thumbnail); + mCheckView = (CheckView) findViewById(R.id.check_view); + mGifTag = (ImageView) findViewById(R.id.gif); + mVideoDuration = (TextView) findViewById(R.id.video_duration); + + mThumbnail.setOnClickListener(this); + mCheckView.setOnClickListener(this); + } + + @Override + public void onClick(View v) { + if (mListener != null) { + if (v == mThumbnail) { + mListener.onThumbnailClicked(mThumbnail, mMedia, mPreBindInfo.mViewHolder); + } else if (v == mCheckView) { + mListener.onCheckViewClicked(mCheckView, mMedia, mPreBindInfo.mViewHolder); + } + } + } + + public void preBindMedia(PreBindInfo info) { + mPreBindInfo = info; + } + + public void bindMedia(Item item) { + mMedia = item; + setGifTag(); + initCheckView(); + setImage(); + setVideoDuration(); + } + + public Item getMedia() { + return mMedia; + } + + private void setGifTag() { + mGifTag.setVisibility(mMedia.isGif() ? View.VISIBLE : View.GONE); + } + + private void initCheckView() { + mCheckView.setCountable(mPreBindInfo.mCheckViewCountable); + } + + public void setCheckEnabled(boolean enabled) { + mCheckView.setEnabled(enabled); + } + + public void setCheckedNum(int checkedNum) { + mCheckView.setCheckedNum(checkedNum); + } + + public void setChecked(boolean checked) { + mCheckView.setChecked(checked); + } + + private void setImage() { + if (mMedia.isGif()) { + SelectionSpec.getInstance().imageEngine.loadGifThumbnail(getContext(), mPreBindInfo.mResize, + mPreBindInfo.mPlaceholder, mThumbnail, mMedia.getContentUri()); + } else { + SelectionSpec.getInstance().imageEngine.loadThumbnail(getContext(), mPreBindInfo.mResize, + mPreBindInfo.mPlaceholder, mThumbnail, mMedia.getContentUri()); + } + } + + private void setVideoDuration() { + if (mMedia.isVideo()) { + mVideoDuration.setVisibility(VISIBLE); + mVideoDuration.setText(DateUtils.formatElapsedTime(mMedia.duration / 1000)); + } else { + mVideoDuration.setVisibility(GONE); + } + } + + public void setOnMediaGridClickListener(OnMediaGridClickListener listener) { + mListener = listener; + } + + public void removeOnMediaGridClickListener() { + mListener = null; + } + + public interface OnMediaGridClickListener { + + void onThumbnailClicked(ImageView thumbnail, Item item, RecyclerView.ViewHolder holder); + + void onCheckViewClicked(CheckView checkView, Item item, RecyclerView.ViewHolder holder); + } + + public static class PreBindInfo { + int mResize; + Drawable mPlaceholder; + boolean mCheckViewCountable; + RecyclerView.ViewHolder mViewHolder; + + public PreBindInfo(int resize, Drawable placeholder, boolean checkViewCountable, + RecyclerView.ViewHolder viewHolder) { + mResize = resize; + mPlaceholder = placeholder; + mCheckViewCountable = checkViewCountable; + mViewHolder = viewHolder; + } + } + +} diff --git a/YFDXJ/matisse/src/main/java/com/zhihu/matisse/internal/ui/widget/MediaGridInset.java b/YFDXJ/matisse/src/main/java/com/zhihu/matisse/internal/ui/widget/MediaGridInset.java new file mode 100644 index 0000000..dca609c --- /dev/null +++ b/YFDXJ/matisse/src/main/java/com/zhihu/matisse/internal/ui/widget/MediaGridInset.java @@ -0,0 +1,60 @@ +/* + * Copyright 2017 Zhihu Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.zhihu.matisse.internal.ui.widget; + +import android.graphics.Rect; +import androidx.recyclerview.widget.RecyclerView; +import android.view.View; + +public class MediaGridInset extends RecyclerView.ItemDecoration { + + private int mSpanCount; + private int mSpacing; + private boolean mIncludeEdge; + + public MediaGridInset(int spanCount, int spacing, boolean includeEdge) { + this.mSpanCount = spanCount; + this.mSpacing = spacing; + this.mIncludeEdge = includeEdge; + } + + @Override + public void getItemOffsets(Rect outRect, View view, RecyclerView parent, + RecyclerView.State state) { + int position = parent.getChildAdapterPosition(view); // item position + int column = position % mSpanCount; // item column + + if (mIncludeEdge) { + // spacing - column * ((1f / spanCount) * spacing) + outRect.left = mSpacing - column * mSpacing / mSpanCount; + // (column + 1) * ((1f / spanCount) * spacing) + outRect.right = (column + 1) * mSpacing / mSpanCount; + + if (position < mSpanCount) { // top edge + outRect.top = mSpacing; + } + outRect.bottom = mSpacing; // item bottom + } else { + // column * ((1f / spanCount) * spacing) + outRect.left = column * mSpacing / mSpanCount; + // spacing - (column + 1) * ((1f / spanCount) * spacing) + outRect.right = mSpacing - (column + 1) * mSpacing / mSpanCount; + if (position >= mSpanCount) { + outRect.top = mSpacing; // item top + } + } + } +} diff --git a/YFDXJ/matisse/src/main/java/com/zhihu/matisse/internal/ui/widget/PreviewViewPager.java b/YFDXJ/matisse/src/main/java/com/zhihu/matisse/internal/ui/widget/PreviewViewPager.java new file mode 100644 index 0000000..b3e66c3 --- /dev/null +++ b/YFDXJ/matisse/src/main/java/com/zhihu/matisse/internal/ui/widget/PreviewViewPager.java @@ -0,0 +1,38 @@ +/* + * Copyright 2017 Zhihu Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.zhihu.matisse.internal.ui.widget; + +import android.content.Context; +import androidx.viewpager.widget.ViewPager; +import android.util.AttributeSet; +import android.view.View; + +import it.sephiroth.android.library.imagezoom.ImageViewTouch; + +public class PreviewViewPager extends ViewPager { + + public PreviewViewPager(Context context, AttributeSet attrs) { + super(context, attrs); + } + + @Override + protected boolean canScroll(View v, boolean checkV, int dx, int x, int y) { + if (v instanceof ImageViewTouch) { + return ((ImageViewTouch) v).canScroll(dx) || super.canScroll(v, checkV, dx, x, y); + } + return super.canScroll(v, checkV, dx, x, y); + } +} diff --git a/YFDXJ/matisse/src/main/java/com/zhihu/matisse/internal/ui/widget/RoundedRectangleImageView.java b/YFDXJ/matisse/src/main/java/com/zhihu/matisse/internal/ui/widget/RoundedRectangleImageView.java new file mode 100644 index 0000000..b1a5220 --- /dev/null +++ b/YFDXJ/matisse/src/main/java/com/zhihu/matisse/internal/ui/widget/RoundedRectangleImageView.java @@ -0,0 +1,65 @@ +/* + * Copyright 2017 Zhihu Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.zhihu.matisse.internal.ui.widget; + +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.Path; +import android.graphics.RectF; +import androidx.appcompat.widget.AppCompatImageView; +import android.util.AttributeSet; + +public class RoundedRectangleImageView extends AppCompatImageView { + + private float mRadius; // dp + private Path mRoundedRectPath; + private RectF mRectF; + + public RoundedRectangleImageView(Context context) { + super(context); + init(context); + } + + public RoundedRectangleImageView(Context context, AttributeSet attrs) { + super(context, attrs); + init(context); + } + + public RoundedRectangleImageView(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + init(context); + } + + private void init(Context context) { + float density = context.getResources().getDisplayMetrics().density; + mRadius = 2.0f * density; + mRoundedRectPath = new Path(); + mRectF = new RectF(); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + mRectF.set(0.0f, 0.0f, getMeasuredWidth(), getMeasuredHeight()); + mRoundedRectPath.addRoundRect(mRectF, mRadius, mRadius, Path.Direction.CW); + } + + @Override + protected void onDraw(Canvas canvas) { + canvas.clipPath(mRoundedRectPath); + super.onDraw(canvas); + } +} diff --git a/YFDXJ/matisse/src/main/java/com/zhihu/matisse/internal/ui/widget/SquareFrameLayout.java b/YFDXJ/matisse/src/main/java/com/zhihu/matisse/internal/ui/widget/SquareFrameLayout.java new file mode 100644 index 0000000..1f95518 --- /dev/null +++ b/YFDXJ/matisse/src/main/java/com/zhihu/matisse/internal/ui/widget/SquareFrameLayout.java @@ -0,0 +1,36 @@ +/* + * Copyright 2017 Zhihu Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.zhihu.matisse.internal.ui.widget; + +import android.content.Context; +import android.util.AttributeSet; +import android.widget.FrameLayout; + +public class SquareFrameLayout extends FrameLayout { + + public SquareFrameLayout(Context context) { + super(context); + } + + public SquareFrameLayout(Context context, AttributeSet attrs) { + super(context, attrs); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + super.onMeasure(widthMeasureSpec, widthMeasureSpec); + } +} diff --git a/YFDXJ/matisse/src/main/java/com/zhihu/matisse/internal/utils/ExifInterfaceCompat.java b/YFDXJ/matisse/src/main/java/com/zhihu/matisse/internal/utils/ExifInterfaceCompat.java new file mode 100644 index 0000000..b2be0e4 --- /dev/null +++ b/YFDXJ/matisse/src/main/java/com/zhihu/matisse/internal/utils/ExifInterfaceCompat.java @@ -0,0 +1,131 @@ +/* + * Copyright 2017 Zhihu Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.zhihu.matisse.internal.utils; + +import android.media.ExifInterface; +import android.text.TextUtils; +import android.util.Log; + +import java.io.IOException; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.TimeZone; + +/** + * Bug fixture for ExifInterface constructor. + */ +final class ExifInterfaceCompat { + private static final String TAG = ExifInterfaceCompat.class.getSimpleName(); + private static final int EXIF_DEGREE_FALLBACK_VALUE = -1; + + /** + * Do not instantiate this class. + */ + private ExifInterfaceCompat() { + } + + /** + * Creates new instance of {@link ExifInterface}. + * Original constructor won't check filename value, so if null value has been passed, + * the process will be killed because of SIGSEGV. + * Google Play crash report system cannot perceive this crash, so this method will throw + * {@link NullPointerException} when the filename is null. + * + * @param filename a JPEG filename. + * @return {@link ExifInterface} instance. + * @throws IOException something wrong with I/O. + */ + public static ExifInterface newInstance(String filename) throws IOException { + if (filename == null) throw new NullPointerException("filename should not be null"); + return new ExifInterface(filename); + } + + private static Date getExifDateTime(String filepath) { + ExifInterface exif; + try { + // ExifInterface does not check whether file path is null or not, + // so passing null file path argument to its constructor causing SIGSEGV. + // We should avoid such a situation by checking file path string. + exif = newInstance(filepath); + } catch (IOException ex) { + Log.e(TAG, "cannot read exif", ex); + return null; + } + + String date = exif.getAttribute(ExifInterface.TAG_DATETIME); + if (TextUtils.isEmpty(date)) { + return null; + } + try { + SimpleDateFormat formatter = new SimpleDateFormat("yyyy:MM:dd HH:mm:ss"); + formatter.setTimeZone(TimeZone.getTimeZone("UTC")); + return formatter.parse(date); + } catch (ParseException e) { + Log.d(TAG, "failed to parse date taken", e); + } + return null; + } + + /** + * Read exif info and get datetime value of the photo. + * + * @param filepath to get datetime + * @return when a photo taken. + */ + public static long getExifDateTimeInMillis(String filepath) { + Date datetime = getExifDateTime(filepath); + if (datetime == null) { + return -1; + } + return datetime.getTime(); + } + + /** + * Read exif info and get orientation value of the photo. + * + * @param filepath to get exif. + * @return exif orientation value + */ + public static int getExifOrientation(String filepath) { + ExifInterface exif; + try { + // ExifInterface does not check whether file path is null or not, + // so passing null file path argument to its constructor causing SIGSEGV. + // We should avoid such a situation by checking file path string. + exif = newInstance(filepath); + } catch (IOException ex) { + Log.e(TAG, "cannot read exif", ex); + return EXIF_DEGREE_FALLBACK_VALUE; + } + + int orientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, EXIF_DEGREE_FALLBACK_VALUE); + if (orientation == EXIF_DEGREE_FALLBACK_VALUE) { + return 0; + } + // We only recognize a subset of orientation tag values. + switch (orientation) { + case ExifInterface.ORIENTATION_ROTATE_90: + return 90; + case ExifInterface.ORIENTATION_ROTATE_180: + return 180; + case ExifInterface.ORIENTATION_ROTATE_270: + return 270; + default: + return 0; + } + } +} \ No newline at end of file diff --git a/YFDXJ/matisse/src/main/java/com/zhihu/matisse/internal/utils/MediaStoreCompat.java b/YFDXJ/matisse/src/main/java/com/zhihu/matisse/internal/utils/MediaStoreCompat.java new file mode 100644 index 0000000..592c77e --- /dev/null +++ b/YFDXJ/matisse/src/main/java/com/zhihu/matisse/internal/utils/MediaStoreCompat.java @@ -0,0 +1,145 @@ +/* + * Copyright 2017 Zhihu Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.zhihu.matisse.internal.utils; + +import android.app.Activity; +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.net.Uri; +import android.os.Build; +import android.os.Environment; +import android.provider.MediaStore; +import androidx.fragment.app.Fragment; +import androidx.core.content.FileProvider; +import androidx.core.os.EnvironmentCompat; + +import com.zhihu.matisse.internal.entity.CaptureStrategy; + +import java.io.File; +import java.io.IOException; +import java.lang.ref.WeakReference; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.List; +import java.util.Locale; + +public class MediaStoreCompat { + + private final WeakReference mContext; + private final WeakReference mFragment; + private CaptureStrategy mCaptureStrategy; + private Uri mCurrentPhotoUri; + private String mCurrentPhotoPath; + + public MediaStoreCompat(Activity activity) { + mContext = new WeakReference<>(activity); + mFragment = null; + } + + public MediaStoreCompat(Activity activity, Fragment fragment) { + mContext = new WeakReference<>(activity); + mFragment = new WeakReference<>(fragment); + } + + /** + * Checks whether the device has a camera feature or not. + * + * @param context a context to check for camera feature. + * @return true if the device has a camera feature. false otherwise. + */ + public static boolean hasCameraFeature(Context context) { + PackageManager pm = context.getApplicationContext().getPackageManager(); + return pm.hasSystemFeature(PackageManager.FEATURE_CAMERA); + } + + public void setCaptureStrategy(CaptureStrategy strategy) { + mCaptureStrategy = strategy; + } + + public void dispatchCaptureIntent(Context context, int requestCode) { + Intent captureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); + if (captureIntent.resolveActivity(context.getPackageManager()) != null) { + File photoFile = null; + try { + photoFile = createImageFile(); + } catch (IOException e) { + e.printStackTrace(); + } + + if (photoFile != null) { + mCurrentPhotoPath = photoFile.getAbsolutePath(); + mCurrentPhotoUri = FileProvider.getUriForFile(mContext.get(), + mCaptureStrategy.authority, photoFile); + captureIntent.putExtra(MediaStore.EXTRA_OUTPUT, mCurrentPhotoUri); + captureIntent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION); + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { + List resInfoList = context.getPackageManager() + .queryIntentActivities(captureIntent, PackageManager.MATCH_DEFAULT_ONLY); + for (ResolveInfo resolveInfo : resInfoList) { + String packageName = resolveInfo.activityInfo.packageName; + context.grantUriPermission(packageName, mCurrentPhotoUri, + Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_READ_URI_PERMISSION); + } + } + if (mFragment != null) { + mFragment.get().startActivityForResult(captureIntent, requestCode); + } else { + mContext.get().startActivityForResult(captureIntent, requestCode); + } + } + } + } + + @SuppressWarnings("ResultOfMethodCallIgnored") + private File createImageFile() throws IOException { + // Create an image file name + String timeStamp = + new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault()).format(new Date()); + String imageFileName = String.format("JPEG_%s.jpg", timeStamp); + File storageDir; + if (mCaptureStrategy.isPublic) { + storageDir = Environment.getExternalStoragePublicDirectory( + Environment.DIRECTORY_PICTURES); + if (!storageDir.exists()) storageDir.mkdirs(); + } else { + storageDir = mContext.get().getExternalFilesDir(Environment.DIRECTORY_PICTURES); + } + if (mCaptureStrategy.directory != null) { + storageDir = new File(storageDir, mCaptureStrategy.directory); + if (!storageDir.exists()) storageDir.mkdirs(); + } + + // Avoid joining path components manually + File tempFile = new File(storageDir, imageFileName); + + // Handle the situation that user's external storage is not ready + if (!Environment.MEDIA_MOUNTED.equals(EnvironmentCompat.getStorageState(tempFile))) { + return null; + } + + return tempFile; + } + + public Uri getCurrentPhotoUri() { + return mCurrentPhotoUri; + } + + public String getCurrentPhotoPath() { + return mCurrentPhotoPath; + } +} diff --git a/YFDXJ/matisse/src/main/java/com/zhihu/matisse/internal/utils/PathUtils.java b/YFDXJ/matisse/src/main/java/com/zhihu/matisse/internal/utils/PathUtils.java new file mode 100644 index 0000000..de64322 --- /dev/null +++ b/YFDXJ/matisse/src/main/java/com/zhihu/matisse/internal/utils/PathUtils.java @@ -0,0 +1,135 @@ +package com.zhihu.matisse.internal.utils; + +import android.annotation.TargetApi; +import android.content.ContentUris; +import android.content.Context; +import android.database.Cursor; +import android.net.Uri; +import android.os.Build; +import android.os.Environment; +import android.provider.DocumentsContract; +import android.provider.MediaStore; + +/** + * http://stackoverflow.com/a/27271131/4739220 + */ + +public class PathUtils { + /** + * Get a file path from a Uri. This will get the the path for Storage Access + * Framework Documents, as well as the _data field for the MediaStore and + * other file-based ContentProviders. + * + * @param context The context. + * @param uri The Uri to query. + * @author paulburke + */ + @TargetApi(Build.VERSION_CODES.KITKAT) + public static String getPath(final Context context, final Uri uri) { + // DocumentProvider + if (Platform.hasKitKat() && DocumentsContract.isDocumentUri(context, uri)) { + // ExternalStorageProvider + if (isExternalStorageDocument(uri)) { + final String docId = DocumentsContract.getDocumentId(uri); + final String[] split = docId.split(":"); + final String type = split[0]; + + if ("primary".equalsIgnoreCase(type)) { + return Environment.getExternalStorageDirectory() + "/" + split[1]; + } + + // TODO handle non-primary volumes + } else if (isDownloadsDocument(uri)) { // DownloadsProvider + + final String id = DocumentsContract.getDocumentId(uri); + final Uri contentUri = ContentUris.withAppendedId( + Uri.parse("content://downloads/public_downloads"), Long.valueOf(id)); + + return getDataColumn(context, contentUri, null, null); + } else if (isMediaDocument(uri)) { // MediaProvider + final String docId = DocumentsContract.getDocumentId(uri); + final String[] split = docId.split(":"); + final String type = split[0]; + + Uri contentUri = null; + if ("image".equals(type)) { + contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI; + } else if ("video".equals(type)) { + contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI; + } else if ("audio".equals(type)) { + contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI; + } + + final String selection = "_id=?"; + final String[] selectionArgs = new String[]{ + split[1] + }; + + return getDataColumn(context, contentUri, selection, selectionArgs); + } + } else if ("content".equalsIgnoreCase(uri.getScheme())) { // MediaStore (and general) + return getDataColumn(context, uri, null, null); + } else if ("file".equalsIgnoreCase(uri.getScheme())) { // File + return uri.getPath(); + } + + return null; + } + + /** + * Get the value of the data column for this Uri. This is useful for + * MediaStore Uris, and other file-based ContentProviders. + * + * @param context The context. + * @param uri The Uri to query. + * @param selection (Optional) Filter used in the query. + * @param selectionArgs (Optional) Selection arguments used in the query. + * @return The value of the _data column, which is typically a file path. + */ + public static String getDataColumn(Context context, Uri uri, String selection, + String[] selectionArgs) { + + Cursor cursor = null; + final String column = "_data"; + final String[] projection = { + column + }; + + try { + cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs, null); + if (cursor != null && cursor.moveToFirst()) { + final int columnIndex = cursor.getColumnIndexOrThrow(column); + return cursor.getString(columnIndex); + } + } finally { + if (cursor != null) + cursor.close(); + } + return null; + } + + + /** + * @param uri The Uri to check. + * @return Whether the Uri authority is ExternalStorageProvider. + */ + public static boolean isExternalStorageDocument(Uri uri) { + return "com.android.externalstorage.documents".equals(uri.getAuthority()); + } + + /** + * @param uri The Uri to check. + * @return Whether the Uri authority is DownloadsProvider. + */ + public static boolean isDownloadsDocument(Uri uri) { + return "com.android.providers.downloads.documents".equals(uri.getAuthority()); + } + + /** + * @param uri The Uri to check. + * @return Whether the Uri authority is MediaProvider. + */ + public static boolean isMediaDocument(Uri uri) { + return "com.android.providers.media.documents".equals(uri.getAuthority()); + } +} diff --git a/YFDXJ/matisse/src/main/java/com/zhihu/matisse/internal/utils/PhotoMetadataUtils.java b/YFDXJ/matisse/src/main/java/com/zhihu/matisse/internal/utils/PhotoMetadataUtils.java new file mode 100644 index 0000000..59c1c8c --- /dev/null +++ b/YFDXJ/matisse/src/main/java/com/zhihu/matisse/internal/utils/PhotoMetadataUtils.java @@ -0,0 +1,178 @@ +/* + * Copyright (C) 2014 nohana, Inc. + * Copyright 2017 Zhihu Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.zhihu.matisse.internal.utils; + +import android.app.Activity; +import android.content.ContentResolver; +import android.content.Context; +import android.database.Cursor; +import android.graphics.BitmapFactory; +import android.graphics.Point; +import android.media.ExifInterface; +import android.net.Uri; +import android.provider.MediaStore; +import android.util.DisplayMetrics; +import android.util.Log; + +import com.zhihu.matisse.MimeType; +import com.zhihu.matisse.R; +import com.zhihu.matisse.filter.Filter; +import com.zhihu.matisse.internal.entity.Item; +import com.zhihu.matisse.internal.entity.SelectionSpec; +import com.zhihu.matisse.internal.entity.IncapableCause; + +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.text.DecimalFormat; +import java.text.NumberFormat; +import java.util.Locale; + +public final class PhotoMetadataUtils { + private static final String TAG = PhotoMetadataUtils.class.getSimpleName(); + private static final int MAX_WIDTH = 1600; + private static final String SCHEME_CONTENT = "content"; + + private PhotoMetadataUtils() { + throw new AssertionError("oops! the utility class is about to be instantiated..."); + } + + public static int getPixelsCount(ContentResolver resolver, Uri uri) { + Point size = getBitmapBound(resolver, uri); + return size.x * size.y; + } + + public static Point getBitmapSize(Uri uri, Activity activity) { + ContentResolver resolver = activity.getContentResolver(); + Point imageSize = getBitmapBound(resolver, uri); + int w = imageSize.x; + int h = imageSize.y; + if (PhotoMetadataUtils.shouldRotate(resolver, uri)) { + w = imageSize.y; + h = imageSize.x; + } + if (h == 0) return new Point(MAX_WIDTH, MAX_WIDTH); + DisplayMetrics metrics = new DisplayMetrics(); + activity.getWindowManager().getDefaultDisplay().getMetrics(metrics); + float screenWidth = (float) metrics.widthPixels; + float screenHeight = (float) metrics.heightPixels; + float widthScale = screenWidth / w; + float heightScale = screenHeight / h; + if (widthScale > heightScale) { + return new Point((int) (w * widthScale), (int) (h * heightScale)); + } + return new Point((int) (w * widthScale), (int) (h * heightScale)); + } + + public static Point getBitmapBound(ContentResolver resolver, Uri uri) { + InputStream is = null; + try { + BitmapFactory.Options options = new BitmapFactory.Options(); + options.inJustDecodeBounds = true; + is = resolver.openInputStream(uri); + BitmapFactory.decodeStream(is, null, options); + int width = options.outWidth; + int height = options.outHeight; + return new Point(width, height); + } catch (FileNotFoundException e) { + return new Point(0, 0); + } finally { + if (is != null) { + try { + is.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + } + + public static String getPath(ContentResolver resolver, Uri uri) { + if (uri == null) { + return null; + } + + if (SCHEME_CONTENT.equals(uri.getScheme())) { + Cursor cursor = null; + try { + cursor = resolver.query(uri, new String[]{MediaStore.Images.ImageColumns.DATA}, + null, null, null); + if (cursor == null || !cursor.moveToFirst()) { + return null; + } + return cursor.getString(cursor.getColumnIndex(MediaStore.Images.ImageColumns.DATA)); + } finally { + if (cursor != null) { + cursor.close(); + } + } + } + return uri.getPath(); + } + + public static IncapableCause isAcceptable(Context context, Item item) { + if (!isSelectableType(context, item)) { + return new IncapableCause(context.getString(R.string.error_file_type)); + } + + if (SelectionSpec.getInstance().filters != null) { + for (Filter filter : SelectionSpec.getInstance().filters) { + IncapableCause incapableCause = filter.filter(context, item); + if (incapableCause != null) { + return incapableCause; + } + } + } + return null; + } + + private static boolean isSelectableType(Context context, Item item) { + if (context == null) { + return false; + } + + ContentResolver resolver = context.getContentResolver(); + for (MimeType type : SelectionSpec.getInstance().mimeTypeSet) { + if (type.checkType(resolver, item.getContentUri())) { + return true; + } + } + return false; + } + + private static boolean shouldRotate(ContentResolver resolver, Uri uri) { + ExifInterface exif; + try { + exif = ExifInterfaceCompat.newInstance(getPath(resolver, uri)); + } catch (IOException e) { + Log.e(TAG, "could not read exif info of the image: " + uri); + return false; + } + int orientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, -1); + return orientation == ExifInterface.ORIENTATION_ROTATE_90 + || orientation == ExifInterface.ORIENTATION_ROTATE_270; + } + + public static float getSizeInMB(long sizeInBytes) { + DecimalFormat df = (DecimalFormat) NumberFormat.getNumberInstance(Locale.US); + df.applyPattern("0.0"); + String result = df.format((float) sizeInBytes / 1024 / 1024); + Log.e(TAG, "getSizeInMB: " + result); + result = result.replaceAll(",", "."); // in some case , 0.0 will be 0,0 + return Float.valueOf(result); + } +} diff --git a/YFDXJ/matisse/src/main/java/com/zhihu/matisse/internal/utils/Platform.java b/YFDXJ/matisse/src/main/java/com/zhihu/matisse/internal/utils/Platform.java new file mode 100644 index 0000000..df41989 --- /dev/null +++ b/YFDXJ/matisse/src/main/java/com/zhihu/matisse/internal/utils/Platform.java @@ -0,0 +1,16 @@ +package com.zhihu.matisse.internal.utils; + +import android.os.Build; + +/** + * @author JoongWon Baik + */ +public class Platform { + public static boolean hasICS() { + return Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH; + } + + public static boolean hasKitKat() { + return Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT; + } +} diff --git a/YFDXJ/matisse/src/main/java/com/zhihu/matisse/internal/utils/SingleMediaScanner.java b/YFDXJ/matisse/src/main/java/com/zhihu/matisse/internal/utils/SingleMediaScanner.java new file mode 100644 index 0000000..81370df --- /dev/null +++ b/YFDXJ/matisse/src/main/java/com/zhihu/matisse/internal/utils/SingleMediaScanner.java @@ -0,0 +1,44 @@ +package com.zhihu.matisse.internal.utils; + +import android.content.Context; +import android.media.MediaScannerConnection; +import android.net.Uri; + +/** + * @author 工藤 + * @email gougou@16fan.com + * create at 2018年10月23日12:17:59 + * description:媒体扫描 + */ +public class SingleMediaScanner implements MediaScannerConnection.MediaScannerConnectionClient { + + private MediaScannerConnection mMsc; + private String mPath; + private ScanListener mListener; + + public interface ScanListener { + + /** + * scan finish + */ + void onScanFinish(); + } + + public SingleMediaScanner(Context context, String mPath, ScanListener mListener) { + this.mPath = mPath; + this.mListener = mListener; + this.mMsc = new MediaScannerConnection(context, this); + this.mMsc.connect(); + } + + @Override public void onMediaScannerConnected() { + mMsc.scanFile(mPath, null); + } + + @Override public void onScanCompleted(String mPath, Uri mUri) { + mMsc.disconnect(); + if (mListener != null) { + mListener.onScanFinish(); + } + } +} diff --git a/YFDXJ/matisse/src/main/java/com/zhihu/matisse/internal/utils/UIUtils.java b/YFDXJ/matisse/src/main/java/com/zhihu/matisse/internal/utils/UIUtils.java new file mode 100644 index 0000000..129b599 --- /dev/null +++ b/YFDXJ/matisse/src/main/java/com/zhihu/matisse/internal/utils/UIUtils.java @@ -0,0 +1,32 @@ +/* + * Copyright 2017 Zhihu Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.zhihu.matisse.internal.utils; + +import android.content.Context; + +public class UIUtils { + + public static int spanCount(Context context, int gridExpectedSize) { + int screenWidth = context.getResources().getDisplayMetrics().widthPixels; + float expected = (float) screenWidth / (float) gridExpectedSize; + int spanCount = Math.round(expected); + if (spanCount == 0) { + spanCount = 1; + } + return spanCount; + } + +} diff --git a/YFDXJ/matisse/src/main/java/com/zhihu/matisse/listener/OnCheckedListener.java b/YFDXJ/matisse/src/main/java/com/zhihu/matisse/listener/OnCheckedListener.java new file mode 100644 index 0000000..4a0ebae --- /dev/null +++ b/YFDXJ/matisse/src/main/java/com/zhihu/matisse/listener/OnCheckedListener.java @@ -0,0 +1,9 @@ +package com.zhihu.matisse.listener; + + +/** + * when original is enabled , callback immediately when user check or uncheck original. + */ +public interface OnCheckedListener { + void onCheck(boolean isChecked); +} diff --git a/YFDXJ/matisse/src/main/java/com/zhihu/matisse/listener/OnFragmentInteractionListener.java b/YFDXJ/matisse/src/main/java/com/zhihu/matisse/listener/OnFragmentInteractionListener.java new file mode 100644 index 0000000..79f5536 --- /dev/null +++ b/YFDXJ/matisse/src/main/java/com/zhihu/matisse/listener/OnFragmentInteractionListener.java @@ -0,0 +1,11 @@ +package com.zhihu.matisse.listener; + +/** + * PreViewItemFragment 和 BasePreViewActivity 通信的接口 ,为了方便拿到 ImageViewTouch 的点击事件 + */ +public interface OnFragmentInteractionListener { + /** + * ImageViewTouch 被点击了 + */ + void onClick(); +} diff --git a/YFDXJ/matisse/src/main/java/com/zhihu/matisse/listener/OnSelectedListener.java b/YFDXJ/matisse/src/main/java/com/zhihu/matisse/listener/OnSelectedListener.java new file mode 100644 index 0000000..b7d6d22 --- /dev/null +++ b/YFDXJ/matisse/src/main/java/com/zhihu/matisse/listener/OnSelectedListener.java @@ -0,0 +1,30 @@ +/* + * Copyright 2017 Zhihu Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.zhihu.matisse.listener; + +import android.net.Uri; +import androidx.annotation.NonNull; + +import java.util.List; + +public interface OnSelectedListener { + /** + * @param uriList the selected item {@link Uri} list. + * @param pathList the selected item file path list. + */ + void onSelected(@NonNull List uriList, @NonNull List pathList); +} diff --git a/YFDXJ/matisse/src/main/java/com/zhihu/matisse/ui/MatisseActivity.java b/YFDXJ/matisse/src/main/java/com/zhihu/matisse/ui/MatisseActivity.java new file mode 100644 index 0000000..662379a --- /dev/null +++ b/YFDXJ/matisse/src/main/java/com/zhihu/matisse/ui/MatisseActivity.java @@ -0,0 +1,437 @@ +/* + * Copyright 2017 Zhihu Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.zhihu.matisse.ui; + +import android.app.Activity; +import android.content.Intent; +import android.content.res.TypedArray; +import android.database.Cursor; +import android.graphics.PorterDuff; +import android.graphics.drawable.Drawable; +import android.net.Uri; +import android.os.Build; +import android.os.Bundle; +import android.os.Handler; +import android.os.Looper; +import androidx.annotation.Nullable; +import androidx.fragment.app.Fragment; +import androidx.appcompat.app.ActionBar; +import androidx.appcompat.app.AppCompatActivity; +import androidx.appcompat.widget.Toolbar; + +import android.util.Log; +import android.view.MenuItem; +import android.view.View; +import android.widget.AdapterView; +import android.widget.LinearLayout; +import android.widget.TextView; + +import com.zhihu.matisse.R; +import com.zhihu.matisse.internal.entity.Album; +import com.zhihu.matisse.internal.entity.Item; +import com.zhihu.matisse.internal.entity.SelectionSpec; +import com.zhihu.matisse.internal.model.AlbumCollection; +import com.zhihu.matisse.internal.model.SelectedItemCollection; +import com.zhihu.matisse.internal.ui.AlbumPreviewActivity; +import com.zhihu.matisse.internal.ui.BasePreviewActivity; +import com.zhihu.matisse.internal.ui.MediaSelectionFragment; +import com.zhihu.matisse.internal.ui.SelectedPreviewActivity; +import com.zhihu.matisse.internal.ui.adapter.AlbumMediaAdapter; +import com.zhihu.matisse.internal.ui.adapter.AlbumsAdapter; +import com.zhihu.matisse.internal.ui.widget.AlbumsSpinner; +import com.zhihu.matisse.internal.ui.widget.CheckRadioView; +import com.zhihu.matisse.internal.ui.widget.IncapableDialog; +import com.zhihu.matisse.internal.utils.MediaStoreCompat; +import com.zhihu.matisse.internal.utils.PathUtils; +import com.zhihu.matisse.internal.utils.PhotoMetadataUtils; + +import com.zhihu.matisse.internal.utils.SingleMediaScanner; +import java.util.ArrayList; + +/** + * Main Activity to display albums and media content (images/videos) in each album + * and also support media selecting operations. + */ +public class MatisseActivity extends AppCompatActivity implements + AlbumCollection.AlbumCallbacks, AdapterView.OnItemSelectedListener, + MediaSelectionFragment.SelectionProvider, View.OnClickListener, + AlbumMediaAdapter.CheckStateListener, AlbumMediaAdapter.OnMediaClickListener, + AlbumMediaAdapter.OnPhotoCapture { + + public static final String EXTRA_RESULT_SELECTION = "extra_result_selection"; + public static final String EXTRA_RESULT_SELECTION_PATH = "extra_result_selection_path"; + public static final String EXTRA_RESULT_ORIGINAL_ENABLE = "extra_result_original_enable"; + private static final int REQUEST_CODE_PREVIEW = 23; + private static final int REQUEST_CODE_CAPTURE = 24; + public static final String CHECK_STATE = "checkState"; + private final AlbumCollection mAlbumCollection = new AlbumCollection(); + private MediaStoreCompat mMediaStoreCompat; + private SelectedItemCollection mSelectedCollection = new SelectedItemCollection(this); + private SelectionSpec mSpec; + + private AlbumsSpinner mAlbumsSpinner; + private AlbumsAdapter mAlbumsAdapter; + private TextView mButtonPreview; + private TextView mButtonApply; + private View mContainer; + private View mEmptyView; + + private LinearLayout mOriginalLayout; + private CheckRadioView mOriginal; + private boolean mOriginalEnable; + + @Override + protected void onCreate(@Nullable Bundle savedInstanceState) { + // programmatically set theme before super.onCreate() + mSpec = SelectionSpec.getInstance(); + setTheme(mSpec.themeId); + super.onCreate(savedInstanceState); + if (!mSpec.hasInited) { + setResult(RESULT_CANCELED); + finish(); + return; + } + setContentView(R.layout.activity_matisse); + + if (mSpec.needOrientationRestriction()) { + setRequestedOrientation(mSpec.orientation); + } + + if (mSpec.capture) { + mMediaStoreCompat = new MediaStoreCompat(this); + if (mSpec.captureStrategy == null) + throw new RuntimeException("Don't forget to set CaptureStrategy."); + mMediaStoreCompat.setCaptureStrategy(mSpec.captureStrategy); + } + + Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); + setSupportActionBar(toolbar); + ActionBar actionBar = getSupportActionBar(); + actionBar.setDisplayShowTitleEnabled(false); + actionBar.setDisplayHomeAsUpEnabled(true); + Drawable navigationIcon = toolbar.getNavigationIcon(); + TypedArray ta = getTheme().obtainStyledAttributes(new int[]{R.attr.album_element_color}); + int color = ta.getColor(0, 0); + ta.recycle(); + navigationIcon.setColorFilter(color, PorterDuff.Mode.SRC_IN); + + mButtonPreview = (TextView) findViewById(R.id.button_preview); + mButtonApply = (TextView) findViewById(R.id.button_apply); + mButtonPreview.setOnClickListener(this); + mButtonApply.setOnClickListener(this); + mContainer = findViewById(R.id.container); + mEmptyView = findViewById(R.id.empty_view); + mOriginalLayout = findViewById(R.id.originalLayout); + mOriginal = findViewById(R.id.original); + mOriginalLayout.setOnClickListener(this); + + mSelectedCollection.onCreate(savedInstanceState); + if (savedInstanceState != null) { + mOriginalEnable = savedInstanceState.getBoolean(CHECK_STATE); + } + updateBottomToolbar(); + + mAlbumsAdapter = new AlbumsAdapter(this, null, false); + mAlbumsSpinner = new AlbumsSpinner(this); + mAlbumsSpinner.setOnItemSelectedListener(this); + mAlbumsSpinner.setSelectedTextView((TextView) findViewById(R.id.selected_album)); + mAlbumsSpinner.setPopupAnchorView(findViewById(R.id.toolbar)); + mAlbumsSpinner.setAdapter(mAlbumsAdapter); + mAlbumCollection.onCreate(this, this); + mAlbumCollection.onRestoreInstanceState(savedInstanceState); + mAlbumCollection.loadAlbums(); + } + + @Override + protected void onSaveInstanceState(Bundle outState) { + super.onSaveInstanceState(outState); + mSelectedCollection.onSaveInstanceState(outState); + mAlbumCollection.onSaveInstanceState(outState); + outState.putBoolean("checkState", mOriginalEnable); + } + + @Override + protected void onDestroy() { + super.onDestroy(); + mAlbumCollection.onDestroy(); + mSpec.onCheckedListener = null; + mSpec.onSelectedListener = null; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + if (item.getItemId() == android.R.id.home) { + onBackPressed(); + return true; + } + return super.onOptionsItemSelected(item); + } + + @Override + public void onBackPressed() { + setResult(Activity.RESULT_CANCELED); + super.onBackPressed(); + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); + if (resultCode != RESULT_OK) + return; + + if (requestCode == REQUEST_CODE_PREVIEW) { + Bundle resultBundle = data.getBundleExtra(BasePreviewActivity.EXTRA_RESULT_BUNDLE); + ArrayList selected = resultBundle.getParcelableArrayList(SelectedItemCollection.STATE_SELECTION); + mOriginalEnable = data.getBooleanExtra(BasePreviewActivity.EXTRA_RESULT_ORIGINAL_ENABLE, false); + int collectionType = resultBundle.getInt(SelectedItemCollection.STATE_COLLECTION_TYPE, + SelectedItemCollection.COLLECTION_UNDEFINED); + if (data.getBooleanExtra(BasePreviewActivity.EXTRA_RESULT_APPLY, false)) { + Intent result = new Intent(); + ArrayList selectedUris = new ArrayList<>(); + ArrayList selectedPaths = new ArrayList<>(); + if (selected != null) { + for (Item item : selected) { + selectedUris.add(item.getContentUri()); + selectedPaths.add(PathUtils.getPath(this, item.getContentUri())); + } + } + result.putParcelableArrayListExtra(EXTRA_RESULT_SELECTION, selectedUris); + result.putStringArrayListExtra(EXTRA_RESULT_SELECTION_PATH, selectedPaths); + result.putExtra(EXTRA_RESULT_ORIGINAL_ENABLE, mOriginalEnable); + setResult(RESULT_OK, result); + finish(); + } else { + mSelectedCollection.overwrite(selected, collectionType); + Fragment mediaSelectionFragment = getSupportFragmentManager().findFragmentByTag( + MediaSelectionFragment.class.getSimpleName()); + if (mediaSelectionFragment instanceof MediaSelectionFragment) { + ((MediaSelectionFragment) mediaSelectionFragment).refreshMediaGrid(); + } + updateBottomToolbar(); + } + } else if (requestCode == REQUEST_CODE_CAPTURE) { + // Just pass the data back to previous calling Activity. + Uri contentUri = mMediaStoreCompat.getCurrentPhotoUri(); + String path = mMediaStoreCompat.getCurrentPhotoPath(); + ArrayList selected = new ArrayList<>(); + selected.add(contentUri); + ArrayList selectedPath = new ArrayList<>(); + selectedPath.add(path); + Intent result = new Intent(); + result.putParcelableArrayListExtra(EXTRA_RESULT_SELECTION, selected); + result.putStringArrayListExtra(EXTRA_RESULT_SELECTION_PATH, selectedPath); + setResult(RESULT_OK, result); + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) + MatisseActivity.this.revokeUriPermission(contentUri, + Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_READ_URI_PERMISSION); + + new SingleMediaScanner(this.getApplicationContext(), path, new SingleMediaScanner.ScanListener() { + @Override public void onScanFinish() { + Log.i("SingleMediaScanner", "scan finish!"); + } + }); + finish(); + } + } + + private void updateBottomToolbar() { + + int selectedCount = mSelectedCollection.count(); + if (selectedCount == 0) { + mButtonPreview.setEnabled(false); + mButtonApply.setEnabled(false); + mButtonApply.setText(getString(R.string.button_apply_default)); + } else if (selectedCount == 1 && mSpec.singleSelectionModeEnabled()) { + mButtonPreview.setEnabled(true); + mButtonApply.setText(R.string.button_apply_default); + mButtonApply.setEnabled(true); + } else { + mButtonPreview.setEnabled(true); + mButtonApply.setEnabled(true); + mButtonApply.setText(getString(R.string.button_apply, selectedCount)); + } + + + if (mSpec.originalable) { + mOriginalLayout.setVisibility(View.VISIBLE); + updateOriginalState(); + } else { + mOriginalLayout.setVisibility(View.INVISIBLE); + } + + + } + + + private void updateOriginalState() { + mOriginal.setChecked(mOriginalEnable); + if (countOverMaxSize() > 0) { + + if (mOriginalEnable) { + IncapableDialog incapableDialog = IncapableDialog.newInstance("", + getString(R.string.error_over_original_size, mSpec.originalMaxSize)); + incapableDialog.show(getSupportFragmentManager(), + IncapableDialog.class.getName()); + + mOriginal.setChecked(false); + mOriginalEnable = false; + } + } + } + + + private int countOverMaxSize() { + int count = 0; + int selectedCount = mSelectedCollection.count(); + for (int i = 0; i < selectedCount; i++) { + Item item = mSelectedCollection.asList().get(i); + + if (item.isImage()) { + float size = PhotoMetadataUtils.getSizeInMB(item.size); + if (size > mSpec.originalMaxSize) { + count++; + } + } + } + return count; + } + + @Override + public void onClick(View v) { + if (v.getId() == R.id.button_preview) { + Intent intent = new Intent(this, SelectedPreviewActivity.class); + intent.putExtra(BasePreviewActivity.EXTRA_DEFAULT_BUNDLE, mSelectedCollection.getDataWithBundle()); + intent.putExtra(BasePreviewActivity.EXTRA_RESULT_ORIGINAL_ENABLE, mOriginalEnable); + startActivityForResult(intent, REQUEST_CODE_PREVIEW); + } else if (v.getId() == R.id.button_apply) { + Intent result = new Intent(); + ArrayList selectedUris = (ArrayList) mSelectedCollection.asListOfUri(); + result.putParcelableArrayListExtra(EXTRA_RESULT_SELECTION, selectedUris); + ArrayList selectedPaths = (ArrayList) mSelectedCollection.asListOfString(); + result.putStringArrayListExtra(EXTRA_RESULT_SELECTION_PATH, selectedPaths); + result.putExtra(EXTRA_RESULT_ORIGINAL_ENABLE, mOriginalEnable); + setResult(RESULT_OK, result); + finish(); + } else if (v.getId() == R.id.originalLayout) { + int count = countOverMaxSize(); + if (count > 0) { + IncapableDialog incapableDialog = IncapableDialog.newInstance("", + getString(R.string.error_over_original_count, count, mSpec.originalMaxSize)); + incapableDialog.show(getSupportFragmentManager(), + IncapableDialog.class.getName()); + return; + } + + mOriginalEnable = !mOriginalEnable; + mOriginal.setChecked(mOriginalEnable); + + if (mSpec.onCheckedListener != null) { + mSpec.onCheckedListener.onCheck(mOriginalEnable); + } + } + } + + @Override + public void onItemSelected(AdapterView parent, View view, int position, long id) { + mAlbumCollection.setStateCurrentSelection(position); + mAlbumsAdapter.getCursor().moveToPosition(position); + Album album = Album.valueOf(mAlbumsAdapter.getCursor()); + if (album.isAll() && SelectionSpec.getInstance().capture) { + album.addCaptureCount(); + } + onAlbumSelected(album); + } + + @Override + public void onNothingSelected(AdapterView parent) { + + } + + @Override + public void onAlbumLoad(final Cursor cursor) { + mAlbumsAdapter.swapCursor(cursor); + // select default album. + Handler handler = new Handler(Looper.getMainLooper()); + handler.post(new Runnable() { + + @Override + public void run() { + cursor.moveToPosition(mAlbumCollection.getCurrentSelection()); + mAlbumsSpinner.setSelection(MatisseActivity.this, + mAlbumCollection.getCurrentSelection()); + Album album = Album.valueOf(cursor); + if (album.isAll() && SelectionSpec.getInstance().capture) { + album.addCaptureCount(); + } + onAlbumSelected(album); + } + }); + } + + @Override + public void onAlbumReset() { + mAlbumsAdapter.swapCursor(null); + } + + private void onAlbumSelected(Album album) { + if (album.isAll() && album.isEmpty()) { + mContainer.setVisibility(View.GONE); + mEmptyView.setVisibility(View.VISIBLE); + } else { + mContainer.setVisibility(View.VISIBLE); + mEmptyView.setVisibility(View.GONE); + Fragment fragment = MediaSelectionFragment.newInstance(album); + getSupportFragmentManager() + .beginTransaction() + .replace(R.id.container, fragment, MediaSelectionFragment.class.getSimpleName()) + .commitAllowingStateLoss(); + } + } + + @Override + public void onUpdate() { + // notify bottom toolbar that check state changed. + updateBottomToolbar(); + + if (mSpec.onSelectedListener != null) { + mSpec.onSelectedListener.onSelected( + mSelectedCollection.asListOfUri(), mSelectedCollection.asListOfString()); + } + } + + @Override + public void onMediaClick(Album album, Item item, int adapterPosition) { + Intent intent = new Intent(this, AlbumPreviewActivity.class); + intent.putExtra(AlbumPreviewActivity.EXTRA_ALBUM, album); + intent.putExtra(AlbumPreviewActivity.EXTRA_ITEM, item); + intent.putExtra(BasePreviewActivity.EXTRA_DEFAULT_BUNDLE, mSelectedCollection.getDataWithBundle()); + intent.putExtra(BasePreviewActivity.EXTRA_RESULT_ORIGINAL_ENABLE, mOriginalEnable); + startActivityForResult(intent, REQUEST_CODE_PREVIEW); + } + + @Override + public SelectedItemCollection provideSelectedItemCollection() { + return mSelectedCollection; + } + + @Override + public void capture() { + if (mMediaStoreCompat != null) { + mMediaStoreCompat.dispatchCaptureIntent(this, REQUEST_CODE_CAPTURE); + } + } + +} diff --git a/YFDXJ/matisse/src/main/res/color/dracula_bottom_toolbar_apply.xml b/YFDXJ/matisse/src/main/res/color/dracula_bottom_toolbar_apply.xml new file mode 100644 index 0000000..84e13af --- /dev/null +++ b/YFDXJ/matisse/src/main/res/color/dracula_bottom_toolbar_apply.xml @@ -0,0 +1,21 @@ + + + + + + \ No newline at end of file diff --git a/YFDXJ/matisse/src/main/res/color/dracula_bottom_toolbar_preview.xml b/YFDXJ/matisse/src/main/res/color/dracula_bottom_toolbar_preview.xml new file mode 100644 index 0000000..56e04b7 --- /dev/null +++ b/YFDXJ/matisse/src/main/res/color/dracula_bottom_toolbar_preview.xml @@ -0,0 +1,21 @@ + + + + + + \ No newline at end of file diff --git a/YFDXJ/matisse/src/main/res/color/dracula_preview_bottom_toolbar_apply.xml b/YFDXJ/matisse/src/main/res/color/dracula_preview_bottom_toolbar_apply.xml new file mode 100644 index 0000000..9920c27 --- /dev/null +++ b/YFDXJ/matisse/src/main/res/color/dracula_preview_bottom_toolbar_apply.xml @@ -0,0 +1,21 @@ + + + + + + \ No newline at end of file diff --git a/YFDXJ/matisse/src/main/res/color/zhihu_bottom_toolbar_apply.xml b/YFDXJ/matisse/src/main/res/color/zhihu_bottom_toolbar_apply.xml new file mode 100644 index 0000000..d12c968 --- /dev/null +++ b/YFDXJ/matisse/src/main/res/color/zhihu_bottom_toolbar_apply.xml @@ -0,0 +1,21 @@ + + + + + + \ No newline at end of file diff --git a/YFDXJ/matisse/src/main/res/color/zhihu_bottom_toolbar_preview.xml b/YFDXJ/matisse/src/main/res/color/zhihu_bottom_toolbar_preview.xml new file mode 100644 index 0000000..fef8d16 --- /dev/null +++ b/YFDXJ/matisse/src/main/res/color/zhihu_bottom_toolbar_preview.xml @@ -0,0 +1,21 @@ + + + + + + \ No newline at end of file diff --git a/YFDXJ/matisse/src/main/res/color/zhihu_preview_bottom_toolbar_apply.xml b/YFDXJ/matisse/src/main/res/color/zhihu_preview_bottom_toolbar_apply.xml new file mode 100644 index 0000000..0bec454 --- /dev/null +++ b/YFDXJ/matisse/src/main/res/color/zhihu_preview_bottom_toolbar_apply.xml @@ -0,0 +1,21 @@ + + + + + + diff --git a/YFDXJ/matisse/src/main/res/drawable-hdpi/ic_arrow_drop_down_white_24dp.png b/YFDXJ/matisse/src/main/res/drawable-hdpi/ic_arrow_drop_down_white_24dp.png new file mode 100644 index 0000000..4c6076d Binary files /dev/null and b/YFDXJ/matisse/src/main/res/drawable-hdpi/ic_arrow_drop_down_white_24dp.png differ diff --git a/YFDXJ/matisse/src/main/res/drawable-hdpi/ic_check_white_18dp.png b/YFDXJ/matisse/src/main/res/drawable-hdpi/ic_check_white_18dp.png new file mode 100644 index 0000000..08dbac9 Binary files /dev/null and b/YFDXJ/matisse/src/main/res/drawable-hdpi/ic_check_white_18dp.png differ diff --git a/YFDXJ/matisse/src/main/res/drawable-hdpi/ic_empty_dracula.png b/YFDXJ/matisse/src/main/res/drawable-hdpi/ic_empty_dracula.png new file mode 100644 index 0000000..0920df4 Binary files /dev/null and b/YFDXJ/matisse/src/main/res/drawable-hdpi/ic_empty_dracula.png differ diff --git a/YFDXJ/matisse/src/main/res/drawable-hdpi/ic_empty_zhihu.png b/YFDXJ/matisse/src/main/res/drawable-hdpi/ic_empty_zhihu.png new file mode 100644 index 0000000..86ffe1c Binary files /dev/null and b/YFDXJ/matisse/src/main/res/drawable-hdpi/ic_empty_zhihu.png differ diff --git a/YFDXJ/matisse/src/main/res/drawable-hdpi/ic_gif.png b/YFDXJ/matisse/src/main/res/drawable-hdpi/ic_gif.png new file mode 100644 index 0000000..d4085f5 Binary files /dev/null and b/YFDXJ/matisse/src/main/res/drawable-hdpi/ic_gif.png differ diff --git a/YFDXJ/matisse/src/main/res/drawable-hdpi/ic_play_circle_outline_white_48dp.png b/YFDXJ/matisse/src/main/res/drawable-hdpi/ic_play_circle_outline_white_48dp.png new file mode 100644 index 0000000..6e1b578 Binary files /dev/null and b/YFDXJ/matisse/src/main/res/drawable-hdpi/ic_play_circle_outline_white_48dp.png differ diff --git a/YFDXJ/matisse/src/main/res/drawable-hdpi/ic_preview_radio_off.webp b/YFDXJ/matisse/src/main/res/drawable-hdpi/ic_preview_radio_off.webp new file mode 100644 index 0000000..80930b9 Binary files /dev/null and b/YFDXJ/matisse/src/main/res/drawable-hdpi/ic_preview_radio_off.webp differ diff --git a/YFDXJ/matisse/src/main/res/drawable-hdpi/ic_preview_radio_on.webp b/YFDXJ/matisse/src/main/res/drawable-hdpi/ic_preview_radio_on.webp new file mode 100644 index 0000000..89ff580 Binary files /dev/null and b/YFDXJ/matisse/src/main/res/drawable-hdpi/ic_preview_radio_on.webp differ diff --git a/YFDXJ/matisse/src/main/res/drawable-mdpi/ic_arrow_drop_down_white_24dp.png b/YFDXJ/matisse/src/main/res/drawable-mdpi/ic_arrow_drop_down_white_24dp.png new file mode 100644 index 0000000..4046a74 Binary files /dev/null and b/YFDXJ/matisse/src/main/res/drawable-mdpi/ic_arrow_drop_down_white_24dp.png differ diff --git a/YFDXJ/matisse/src/main/res/drawable-mdpi/ic_check_white_18dp.png b/YFDXJ/matisse/src/main/res/drawable-mdpi/ic_check_white_18dp.png new file mode 100644 index 0000000..ad14847 Binary files /dev/null and b/YFDXJ/matisse/src/main/res/drawable-mdpi/ic_check_white_18dp.png differ diff --git a/YFDXJ/matisse/src/main/res/drawable-mdpi/ic_empty_dracula.png b/YFDXJ/matisse/src/main/res/drawable-mdpi/ic_empty_dracula.png new file mode 100644 index 0000000..34937ef Binary files /dev/null and b/YFDXJ/matisse/src/main/res/drawable-mdpi/ic_empty_dracula.png differ diff --git a/YFDXJ/matisse/src/main/res/drawable-mdpi/ic_empty_zhihu.png b/YFDXJ/matisse/src/main/res/drawable-mdpi/ic_empty_zhihu.png new file mode 100644 index 0000000..f5628ee Binary files /dev/null and b/YFDXJ/matisse/src/main/res/drawable-mdpi/ic_empty_zhihu.png differ diff --git a/YFDXJ/matisse/src/main/res/drawable-mdpi/ic_gif.png b/YFDXJ/matisse/src/main/res/drawable-mdpi/ic_gif.png new file mode 100644 index 0000000..aeeb0d4 Binary files /dev/null and b/YFDXJ/matisse/src/main/res/drawable-mdpi/ic_gif.png differ diff --git a/YFDXJ/matisse/src/main/res/drawable-mdpi/ic_play_circle_outline_white_48dp.png b/YFDXJ/matisse/src/main/res/drawable-mdpi/ic_play_circle_outline_white_48dp.png new file mode 100644 index 0000000..615b80d Binary files /dev/null and b/YFDXJ/matisse/src/main/res/drawable-mdpi/ic_play_circle_outline_white_48dp.png differ diff --git a/YFDXJ/matisse/src/main/res/drawable-mdpi/ic_preview_radio_off.webp b/YFDXJ/matisse/src/main/res/drawable-mdpi/ic_preview_radio_off.webp new file mode 100644 index 0000000..1a8f6c1 Binary files /dev/null and b/YFDXJ/matisse/src/main/res/drawable-mdpi/ic_preview_radio_off.webp differ diff --git a/YFDXJ/matisse/src/main/res/drawable-mdpi/ic_preview_radio_on.webp b/YFDXJ/matisse/src/main/res/drawable-mdpi/ic_preview_radio_on.webp new file mode 100644 index 0000000..b336c9c Binary files /dev/null and b/YFDXJ/matisse/src/main/res/drawable-mdpi/ic_preview_radio_on.webp differ diff --git a/YFDXJ/matisse/src/main/res/drawable-xhdpi/ic_arrow_drop_down_white_24dp.png b/YFDXJ/matisse/src/main/res/drawable-xhdpi/ic_arrow_drop_down_white_24dp.png new file mode 100644 index 0000000..da239e4 Binary files /dev/null and b/YFDXJ/matisse/src/main/res/drawable-xhdpi/ic_arrow_drop_down_white_24dp.png differ diff --git a/YFDXJ/matisse/src/main/res/drawable-xhdpi/ic_check_white_18dp.png b/YFDXJ/matisse/src/main/res/drawable-xhdpi/ic_check_white_18dp.png new file mode 100644 index 0000000..729f290 Binary files /dev/null and b/YFDXJ/matisse/src/main/res/drawable-xhdpi/ic_check_white_18dp.png differ diff --git a/YFDXJ/matisse/src/main/res/drawable-xhdpi/ic_empty_dracula.png b/YFDXJ/matisse/src/main/res/drawable-xhdpi/ic_empty_dracula.png new file mode 100644 index 0000000..2cd36f6 Binary files /dev/null and b/YFDXJ/matisse/src/main/res/drawable-xhdpi/ic_empty_dracula.png differ diff --git a/YFDXJ/matisse/src/main/res/drawable-xhdpi/ic_empty_zhihu.png b/YFDXJ/matisse/src/main/res/drawable-xhdpi/ic_empty_zhihu.png new file mode 100644 index 0000000..c223b56 Binary files /dev/null and b/YFDXJ/matisse/src/main/res/drawable-xhdpi/ic_empty_zhihu.png differ diff --git a/YFDXJ/matisse/src/main/res/drawable-xhdpi/ic_gif.png b/YFDXJ/matisse/src/main/res/drawable-xhdpi/ic_gif.png new file mode 100644 index 0000000..ed2a70b Binary files /dev/null and b/YFDXJ/matisse/src/main/res/drawable-xhdpi/ic_gif.png differ diff --git a/YFDXJ/matisse/src/main/res/drawable-xhdpi/ic_play_circle_outline_white_48dp.png b/YFDXJ/matisse/src/main/res/drawable-xhdpi/ic_play_circle_outline_white_48dp.png new file mode 100644 index 0000000..516f643 Binary files /dev/null and b/YFDXJ/matisse/src/main/res/drawable-xhdpi/ic_play_circle_outline_white_48dp.png differ diff --git a/YFDXJ/matisse/src/main/res/drawable-xhdpi/ic_preview_radio_off.webp b/YFDXJ/matisse/src/main/res/drawable-xhdpi/ic_preview_radio_off.webp new file mode 100644 index 0000000..b13bb1b Binary files /dev/null and b/YFDXJ/matisse/src/main/res/drawable-xhdpi/ic_preview_radio_off.webp differ diff --git a/YFDXJ/matisse/src/main/res/drawable-xhdpi/ic_preview_radio_on.webp b/YFDXJ/matisse/src/main/res/drawable-xhdpi/ic_preview_radio_on.webp new file mode 100644 index 0000000..632b2a7 Binary files /dev/null and b/YFDXJ/matisse/src/main/res/drawable-xhdpi/ic_preview_radio_on.webp differ diff --git a/YFDXJ/matisse/src/main/res/drawable-xxhdpi/ic_arrow_drop_down_white_24dp.png b/YFDXJ/matisse/src/main/res/drawable-xxhdpi/ic_arrow_drop_down_white_24dp.png new file mode 100644 index 0000000..c19c19d Binary files /dev/null and b/YFDXJ/matisse/src/main/res/drawable-xxhdpi/ic_arrow_drop_down_white_24dp.png differ diff --git a/YFDXJ/matisse/src/main/res/drawable-xxhdpi/ic_check_white_18dp.png b/YFDXJ/matisse/src/main/res/drawable-xxhdpi/ic_check_white_18dp.png new file mode 100644 index 0000000..9e3f948 Binary files /dev/null and b/YFDXJ/matisse/src/main/res/drawable-xxhdpi/ic_check_white_18dp.png differ diff --git a/YFDXJ/matisse/src/main/res/drawable-xxhdpi/ic_empty_dracula.png b/YFDXJ/matisse/src/main/res/drawable-xxhdpi/ic_empty_dracula.png new file mode 100644 index 0000000..daaedc8 Binary files /dev/null and b/YFDXJ/matisse/src/main/res/drawable-xxhdpi/ic_empty_dracula.png differ diff --git a/YFDXJ/matisse/src/main/res/drawable-xxhdpi/ic_empty_zhihu.png b/YFDXJ/matisse/src/main/res/drawable-xxhdpi/ic_empty_zhihu.png new file mode 100644 index 0000000..c7b14e4 Binary files /dev/null and b/YFDXJ/matisse/src/main/res/drawable-xxhdpi/ic_empty_zhihu.png differ diff --git a/YFDXJ/matisse/src/main/res/drawable-xxhdpi/ic_gif.png b/YFDXJ/matisse/src/main/res/drawable-xxhdpi/ic_gif.png new file mode 100644 index 0000000..2803825 Binary files /dev/null and b/YFDXJ/matisse/src/main/res/drawable-xxhdpi/ic_gif.png differ diff --git a/YFDXJ/matisse/src/main/res/drawable-xxhdpi/ic_play_circle_outline_white_48dp.png b/YFDXJ/matisse/src/main/res/drawable-xxhdpi/ic_play_circle_outline_white_48dp.png new file mode 100644 index 0000000..0311f89 Binary files /dev/null and b/YFDXJ/matisse/src/main/res/drawable-xxhdpi/ic_play_circle_outline_white_48dp.png differ diff --git a/YFDXJ/matisse/src/main/res/drawable-xxxhdpi/ic_arrow_drop_down_white_24dp.png b/YFDXJ/matisse/src/main/res/drawable-xxxhdpi/ic_arrow_drop_down_white_24dp.png new file mode 100644 index 0000000..452e502 Binary files /dev/null and b/YFDXJ/matisse/src/main/res/drawable-xxxhdpi/ic_arrow_drop_down_white_24dp.png differ diff --git a/YFDXJ/matisse/src/main/res/drawable-xxxhdpi/ic_check_white_18dp.png b/YFDXJ/matisse/src/main/res/drawable-xxxhdpi/ic_check_white_18dp.png new file mode 100644 index 0000000..2c2ad77 Binary files /dev/null and b/YFDXJ/matisse/src/main/res/drawable-xxxhdpi/ic_check_white_18dp.png differ diff --git a/YFDXJ/matisse/src/main/res/drawable-xxxhdpi/ic_empty_dracula.png b/YFDXJ/matisse/src/main/res/drawable-xxxhdpi/ic_empty_dracula.png new file mode 100644 index 0000000..4fc34d8 Binary files /dev/null and b/YFDXJ/matisse/src/main/res/drawable-xxxhdpi/ic_empty_dracula.png differ diff --git a/YFDXJ/matisse/src/main/res/drawable-xxxhdpi/ic_empty_zhihu.png b/YFDXJ/matisse/src/main/res/drawable-xxxhdpi/ic_empty_zhihu.png new file mode 100644 index 0000000..9b3c3f1 Binary files /dev/null and b/YFDXJ/matisse/src/main/res/drawable-xxxhdpi/ic_empty_zhihu.png differ diff --git a/YFDXJ/matisse/src/main/res/drawable-xxxhdpi/ic_gif.png b/YFDXJ/matisse/src/main/res/drawable-xxxhdpi/ic_gif.png new file mode 100644 index 0000000..d9d6433 Binary files /dev/null and b/YFDXJ/matisse/src/main/res/drawable-xxxhdpi/ic_gif.png differ diff --git a/YFDXJ/matisse/src/main/res/drawable-xxxhdpi/ic_photo_camera_white_24dp.png b/YFDXJ/matisse/src/main/res/drawable-xxxhdpi/ic_photo_camera_white_24dp.png new file mode 100644 index 0000000..777658e Binary files /dev/null and b/YFDXJ/matisse/src/main/res/drawable-xxxhdpi/ic_photo_camera_white_24dp.png differ diff --git a/YFDXJ/matisse/src/main/res/drawable-xxxhdpi/ic_play_circle_outline_white_48dp.png b/YFDXJ/matisse/src/main/res/drawable-xxxhdpi/ic_play_circle_outline_white_48dp.png new file mode 100644 index 0000000..7a5a168 Binary files /dev/null and b/YFDXJ/matisse/src/main/res/drawable-xxxhdpi/ic_play_circle_outline_white_48dp.png differ diff --git a/YFDXJ/matisse/src/main/res/layout/activity_matisse.xml b/YFDXJ/matisse/src/main/res/layout/activity_matisse.xml new file mode 100644 index 0000000..6bf0c9b --- /dev/null +++ b/YFDXJ/matisse/src/main/res/layout/activity_matisse.xml @@ -0,0 +1,137 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/YFDXJ/matisse/src/main/res/layout/activity_media_preview.xml b/YFDXJ/matisse/src/main/res/layout/activity_media_preview.xml new file mode 100644 index 0000000..e9fc349 --- /dev/null +++ b/YFDXJ/matisse/src/main/res/layout/activity_media_preview.xml @@ -0,0 +1,130 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/YFDXJ/matisse/src/main/res/layout/album_list_item.xml b/YFDXJ/matisse/src/main/res/layout/album_list_item.xml new file mode 100644 index 0000000..1fc8d2f --- /dev/null +++ b/YFDXJ/matisse/src/main/res/layout/album_list_item.xml @@ -0,0 +1,57 @@ + + + + + + + + + + \ No newline at end of file diff --git a/YFDXJ/matisse/src/main/res/layout/fragment_media_selection.xml b/YFDXJ/matisse/src/main/res/layout/fragment_media_selection.xml new file mode 100644 index 0000000..79393ee --- /dev/null +++ b/YFDXJ/matisse/src/main/res/layout/fragment_media_selection.xml @@ -0,0 +1,31 @@ + + + + + + + diff --git a/YFDXJ/matisse/src/main/res/layout/fragment_preview_item.xml b/YFDXJ/matisse/src/main/res/layout/fragment_preview_item.xml new file mode 100644 index 0000000..2cfc58d --- /dev/null +++ b/YFDXJ/matisse/src/main/res/layout/fragment_preview_item.xml @@ -0,0 +1,32 @@ + + + + + + + + diff --git a/YFDXJ/matisse/src/main/res/layout/media_grid_content.xml b/YFDXJ/matisse/src/main/res/layout/media_grid_content.xml new file mode 100644 index 0000000..711557c --- /dev/null +++ b/YFDXJ/matisse/src/main/res/layout/media_grid_content.xml @@ -0,0 +1,50 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/YFDXJ/matisse/src/main/res/layout/media_grid_item.xml b/YFDXJ/matisse/src/main/res/layout/media_grid_item.xml new file mode 100644 index 0000000..392e38b --- /dev/null +++ b/YFDXJ/matisse/src/main/res/layout/media_grid_item.xml @@ -0,0 +1,21 @@ + + + \ No newline at end of file diff --git a/YFDXJ/matisse/src/main/res/layout/photo_capture_item.xml b/YFDXJ/matisse/src/main/res/layout/photo_capture_item.xml new file mode 100644 index 0000000..9c30942 --- /dev/null +++ b/YFDXJ/matisse/src/main/res/layout/photo_capture_item.xml @@ -0,0 +1,36 @@ + + + + + + + \ No newline at end of file diff --git a/YFDXJ/matisse/src/main/res/values-ar/strings.xml b/YFDXJ/matisse/src/main/res/values-ar/strings.xml new file mode 100644 index 0000000..784cc64 --- /dev/null +++ b/YFDXJ/matisse/src/main/res/values-ar/strings.xml @@ -0,0 +1,20 @@ + + + جميع وسائل الإعلام + + معاينة + تطبيق + تطبيق (%1$d) + رجوع + كاميرا + لا وسائل الإعلام حتى الآن + تم + + لقد وصلت إلى الحد الأقصى للاختيار + يمكنك تحديد ما يصل إلى %1$d من ملفات الوسائط فقط + تحت الجودة + فوق الجودة + نوع ملف غير مدعوم + لا يمكن تحديد الصور ومقاطع الفيديو في نفس الوقت + لم يتم العثور على التطبيق دعم معاينة الفيديو + diff --git a/YFDXJ/matisse/src/main/res/values-ca/strings.xml b/YFDXJ/matisse/src/main/res/values-ca/strings.xml new file mode 100644 index 0000000..0611d09 --- /dev/null +++ b/YFDXJ/matisse/src/main/res/values-ca/strings.xml @@ -0,0 +1,35 @@ + + + + Tots + + Previsualitza + Selecciona + Selecciona(%1$d) + Enrere + Càmera + Cap arxiu seleccionat + OK + + Has sobrepassat el màxim d\'arxius seleccionables + Només pots seleccionar fins a %1$d arxius multimedia + Baixa qualitat + Excés qualitat + Tipus d\'arxiu no permés + No es poden seleccionar imatges i vídeos al mateix temps + No s\'ha trobat cap app que suporti la previsualització de vídeo + diff --git a/YFDXJ/matisse/src/main/res/values-de/strings.xml b/YFDXJ/matisse/src/main/res/values-de/strings.xml new file mode 100644 index 0000000..b92112b --- /dev/null +++ b/YFDXJ/matisse/src/main/res/values-de/strings.xml @@ -0,0 +1,32 @@ + + + + Alle Medien + + Vorschau + Auswählen + Auswählen(%1$d) + Zurück + Kamera + Keine Medien gewählt + OK + + Sie haben die maximale Anzahl an Medien ausgewählt. + Sie können nur %1$d Medien auswählen + Unter Qualität + Über Qualität + Dateityp nicht unterstützt + Sie können Bilder und Videos nicht gleichzeitig auswählen + Keine App zur Videovorschau gefunden + diff --git a/YFDXJ/matisse/src/main/res/values-es/strings.xml b/YFDXJ/matisse/src/main/res/values-es/strings.xml new file mode 100644 index 0000000..5339a3b --- /dev/null +++ b/YFDXJ/matisse/src/main/res/values-es/strings.xml @@ -0,0 +1,35 @@ + + + + Todos + + Previsualizar + Seleccionar + Seleccionar(%1$d) + Atrás + Cámara + Ningún archivo seleccionado + OK + + Has alcanzado el máximo de archivos seleccionables + Sólo puede seleccionar hasta %1$d archivos multimedia + Baja calidad + Exceso calidad + Tipo de fichero no soportado + No se puede seleccionar imágenes y vídeos al mismo tiempo + No se ha encontrado ninguna app que soporte la previsualización de vídeo + diff --git a/YFDXJ/matisse/src/main/res/values-it/strings.xml b/YFDXJ/matisse/src/main/res/values-it/strings.xml new file mode 100644 index 0000000..fbf5082 --- /dev/null +++ b/YFDXJ/matisse/src/main/res/values-it/strings.xml @@ -0,0 +1,35 @@ + + + + Tutti i media + + Anteprima + Conferma + Conferma(%1$d) + Indietro + Camera + Nessun media disponibile + OK + + Hai selezionato il numero massimo di elementi + Puoi selezionare fino a %1$d elementi + Sotto qualità + Sopra qualità + Tipo di file non supportato + Non è possibile selezionare contemporaneamente immagini e video + Non è stata trovata alcuna app che supporti l\'anteprima video + diff --git a/YFDXJ/matisse/src/main/res/values-ko/strings.xml b/YFDXJ/matisse/src/main/res/values-ko/strings.xml new file mode 100644 index 0000000..7612d66 --- /dev/null +++ b/YFDXJ/matisse/src/main/res/values-ko/strings.xml @@ -0,0 +1,18 @@ + + + 전체보기 + 미리보기 + 카메라 + 비디오 미리보기를 지원하는 앱을 찾을 수 없습니다. + 이미지와 비디오는 동시에 선택 할 수 없습니다. + 적용 + 적용(%1$d) + 뒤로 + 미디어 파일이 없습니다. + 확인 + 더이상 선택할 수 없습니다. + 최대 %1$d까지 선택 가능합니다. + 화질이 너무 낮습니다. + 화질이 너무 높습니다. + 지원되지 않는 파일 유형입니다. + \ No newline at end of file diff --git a/YFDXJ/matisse/src/main/res/values-pl/strings.xml b/YFDXJ/matisse/src/main/res/values-pl/strings.xml new file mode 100644 index 0000000..739aead --- /dev/null +++ b/YFDXJ/matisse/src/main/res/values-pl/strings.xml @@ -0,0 +1,38 @@ + + + + Wszystkie Media + + Preview + Zatwierdź + Zatwierdź(%1$d) + Wstecz + Aparat + Brak mediów + OK + + Osiągnąłeś limit wybieralnych elementów + Możesz wybrać do %1$d plików + Poniżej jakości + Powyżej jakości + Niewspierany typ pliku + Nie można wybierać obrazów i filmów w tym samym czasie + Nie znaleziono aplikacji wspierającej podgląd wideo + Oryginał + Zatwierdź + Zatwierdź(%1$d) + diff --git a/YFDXJ/matisse/src/main/res/values-pt-rBR/strings.xml b/YFDXJ/matisse/src/main/res/values-pt-rBR/strings.xml new file mode 100644 index 0000000..75447a0 --- /dev/null +++ b/YFDXJ/matisse/src/main/res/values-pt-rBR/strings.xml @@ -0,0 +1,35 @@ + + + + Todas as mídias + + Pré-visualizaçao + Aplicar + Aplicar(%1$d) + Voltar + Câmera + Nenhuma mídia disponível + OK + + Você atingiu o máximo de itens possíveis para seleção + Você só pode selecionar até %1$d arquivos de mídia + Abaixo da qualidade + Acima da qualidade + Tipo de arquivo não suportado + Não é possível selecionar arquivos de imagem e vídeo simultaneamente + Nenhum player de vídeo disponível para reprodução + diff --git a/YFDXJ/matisse/src/main/res/values-ru/strings.xml b/YFDXJ/matisse/src/main/res/values-ru/strings.xml new file mode 100644 index 0000000..2be64f2 --- /dev/null +++ b/YFDXJ/matisse/src/main/res/values-ru/strings.xml @@ -0,0 +1,43 @@ + + + + Все + + Предпросмотр + Применить + Применить(%1$d) + Назад + Камера + Пусто + OK + + Вы выбрали максимальное количество файлов + + Можно выбрать не более %1$d файла + Можно выбрать не более %1$d файлов + Можно выбрать не более %1$d файлов + Можно выбрать не более %1$d файлов + + Слишком низкое качество + Слишком высокое качество + Неподдерживаемый тип файла + Невозможно выбрать изображения и видео одновременно + Приложение для предпросмотра видео не найдено + Оригинал + Применить(%1$d) + Применить + diff --git a/YFDXJ/matisse/src/main/res/values-tr-rTR/strings.xml b/YFDXJ/matisse/src/main/res/values-tr-rTR/strings.xml new file mode 100644 index 0000000..671e3fb --- /dev/null +++ b/YFDXJ/matisse/src/main/res/values-tr-rTR/strings.xml @@ -0,0 +1,35 @@ + + + + Tüm Medya + + Ön İzleme + Uygula + Uygula(%1$d) + Geri + Kamera + Henüz medya yok + TAMAM + + Maksimum seçilebilir değere ulaştınız + Sadece %1$d medya dosyasını seçebilirsiniz + Düşük kalite + Yüksek kalite + Desteklenmeyen dosya tipi + Görüntüleri ve videoları aynı anda seçemezsiniz + Video önizlemesini destekleyen hiçbir uygulama bulunamadı + \ No newline at end of file diff --git a/YFDXJ/matisse/src/main/res/values-uk/strings.xml b/YFDXJ/matisse/src/main/res/values-uk/strings.xml new file mode 100644 index 0000000..67ba0ae --- /dev/null +++ b/YFDXJ/matisse/src/main/res/values-uk/strings.xml @@ -0,0 +1,34 @@ + + + + Всі + + Перегляд + Застосувати + Застосувати(%1$d) + Назад + Камера + Пусто + OK + + Вы обрали максимальну кількість файлів + Занадто низька якість + Занадто висока якість + Непідтримуваний тип файла + Неможливо обрати зображення і відео одночасно + Застосунок для перегляду відео не знайдений + diff --git a/YFDXJ/matisse/src/main/res/values-zh-rTW/strings.xml b/YFDXJ/matisse/src/main/res/values-zh-rTW/strings.xml new file mode 100644 index 0000000..fc77408 --- /dev/null +++ b/YFDXJ/matisse/src/main/res/values-zh-rTW/strings.xml @@ -0,0 +1,38 @@ + + + + 全部 + + 預覽 + 使用 + 使用(%1$d) + 返回 + 拍一張 + 還沒有圖片或影片 + 我知道了 + + 您已經達到最大選擇數量 + 最多只能選擇 %1$d 個文件 + 圖片質量太低 + 圖片質量太高 + 不支援的文件類型 + 不能同時選擇圖片和影片 + 沒有支持影片預覽的應用程式 + 原圖 + 确定 + 确定(%1$d) + \ No newline at end of file diff --git a/YFDXJ/matisse/src/main/res/values-zh/strings.xml b/YFDXJ/matisse/src/main/res/values-zh/strings.xml new file mode 100644 index 0000000..853ea42 --- /dev/null +++ b/YFDXJ/matisse/src/main/res/values-zh/strings.xml @@ -0,0 +1,40 @@ + + + + 全部 + + 预览 + 使用 + 使用(%1$d) + 返回 + 拍一张 + 还没有图片或视频 + 我知道了 + + 您已经达到最大选择数量 + 最多只能选择 %1$d 个文件 + 图片质量太低 + 图片质量太高 + 不支持的文件类型 + 不能同时选择图片和视频 + 没有支持视频预览的应用 + "该照片大于 %1$d M,无法上传将取消勾选原图" + "有 %1$d 张照片大于 %2$d M\n无法上传,将取消勾选原图" + 原图 + 确定 + 确定(%1$d) + \ No newline at end of file diff --git a/YFDXJ/matisse/src/main/res/values/attrs.xml b/YFDXJ/matisse/src/main/res/values/attrs.xml new file mode 100644 index 0000000..852a3e3 --- /dev/null +++ b/YFDXJ/matisse/src/main/res/values/attrs.xml @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/YFDXJ/matisse/src/main/res/values/colors.xml b/YFDXJ/matisse/src/main/res/values/colors.xml new file mode 100644 index 0000000..24ec1f9 --- /dev/null +++ b/YFDXJ/matisse/src/main/res/values/colors.xml @@ -0,0 +1,22 @@ + + + + + #CC000000 + #61FFFFFF + + \ No newline at end of file diff --git a/YFDXJ/matisse/src/main/res/values/colors_dracula.xml b/YFDXJ/matisse/src/main/res/values/colors_dracula.xml new file mode 100644 index 0000000..e8bb64f --- /dev/null +++ b/YFDXJ/matisse/src/main/res/values/colors_dracula.xml @@ -0,0 +1,43 @@ + + + + #263237 + #1D282C + + #34474E + #DEFFFFFF + #89FFFFFF + #455A64 + #4DFFFFFF + + #37474F + #263237 + #FFFFFF + #FFFFFF + + #232E32 + #34474E + + #DEFFFFFF + #4DFFFFFF + #03A9F4 + #4D03A9F4 + + #FFFFFF + #03A9F4 + #4D03A9F4 + \ No newline at end of file diff --git a/YFDXJ/matisse/src/main/res/values/colors_zhihu.xml b/YFDXJ/matisse/src/main/res/values/colors_zhihu.xml new file mode 100644 index 0000000..4cf43fa --- /dev/null +++ b/YFDXJ/matisse/src/main/res/values/colors_zhihu.xml @@ -0,0 +1,45 @@ + + + + #1E8AE8 + #176EB9 + + #FFFFFF + #DE000000 + #999999 + #EAEEF4 + #4D000000 + + #EAEEF4 + #1E8AE8 + #FFFFFF + #424242 + + #FFFFFF + #FFFFFF + + #DE000000 + #4D000000 + #0077D9 + #4D0077D9 + + #FFFFFF + #0077D9 + #4D0077D9 + + #808080 + \ No newline at end of file diff --git a/YFDXJ/matisse/src/main/res/values/dimens.xml b/YFDXJ/matisse/src/main/res/values/dimens.xml new file mode 100644 index 0000000..f32d77a --- /dev/null +++ b/YFDXJ/matisse/src/main/res/values/dimens.xml @@ -0,0 +1,22 @@ + + + + 48dp + 4dp + + 72dp + \ No newline at end of file diff --git a/YFDXJ/matisse/src/main/res/values/strings.xml b/YFDXJ/matisse/src/main/res/values/strings.xml new file mode 100644 index 0000000..819a12a --- /dev/null +++ b/YFDXJ/matisse/src/main/res/values/strings.xml @@ -0,0 +1,42 @@ + + + + All Media + + Preview + Apply + Apply(%1$d) + Back + Camera + No media yet + OK + + You have reached max selectable + You can only select up to %1$d media files + Under quality + Over quality + Unsupported file type + Can\'t select images and videos at the same time + No App found supporting video preview + Can\'t select the images larger than %1$d MB + %1$d images over %2$d MB. Original will be unchecked + Original + Sure + Sure(%1$d) + diff --git a/YFDXJ/matisse/src/main/res/values/styles.xml b/YFDXJ/matisse/src/main/res/values/styles.xml new file mode 100644 index 0000000..c1f9441 --- /dev/null +++ b/YFDXJ/matisse/src/main/res/values/styles.xml @@ -0,0 +1,86 @@ + + + + + //====================================== Theme Zhihu =========================================== + + + + + + + + //===================================== Theme Dracula ========================================== + + + + + + + + \ No newline at end of file diff --git a/YFDXJ/nohttp/build.gradle b/YFDXJ/nohttp/build.gradle new file mode 100644 index 0000000..f1baad0 --- /dev/null +++ b/YFDXJ/nohttp/build.gradle @@ -0,0 +1,11 @@ +apply plugin: 'com.android.library' + +android { + compileSdk 34 + defaultConfig { + minSdk 24 + targetSdk 34 + testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" + } + namespace 'com.yolanda.nohttp' +} diff --git a/YFDXJ/nohttp/src/main/AndroidManifest.xml b/YFDXJ/nohttp/src/main/AndroidManifest.xml new file mode 100644 index 0000000..6b3ad56 --- /dev/null +++ b/YFDXJ/nohttp/src/main/AndroidManifest.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/BasicBinary.java b/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/BasicBinary.java new file mode 100644 index 0000000..3689a8b --- /dev/null +++ b/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/BasicBinary.java @@ -0,0 +1,279 @@ +/* + * Copyright 2015 Yan Zhenjie + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yolanda.nohttp; + +import android.text.TextUtils; + +import com.yolanda.nohttp.able.Finishable; +import com.yolanda.nohttp.able.Startable; +import com.yolanda.nohttp.tools.IOUtils; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.URLConnection; + +/** + *

+ * A basic implementation of Binary. + * All the methods are called in Son thread. + *

+ * Created in Oct 17, 2015 12:40:54 PM. + * + * @author Yan Zhenjie. + */ +public abstract class BasicBinary implements Binary, Startable, Finishable { + + private boolean isStarted = false; + + private boolean isCancel = false; + + private boolean isFinish = false; + + private int what; + + private OnUploadListener mUploadListener; + + private String fileName; + + private String mimeType; + + public BasicBinary(String fileName, String mimeType) { + this.fileName = fileName; + this.mimeType = mimeType; + } + + /** + * To monitor file upload progress. + * + * @param what in {@link OnUploadListener} will return to you. + * @param mProgressHandler {@link OnUploadListener}. + */ + public void setUploadListener(int what, OnUploadListener mProgressHandler) { + this.what = what; + this.mUploadListener = mProgressHandler; + } + + @Override + public final long getLength() { + if (!isCanceled()) + return getBinaryLength(); + return 0; + } + + public abstract long getBinaryLength(); + + protected abstract InputStream getInputStream() throws IOException; + + @Override + public void onWriteBinary(OutputStream outputStream) { + if (!isCanceled()) { + InputStream inputStream = null; + try { + inputStream = IOUtils.toBufferedInputStream(getInputStream()); + start(); + postStart(); + + int oldProgress = 0; + long totalLength = getLength(); + int len; + + byte[] buffer = new byte[4096]; + + long hasUpCount = 0; + + while (!isCanceled() && (len = inputStream.read(buffer)) != -1) { + outputStream.write(buffer, 0, len); + if (totalLength != 0 && mUploadListener != null) { + hasUpCount += len; + int progress = (int) (hasUpCount * 100 / totalLength); + if ((0 == progress % 3 || 0 == progress % 5 || 0 == progress % 7) && oldProgress != progress) { + oldProgress = progress; + postProgress(oldProgress); + } + } + } + } catch (IOException e) { + Logger.e(e); + postError(e); + } finally { + IOUtils.closeQuietly(inputStream); + postFinish(); + } + } + finish(); + } + + @Override + public String getFileName() { + if (TextUtils.isEmpty(fileName)) + fileName = Long.toString(System.currentTimeMillis()); + return fileName; + } + + @Override + public String getMimeType() { + String fileName = getFileName(); + if (TextUtils.isEmpty(mimeType) && !TextUtils.isEmpty(fileName)) + mimeType = URLConnection.guessContentTypeFromName(getFileName()); + if (TextUtils.isEmpty(mimeType)) + mimeType = Headers.HEAD_VALUE_ACCEPT_APPLICATION_OCTET_STREAM; + return mimeType; + } + + /** + * Inform the task start. + */ + protected void postStart() { + UploadPoster start = new UploadPoster(what, mUploadListener); + start.start(); + PosterHandler.getInstance().post(start); + } + + /** + * The update task schedule. + * + * @param progress progress. + */ + protected void postProgress(int progress) { + UploadPoster progressPoster = new UploadPoster(what, mUploadListener); + progressPoster.progress(progress); + PosterHandler.getInstance().post(progressPoster); + } + + /** + * Inform the task cancel. + */ + protected void postCancel() { + UploadPoster cancelPoster = new UploadPoster(what, mUploadListener); + cancelPoster.cancel(); + PosterHandler.getInstance().post(cancelPoster); + } + + /** + * Error notification tasks. + * + * @param e exception. + */ + protected void postError(Exception e) { + UploadPoster error = new UploadPoster(what, mUploadListener); + error.error(e); + PosterHandler.getInstance().post(error); + } + + /** + * Inform the task finish. + */ + protected void postFinish() { + UploadPoster finish = new UploadPoster(what, mUploadListener); + finish.finish(); + PosterHandler.getInstance().post(finish); + } + + @Override + public void start() { + isStarted = true; + } + + @Override + public boolean isStarted() { + return isStarted; + } + + @Override + public void cancel() { + if (!isCancel) { + this.isCancel = true; + postCancel(); + } + } + + @Override + public boolean isCanceled() { + return isCancel; + } + + @Override + public void finish() { + isFinish = true; + } + + @Override + public boolean isFinished() { + return isFinish; + } + + private class UploadPoster implements Runnable { + + private final int what; + private final OnUploadListener mOnUploadListener; + + private int command; + + public static final int ON_START = 0; + public static final int ON_CANCEL = 1; + public static final int ON_PROGRESS = 2; + public static final int ON_FINISH = 3; + public static final int ON_ERROR = 4; + + private int progress; + private Exception exception; + + public UploadPoster(int what, OnUploadListener onUploadListener) { + this.what = what; + this.mOnUploadListener = onUploadListener; + } + + public void start() { + this.command = ON_START; + } + + public void cancel() { + this.command = ON_CANCEL; + } + + public void progress(int progress) { + this.command = ON_PROGRESS; + this.progress = progress; + } + + public void finish() { + this.command = ON_FINISH; + } + + public void error(Exception exception) { + this.command = ON_ERROR; + this.exception = exception; + } + + @Override + public void run() { + if (mOnUploadListener != null) { + if (command == ON_START) + mOnUploadListener.onStart(what); + else if (command == ON_FINISH) + mOnUploadListener.onFinish(what); + else if (command == ON_PROGRESS) + mOnUploadListener.onProgress(what, progress); + else if (command == ON_CANCEL) + mOnUploadListener.onCancel(what); + else if (command == ON_ERROR) + mOnUploadListener.onError(what, exception); + } + } + + } +} diff --git a/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/BasicConnection.java b/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/BasicConnection.java new file mode 100644 index 0000000..85d7701 --- /dev/null +++ b/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/BasicConnection.java @@ -0,0 +1,440 @@ +/* + * Copyright 2015 Yan Zhenjie + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yolanda.nohttp; + +import android.os.Build; +import android.text.TextUtils; + +import com.yolanda.nohttp.error.NetworkError; +import com.yolanda.nohttp.error.TimeoutError; +import com.yolanda.nohttp.error.URLError; +import com.yolanda.nohttp.error.UnKnownHostError; +import com.yolanda.nohttp.rest.ProtocolResult; +import com.yolanda.nohttp.rest.Request; +import com.yolanda.nohttp.rest.StringRequest; +import com.yolanda.nohttp.tools.AndroidVersion; +import com.yolanda.nohttp.tools.HeaderUtil; +import com.yolanda.nohttp.tools.IOUtils; +import com.yolanda.nohttp.tools.NetUtil; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.net.HttpURLConnection; +import java.net.MalformedURLException; +import java.net.ProtocolException; +import java.net.Proxy; +import java.net.SocketTimeoutException; +import java.net.URI; +import java.net.URL; +import java.net.UnknownHostException; +import java.util.List; +import java.util.Map; +import java.util.zip.GZIPInputStream; + +import javax.net.ssl.HostnameVerifier; +import javax.net.ssl.HttpsURLConnection; +import javax.net.ssl.SSLSocketFactory; + +/** + *

+ * Package good Http implementation class, establish connection, read and write data. + *

+ * Created in Aug 4, 2015 10:12:38 AM. + * + * @author Yan Zhenjie. + */ +public class BasicConnection { + + /** + * Send the request, send only head, parameters, such as file information. + * + * @param request {@link IBasicRequest}. + * @return {@link ProtocolResult}. + */ + protected Connection getConnection(IBasicRequest request) { + Logger.d("--------------Request start--------------"); + + Headers responseHeaders = new HttpHeaders(); + InputStream inputStream = null; + Exception exception = null; + + HttpURLConnection urlConnection = null; + String url = request.url(); + try { + if (!NetUtil.isNetworkAvailable()) + throw new NetworkError("The network is not available, please check the network. The requested url is: " + url); + + // MalformedURLException, IOException, ProtocolException, UnknownHostException, SocketTimeoutException + urlConnection = createConnectionAndWriteData(request); + Logger.d("-------Response start-------"); + int responseCode = urlConnection.getResponseCode(); + responseHeaders = parseResponseHeaders(new URI(request.url()), responseCode, urlConnection.getResponseMessage(), urlConnection.getHeaderFields()); + + // handle body + if (responseCode == 301 || responseCode == 302 || responseCode == 303 || responseCode == 307) { + Connection redirectConnection = handleRedirect(request, responseHeaders); + responseHeaders = redirectConnection.responseHeaders(); + inputStream = redirectConnection.serverStream(); + exception = redirectConnection.exception(); + } else if (hasResponseBody(request.getRequestMethod(), responseCode)) { + inputStream = getServerStream(responseCode, responseHeaders.getContentEncoding(), urlConnection); + } + Logger.d("-------Response end-------"); + } catch (MalformedURLException e) { + exception = new URLError("The url is malformed: " + url + "."); + } catch (UnknownHostException e) { + exception = new UnKnownHostError("Hostname can not be resolved: " + url + "."); + } catch (SocketTimeoutException e) { + exception = new TimeoutError("Request time out: " + url + "."); + } catch (Exception e) { + exception = e; + } finally { + if (exception != null) + Logger.e(exception); + } + Logger.d("--------------Request finish--------------"); + return new Connection(urlConnection, responseHeaders, inputStream, exception); + } + + /** + * Handle retries, and complete the request network here. + * + * @param request {@link IBasicRequest}. + * @return {@link ProtocolResult}. + * @throws Exception {@link #createHttpURLConnection(IBasicRequest)}. + */ + private HttpURLConnection createConnectionAndWriteData(IBasicRequest request) throws Exception { + HttpURLConnection connection = null; + Exception exception = null; + int retryCount = request.getRetryCount() + 1; + boolean failed = true; + for (; failed && retryCount > 0; retryCount--) { + try { + connection = createHttpURLConnection(request); + exception = null; + failed = false; + } catch (Exception e) { + exception = e; + } + } + if (failed) { + throw exception; + } else if (request.getRequestMethod().allowRequestBody()) { + writeRequestBody(request, connection.getOutputStream()); + } + return connection; + } + + /** + * The connection is established, including the head and send the request body. + * + * @param request {@link IBasicRequest}. + * @return {@link HttpURLConnection} Have been established and the server connection, and send the complete data, you can directly determine the response code and read the data. + * @throws Exception can happen when the connection is established and send data. + */ + private HttpURLConnection createHttpURLConnection(IBasicRequest request) throws Exception { + // 1.Pre operation notice + request.onPreExecute(); + + // 2.Build URL + String urlStr = request.url(); + Logger.i("Request address: " + urlStr); + URL url = new URL(urlStr); + HttpURLConnection connection; + Proxy proxy = request.getProxy(); + if (proxy == null) + connection = (HttpURLConnection) url.openConnection(); + else + connection = (HttpURLConnection) url.openConnection(proxy); + + connection.setConnectTimeout(request.getConnectTimeout()); + connection.setReadTimeout(request.getReadTimeout()); + connection.setInstanceFollowRedirects(false); + + if (connection instanceof HttpsURLConnection) { + SSLSocketFactory sslSocketFactory = request.getSSLSocketFactory(); + if (sslSocketFactory != null) + ((HttpsURLConnection) connection).setSSLSocketFactory(sslSocketFactory); + HostnameVerifier hostnameVerifier = request.getHostnameVerifier(); + if (hostnameVerifier != null) + ((HttpsURLConnection) connection).setHostnameVerifier(hostnameVerifier); + } + + // 3. Base attribute + RequestMethod requestMethod = request.getRequestMethod(); + String requestMethodStr = requestMethod.toString(); + Logger.i("Request method: " + requestMethodStr); + // Fix delete patch error. + try { + connection.setRequestMethod(requestMethodStr); + } catch (ProtocolException protocol) { + try { + Field methodField = connection.getClass().getDeclaredField("method"); + methodField.setAccessible(true); + methodField.set(connection, requestMethodStr); + } catch (Exception noSuchFieldIllegalAccess) { + throw protocol; + } + } + + connection.setDoInput(true); + connection.setDoOutput(requestMethod.allowRequestBody()); + + // 4.Set request headers + setHeaders(url.toURI(), connection, request); + + // 5. Connect + connection.connect(); + return connection; + } + + /** + * Set request headers, here will add cookies. + * + * @param uri uri. + * @param connection {@link HttpURLConnection}. + * @param request {@link IBasicRequest}. + */ + private void setHeaders(URI uri, HttpURLConnection connection, IBasicRequest request) { + Headers headers = request.headers(); + headers.set(Headers.HEAD_KEY_CONTENT_TYPE, request.getContentType()); + + // To fix bug: accidental EOFException before API 19 + List values = headers.getValues(Headers.HEAD_KEY_CONNECTION); + if (values == null || values.size() == 0) { + headers.set(Headers.HEAD_KEY_CONNECTION, Build.VERSION.SDK_INT > AndroidVersion.KITKAT ? Headers.HEAD_VALUE_CONNECTION_KEEP_ALIVE : Headers.HEAD_VALUE_CONNECTION_CLOSE); + } + + // Content-Length. + RequestMethod requestMethod = request.getRequestMethod(); + if (requestMethod.allowRequestBody()) { + long contentLength = request.getContentLength(); + if (contentLength < Integer.MAX_VALUE) + connection.setFixedLengthStreamingMode((int) contentLength); + else if (Build.VERSION.SDK_INT >= AndroidVersion.KITKAT) + try { + Class connectionClass = connection.getClass(); + Method setFixedLengthStreamingModeMethod = connectionClass.getMethod("setFixedLengthStreamingMode", long.class); + setFixedLengthStreamingModeMethod.invoke(connection, contentLength); + } catch (Throwable e) { + Logger.w(e); + connection.setChunkedStreamingMode(256 * 1024); + } + else + connection.setChunkedStreamingMode(256 * 1024); + headers.set(Headers.HEAD_KEY_CONTENT_LENGTH, Long.toString(contentLength)); + } + + // Cookie. + if (NoHttp.isEnableCookie() && uri != null) + headers.addCookie(uri, NoHttp.getDefaultCookieManager()); + + Map requestHeaders = headers.toRequestHeaders(); + + // Adds all request header to httpConnection. + for (Map.Entry headerEntry : requestHeaders.entrySet()) { + String headKey = headerEntry.getKey(); + String headValue = headerEntry.getValue(); + Logger.i(headKey + ": " + headValue); + connection.setRequestProperty(headKey, headValue); + } + } + + /** + * Write request params. + * + * @param request {@link IBasicRequest}. + * @param outputStream {@link OutputStream}. + * @throws IOException io exception. + */ + private void writeRequestBody(IBasicRequest request, OutputStream outputStream) throws IOException { + // 6. Write request body + Logger.i("-------Send request data start-------"); + OutputStream realOutputStream = IOUtils.toBufferedOutputStream(outputStream); + request.onWriteRequestBody(realOutputStream); + IOUtils.closeQuietly(realOutputStream); + Logger.i("-------Send request data end-------"); + } + + /** + * The redirection process any response. + * + * @param oldRequest need to redirect the {@link Request}. + * @param responseHeaders need to redirect the request of the responding head. + * @return {@link ProtocolResult}. + */ + private Connection handleRedirect(IBasicRequest oldRequest, Headers responseHeaders) { + // redirect request + IBasicRequest redirectRequest = null; + RedirectHandler redirectHandler = oldRequest.getRedirectHandler(); + if (redirectHandler != null) { + if (redirectHandler.isDisallowedRedirect(responseHeaders)) + return new Connection(null, responseHeaders, null, null); + else + redirectRequest = redirectHandler.onRedirect(responseHeaders); + } + if (redirectRequest == null) { + redirectRequest = new StringRequest(responseHeaders.getLocation(), oldRequest.getRequestMethod()); + redirectRequest.setSSLSocketFactory(oldRequest.getSSLSocketFactory()); + redirectRequest.setProxy(oldRequest.getProxy()); + } + return getConnection(redirectRequest); + } + + /** + * Get input stream from connection. + * + * @param responseCode response code of connection. + * @param contentEncoding {@value Headers#HEAD_KEY_CONTENT_ENCODING} value of the HTTP response headers. + * @param urlConnection connection. + * @return when the normal return the correct input stream, returns the error when the response code is more than 400 input stream. + * @throws IOException if no InputStream could be created. + */ + protected InputStream getServerStream(int responseCode, String contentEncoding, HttpURLConnection urlConnection) throws IOException { + if (responseCode >= 400) + return getErrorStream(contentEncoding, urlConnection); + else { + return getInputStream(contentEncoding, urlConnection); + } + } + + /** + * Get the input stream, and automatically extract. + * + * @param contentEncoding {@value Headers#HEAD_KEY_CONTENT_ENCODING} value of the HTTP response headers. + * @param urlConnection {@link HttpURLConnection}. + * @return http input stream. + * @throws IOException Unpack the stream may be thrown, or if no input stream could be created. + */ + protected InputStream getInputStream(String contentEncoding, HttpURLConnection urlConnection) throws IOException { + InputStream inputStream = urlConnection.getInputStream(); + return gzipInputStream(contentEncoding, inputStream); + } + + /** + * Get the wrong input stream, and automatically extract. + * + * @param contentEncoding {@value Headers#HEAD_KEY_CONTENT_ENCODING} value of the HTTP response headers. + * @param urlConnection {@link HttpURLConnection}. + * @return http error stream. + * @throws IOException Unpack the stream may be thrown. + */ + protected InputStream getErrorStream(String contentEncoding, HttpURLConnection urlConnection) throws IOException { + InputStream inputStream = urlConnection.getErrorStream(); + return gzipInputStream(contentEncoding, inputStream); + } + + /** + * Pressure http input stream. + * + * @param contentEncoding {@value Headers#HEAD_KEY_CONTENT_ENCODING} value of the HTTP response headers. + * @param inputStream {@link InputStream}. + * @return It can directly read normal data flow + * @throws IOException if an {@code IOException} occurs. + */ + protected InputStream gzipInputStream(String contentEncoding, InputStream inputStream) throws IOException { + if (HeaderUtil.isGzipContent(contentEncoding)) { + inputStream = new GZIPInputStream(inputStream); + } + return inputStream; + } + + /** + * Parse server response headers, here will save cookies. + * + * @param uri according to the requested URL generated uris. + * @param responseCode responseCode. + * @param responseMessage responseMessage. + * @param responseHeaders responseHeaders of server. + * @return response headers of server. + */ + protected Headers parseResponseHeaders(URI uri, int responseCode, String responseMessage, Map> responseHeaders) { + // handle cookie + if (NoHttp.isEnableCookie()) + try { + NoHttp.getDefaultCookieManager().put(uri, responseHeaders); + } catch (IOException e) { + Logger.e(e, "Save cookie filed: " + uri.toString() + "."); + } + + // handle headers + Headers headers = new HttpHeaders(); + headers.set(responseHeaders); + headers.set(Headers.HEAD_KEY_RESPONSE_MESSAGE, responseMessage); + headers.set(Headers.HEAD_KEY_RESPONSE_CODE, Integer.toString(responseCode)); + // print + for (String headKey : headers.keySet()) { + List headValues = headers.getValues(headKey); + for (String headValue : headValues) { + StringBuilder builder = new StringBuilder(); + if (!TextUtils.isEmpty(headKey)) + builder.append(headKey).append(": "); + if (!TextUtils.isEmpty(headValue)) + builder.append(headValue); + Logger.i(builder.toString()); + } + } + return headers; + } + + ////////// Read response body ////////// + + /** + * This requestMethod and responseCode has responseBody ? + * + * @param requestMethod it's come from {@link RequestMethod}. + * @param responseCode responseCode from server. + * @return true: there is data, false: no data. + */ + public static boolean hasResponseBody(RequestMethod requestMethod, int responseCode) { + return requestMethod != RequestMethod.HEAD && hasResponseBody(responseCode); + } + + /** + * According to the response code to judge whether there is data. + * + * @param responseCode responseCode. + * @return true: there is data, false: no data. + */ + public static boolean hasResponseBody(int responseCode) { + return !(100 <= responseCode && responseCode < 200) && responseCode != 204 && responseCode != 205 && !(300 <= responseCode && responseCode < 400); + } + + /** + * This requestMethod and responseCode has responseBody ? + * + * @param requestMethod it's come from {@link RequestMethod}. + * @param responseCode responseCode from server. + * @return true: there is data, false: no data. + */ + public static boolean hasDownload(RequestMethod requestMethod, int responseCode) { + return requestMethod != RequestMethod.HEAD && hasDownload(responseCode); + } + + /** + * According to the response code to judge whether there is download data. + * + * @param responseCode responseCode. + * @return true: there is data, false: no data. + */ + public static boolean hasDownload(int responseCode) { + return 200 <= responseCode && responseCode < 300 && responseCode != 204 && responseCode != 205; + } + +} diff --git a/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/BasicRequest.java b/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/BasicRequest.java new file mode 100644 index 0000000..b80c759 --- /dev/null +++ b/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/BasicRequest.java @@ -0,0 +1,839 @@ +/* + * Copyright 2015 Yan Zhenjie + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yolanda.nohttp; + +import android.text.TextUtils; + +import com.yolanda.nohttp.tools.CounterOutputStream; +import com.yolanda.nohttp.tools.HeaderUtil; +import com.yolanda.nohttp.tools.IOUtils; +import com.yolanda.nohttp.tools.LinkedMultiValueMap; +import com.yolanda.nohttp.tools.MultiValueMap; + +import org.json.JSONObject; + +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.UnsupportedEncodingException; +import java.net.HttpCookie; +import java.net.Proxy; +import java.net.URLEncoder; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.BlockingQueue; + +import javax.net.ssl.HostnameVerifier; +import javax.net.ssl.SSLSocketFactory; + +/** + *

+ * Implement all the methods of the base class {@link IBasicRequest}. + *

+ * Created in Nov 4, 2015 8:28:50 AM. + * + * @author Yan Zhenjie. + */ +public abstract class BasicRequest implements IBasicRequest { + + private final String boundary = createBoundary(); + private final String startBoundary = "--" + boundary; + private final String endBoundary = startBoundary + "--"; + + /** + * Request priority. + */ + private Priority mPriority = Priority.DEFAULT; + /** + * The sequence. + */ + private int sequence; + /** + * Target address. + */ + private String url; + /** + * Request method. + */ + private RequestMethod mRequestMethod; + /** + * MultipartFormEnable. + */ + private boolean isMultipartFormEnable = false; + /** + * Proxy server. + */ + private Proxy mProxy; + /** + * SSLSockets. + */ + private SSLSocketFactory mSSLSocketFactory = null; + /** + * HostnameVerifier. + */ + private HostnameVerifier mHostnameVerifier = null; + /** + * Connect timeout of request. + */ + private int mConnectTimeout = NoHttp.getDefaultConnectTimeout(); + /** + * Read data timeout. + */ + private int mReadTimeout = NoHttp.getDefaultReadTimeout(); + /** + * Request heads. + */ + private Headers mHeaders; + /** + * After the failure of retries. + */ + private int mRetryCount; + /** + * The params encoding. + */ + private String mParamEncoding; + /** + * Param collection. + */ + private MultiValueMap mParamKeyValues; + /** + * RequestBody. + */ + private InputStream mRequestBody; + /** + * Redirect handler. + */ + private RedirectHandler mRedirectHandler; + /** + * Request queue + */ + private BlockingQueue blockingQueue; + /** + * The record has started. + */ + private boolean isStart = false; + /** + * The request is completed. + */ + private boolean isFinished = false; + /** + * Has been canceled. + */ + private boolean isCanceled = false; + /** + * Cancel sign. + */ + private Object mCancelSign; + /** + * Tag of request. + */ + private Object mTag; + + /** + * Create a request, RequestMethod is {@link RequestMethod#GET}. + * + * @param url request address, like: http://www.yanzhenjie.com. + */ + public BasicRequest(String url) { + this(url, RequestMethod.GET); + } + + /** + * Create a request. + * + * @param url request adress, like: http://www.yanzhenjie.com. + * @param requestMethod request method, like {@link RequestMethod#GET}, {@link RequestMethod#POST}. + */ + public BasicRequest(String url, RequestMethod requestMethod) { + this.url = url; + mRequestMethod = requestMethod; + + mHeaders = new HttpHeaders(); + mHeaders.set(Headers.HEAD_KEY_ACCEPT, Headers.HEAD_VALUE_ACCEPT_ALL); + mHeaders.set(Headers.HEAD_KEY_ACCEPT_ENCODING, Headers.HEAD_VALUE_ACCEPT_ENCODING_GZIP_DEFLATE); + mHeaders.set(Headers.HEAD_KEY_ACCEPT_LANGUAGE, HeaderUtil.systemAcceptLanguage()); + mHeaders.set(Headers.HEAD_KEY_USER_AGENT, UserAgent.instance()); + + mParamKeyValues = new LinkedMultiValueMap(); + } + + @Override + public void setPriority(Priority priority) { + this.mPriority = priority; + } + + @Override + public Priority getPriority() { + return mPriority; + } + + @Override + public void setSequence(int sequence) { + this.sequence = sequence; + } + + @Override + public int getSequence() { + return this.sequence; + } + + @Override + public final int compareTo(IBasicRequest another) { + final Priority me = getPriority(); + final Priority it = another.getPriority(); + return me == it ? getSequence() - another.getSequence() : it.ordinal() - me.ordinal(); + } + + @Override + public String url() { + StringBuilder urlBuilder = new StringBuilder(url); + if (!getRequestMethod().allowRequestBody() && mParamKeyValues.size() > 0) { + StringBuffer paramBuffer = buildCommonParams(getParamKeyValues(), getParamsEncoding()); + if (url.contains("?") && url.contains("=") && paramBuffer.length() > 0) + urlBuilder.append("&"); + else if (paramBuffer.length() > 0 && !url.endsWith("?")) // end with '?', not append '?'. + urlBuilder.append("?"); + urlBuilder.append(paramBuffer); + } + return urlBuilder.toString(); + } + + @Override + public RequestMethod getRequestMethod() { + return mRequestMethod; + } + + @Override + public void setMultipartFormEnable(boolean enable) { + if (enable && !getRequestMethod().allowRequestBody()) + throw new IllegalArgumentException("MultipartFormEnable is request method is the premise of the POST/PUT/PATCH/DELETE, but the Android system under API level 19 does not support the DELETE."); + isMultipartFormEnable = enable; + } + + @Override + public boolean isMultipartFormEnable() { + if (isMultipartFormEnable) { + return true; + } else { + Set keys = mParamKeyValues.keySet(); + for (String key : keys) { + List values = mParamKeyValues.getValues(key); + for (Object value : values) { + if (value instanceof Binary) + return true; + } + } + } + return false; + } + + @Override + public void setProxy(Proxy proxy) { + this.mProxy = proxy; + } + + @Override + public Proxy getProxy() { + return mProxy; + } + + @Override + public void setSSLSocketFactory(SSLSocketFactory socketFactory) { + mSSLSocketFactory = socketFactory; + } + + @Override + public SSLSocketFactory getSSLSocketFactory() { + return mSSLSocketFactory; + } + + @Override + public void setHostnameVerifier(HostnameVerifier hostnameVerifier) { + mHostnameVerifier = hostnameVerifier; + } + + @Override + public HostnameVerifier getHostnameVerifier() { + return mHostnameVerifier; + } + + @Override + public void setConnectTimeout(int connectTimeout) { + mConnectTimeout = connectTimeout; + } + + @Override + public int getConnectTimeout() { + return mConnectTimeout; + } + + @Override + public void setReadTimeout(int readTimeout) { + mReadTimeout = readTimeout; + } + + @Override + public int getReadTimeout() { + return mReadTimeout; + } + + @Override + public void addHeader(String key, String value) { + mHeaders.add(key, value); + } + + @Override + public void setHeader(String key, String value) { + mHeaders.set(key, value); + } + + @Override + public void addHeader(HttpCookie cookie) { + if (cookie != null) + mHeaders.add(Headers.HEAD_KEY_COOKIE, cookie.getName() + "=" + cookie.getValue()); + } + + @Override + public void removeHeader(String key) { + mHeaders.remove(key); + } + + @Override + public void removeAllHeader() { + mHeaders.clear(); + } + + @Override + public Headers headers() { + return mHeaders; + } + + @Override + public void setAccept(String accept) { + mHeaders.set(Headers.HEAD_KEY_ACCEPT, accept); + } + + @Override + public void setAcceptLanguage(String acceptLanguage) { + mHeaders.set(Headers.HEAD_KEY_ACCEPT_LANGUAGE, acceptLanguage); + } + + @Override + public long getContentLength() { + CounterOutputStream outputStream = new CounterOutputStream(); + try { + onWriteRequestBody(outputStream); + } catch (IOException e) { + Logger.e(e); + } + return outputStream.get(); + } + + @Override + public void setContentType(String contentType) { + mHeaders.set(Headers.HEAD_KEY_CONTENT_TYPE, contentType); + } + + @Override + public String getContentType() { + String contentType = mHeaders.getValue(Headers.HEAD_KEY_CONTENT_TYPE, 0); + if (!TextUtils.isEmpty(contentType)) + return contentType; + if (getRequestMethod().allowRequestBody() && isMultipartFormEnable()) + return Headers.HEAD_VALUE_ACCEPT_MULTIPART_FORM_DATA + "; boundary=" + boundary; + else + return Headers.HEAD_VALUE_ACCEPT_APPLICATION_X_WWW_FORM_URLENCODED + "; charset=" + getParamsEncoding(); + } + + @Override + public void setUserAgent(String userAgent) { + mHeaders.set(Headers.HEAD_KEY_USER_AGENT, userAgent); + } + + @Override + public void setRetryCount(int count) { + this.mRetryCount = count; + } + + @Override + public int getRetryCount() { + return mRetryCount; + } + + @Override + public void setParamsEncoding(String encoding) { + this.mParamEncoding = encoding; + } + + @Override + public String getParamsEncoding() { + if (TextUtils.isEmpty(mParamEncoding)) + mParamEncoding = NoHttp.CHARSET_UTF8; + return mParamEncoding; + } + + @Override + public void add(String key, int value) { + add(key, Integer.toString(value)); + } + + @Override + public void add(String key, long value) { + add(key, Long.toString(value)); + } + + @Override + public void add(String key, boolean value) { + add(key, String.valueOf(value)); + } + + @Override + public void add(String key, char value) { + add(key, String.valueOf(value)); + } + + @Override + public void add(String key, double value) { + add(key, Double.toString(value)); + } + + @Override + public void add(String key, float value) { + add(key, Float.toString(value)); + } + + @Override + public void add(String key, short value) { + add(key, Integer.toString(value)); + } + + @Override + public void add(String key, byte value) { + add(key, Integer.toString(value)); + } + + @Override + public void add(String key, String value) { + if (value != null) { + mParamKeyValues.set(key, value); + } + } + + @Override + public void set(String key, String value) { + if (value != null) + mParamKeyValues.set(key, value); + } + + @Override + public void add(String key, Binary binary) { + mParamKeyValues.add(key, binary); + } + + @Override + public void set(String key, Binary binary) { + mParamKeyValues.set(key, binary); + } + + @Override + public void add(String key, File file) { + add(key, new FileBinary(file)); + } + + @Override + public void set(String key, File file) { + set(key, new FileBinary(file)); + } + + @Override + public void add(String key, List binaries) { + if (binaries != null) { + for (Binary binary : binaries) + mParamKeyValues.add(key, binary); + } + } + + @Override + public void set(String key, List binaries) { + mParamKeyValues.remove(key); + add(key, binaries); + } + + @Override + public void add(Map params) { + if (params != null) { + for (Map.Entry stringEntry : params.entrySet()) + add(stringEntry.getKey(), stringEntry.getValue()); + } + } + + @Override + public void set(Map params) { + if (params != null) { + for (Map.Entry stringEntry : params.entrySet()) + set(stringEntry.getKey(), stringEntry.getValue()); + } + } + + @Override + public List remove(String key) { + return mParamKeyValues.remove(key); + } + + @Override + public void removeAll() { + mParamKeyValues.clear(); + } + + @Override + public MultiValueMap getParamKeyValues() { + return mParamKeyValues; + } + + @Override + public void setDefineRequestBody(InputStream requestBody, String contentType) { + if (requestBody == null || contentType == null) + throw new IllegalArgumentException("The requestBody and contentType must be can't be null"); + if (requestBody instanceof ByteArrayInputStream || requestBody instanceof FileInputStream) { + this.mRequestBody = requestBody; + mHeaders.set(Headers.HEAD_KEY_CONTENT_TYPE, contentType); + } else { + throw new IllegalArgumentException("Can only accept ByteArrayInputStream and FileInputStream type of stream"); + } + } + + @Override + public void setDefineRequestBody(String requestBody, String contentType) { + if (!TextUtils.isEmpty(requestBody)) { + try { + mRequestBody = IOUtils.toInputStream(requestBody, getParamsEncoding()); + if (!TextUtils.isEmpty(contentType)) + mHeaders.set(Headers.HEAD_KEY_CONTENT_TYPE, contentType + "; charset=" + getParamsEncoding()); + } catch (UnsupportedEncodingException e) { + setDefineRequestBody(IOUtils.toInputStream(requestBody), contentType); + } + } + } + + @Override + public void setDefineRequestBodyForJson(String jsonBody) { + if (!TextUtils.isEmpty(jsonBody)) + setDefineRequestBody(jsonBody, Headers.HEAD_VALUE_ACCEPT_APPLICATION_JSON); + } + + @Override + public void setDefineRequestBodyForJson(JSONObject jsonBody) { + if (jsonBody != null) + setDefineRequestBody(jsonBody.toString(), Headers.HEAD_VALUE_ACCEPT_APPLICATION_JSON); + } + + @Override + public void setDefineRequestBodyForXML(String xmlBody) { + if (!TextUtils.isEmpty(xmlBody)) + setDefineRequestBody(xmlBody, Headers.HEAD_VALUE_ACCEPT_APPLICATION_XML); + } + + /** + * Is there a custom request inclusions. + * + * @return Returns true representatives have, return false on behalf of the no. + */ + protected boolean hasDefineRequestBody() { + return mRequestBody != null; + } + + /** + * To get custom inclusions. + * + * @return {@link InputStream}. + */ + protected InputStream getDefineRequestBody() { + return mRequestBody; + } + + @Override + public void onPreExecute() { + } + + @Override + public void onWriteRequestBody(OutputStream writer) throws IOException { + if (mRequestBody != null) { + writeRequestBody(writer); + } else if (isMultipartFormEnable()) { + writeFormStreamData(writer); + } else { + writeCommonStreamData(writer); + } + } + + /** + * Send form data. + * + * @param writer {@link OutputStream}. + * @throws IOException write error. + */ + protected void writeFormStreamData(OutputStream writer) throws IOException { + Set keys = mParamKeyValues.keySet(); + for (String key : keys) { + List values = mParamKeyValues.getValues(key); + for (Object value : values) { + if (!isCanceled()) { + if (value != null && value instanceof String) { + if (!(writer instanceof CounterOutputStream)) + Logger.i(key + "=" + value); + writeFormString(writer, key, value.toString()); + } else if (value != null && value instanceof Binary) { + if (!(writer instanceof CounterOutputStream)) + Logger.i(key + " is Binary"); + writeFormBinary(writer, key, (Binary) value); + } + writer.write("\r\n".getBytes()); + } + } + } + writer.write((endBoundary).getBytes()); + } + + /** + * Send text data in a form. + * + * @param writer {@link OutputStream} + * @param key equivalent to form the name of the input label, {@code "Content-Disposition: form-data; name=key"}. + * @param value equivalent to form the value of the input label. + * @throws IOException Write the data may be abnormal. + */ + private void writeFormString(OutputStream writer, String key, String value) throws IOException { + StringBuilder stringFieldBuilder = new StringBuilder(startBoundary).append("\r\n"); + + stringFieldBuilder.append("Content-Disposition: form-data; name=\"").append(key).append("\"\r\n"); + stringFieldBuilder.append("Content-Type: text/plain; charset=").append(getParamsEncoding()).append("\r\n\r\n"); + + writer.write(stringFieldBuilder.toString().getBytes(getParamsEncoding())); + + writer.write(value.getBytes(getParamsEncoding())); + } + + /** + * Send binary data in a form. + */ + private void writeFormBinary(OutputStream writer, String key, Binary value) throws IOException { + if (!value.isCanceled()) { + StringBuilder binaryFieldBuilder = new StringBuilder(startBoundary).append("\r\n"); + + binaryFieldBuilder.append("Content-Disposition: form-data; name=\"").append(key).append("\""); + binaryFieldBuilder.append("; filename=\"").append(value.getFileName()).append("\"\r\n"); + + binaryFieldBuilder.append("Content-Type: ").append(value.getMimeType()).append("\r\n"); + binaryFieldBuilder.append("Content-Transfer-Encoding: binary\r\n\r\n"); + + writer.write(binaryFieldBuilder.toString().getBytes()); + + if (writer instanceof CounterOutputStream) { + ((CounterOutputStream) writer).write(value.getLength()); + } else { + value.onWriteBinary(writer); + } + } + } + + /** + * Send non form data. + * + * @param writer {@link OutputStream}. + * @throws IOException write error. + */ + protected void writeCommonStreamData(OutputStream writer) throws IOException { + String requestBody = buildCommonParams(getParamKeyValues(), getParamsEncoding()).toString(); + if (!(writer instanceof CounterOutputStream)) + Logger.i("Push RequestBody: " + requestBody); + writer.write(requestBody.getBytes()); + } + + /** + * Send request requestBody. + * + * @param writer {@link OutputStream}. + * @throws IOException write error. + */ + protected void writeRequestBody(OutputStream writer) throws IOException { + if (mRequestBody != null) { + if (writer instanceof CounterOutputStream) { + writer.write(mRequestBody.available()); + } else { + IOUtils.write(mRequestBody, writer); + IOUtils.closeQuietly(mRequestBody); + mRequestBody = null; + } + } + } + + @Override + public void setRedirectHandler(RedirectHandler redirectHandler) { + mRedirectHandler = redirectHandler; + } + + @Override + public RedirectHandler getRedirectHandler() { + return mRedirectHandler; + } + + @Override + public void setTag(Object tag) { + this.mTag = tag; + } + + @Override + public Object getTag() { + return this.mTag; + } + + @Override + public void setQueue(BlockingQueue queue) { + blockingQueue = queue; + } + + @Override + public boolean inQueue() { + return blockingQueue != null && blockingQueue.contains(this); + } + + @Override + public void start() { + this.isStart = true; + } + + @Override + public boolean isStarted() { + return isStart; + } + + @Override + public void finish() { + this.isFinished = true; + } + + @Override + public boolean isFinished() { + return isFinished; + } + + @Override + public void cancel() { + if (!isCanceled) { + isCanceled = true; + if (mRequestBody != null) + IOUtils.closeQuietly(mRequestBody); + + if (blockingQueue != null) + blockingQueue.remove(this); + + // cancel file upload + Set keys = mParamKeyValues.keySet(); + for (String key : keys) { + List values = mParamKeyValues.getValues(key); + for (Object value : values) + if (value != null && value instanceof Binary) + ((Binary) value).cancel(); + } + } + } + + @Override + public boolean isCanceled() { + return isCanceled; + } + + public void setCancelSign(Object sign) { + this.mCancelSign = sign; + } + + @Override + public void cancelBySign(Object sign) { + if (mCancelSign == sign) + cancel(); + } + + ////////// static module ///////// + + /** + * Split joint non form data. + * + * @param paramMap param map. + * @param encodeCharset charset. + * @return string parameter combination, each key value on nails with {@code "&"} space. + */ + public static StringBuffer buildCommonParams(MultiValueMap paramMap, String encodeCharset) { + StringBuffer paramBuffer = new StringBuffer(); + Set keySet = paramMap.keySet(); + for (String key : keySet) { + List values = paramMap.getValues(key); + for (Object value : values) { + if (value != null && value instanceof CharSequence) { + paramBuffer.append("&"); + try { + paramBuffer.append(URLEncoder.encode(key, encodeCharset)); + paramBuffer.append("="); + paramBuffer.append(URLEncoder.encode(value.toString(), encodeCharset)); + } catch (UnsupportedEncodingException e) { + Logger.e("Encoding " + encodeCharset + " format is not supported by the system"); + paramBuffer.append(key); + paramBuffer.append("="); + paramBuffer.append(value.toString()); + } + } + } + } + if (paramBuffer.length() > 0) + paramBuffer.deleteCharAt(0); + return paramBuffer; + } + + /** + * Create acceptLanguage. + * + * @return Returns the client can accept the language types. Such as:zh-CN,zh. + * @deprecated use {@link HeaderUtil#systemAcceptLanguage()} instead. + */ + @Deprecated + public static String defaultAcceptLanguage() { + return HeaderUtil.systemAcceptLanguage(); + } + + /** + * Randomly generated boundary mark. + * + * @return Random code. + */ + public static String createBoundary() { + StringBuffer sb = new StringBuffer("----NoHttpFormBoundary"); + for (int t = 1; t < 12; t++) { + long time = System.currentTimeMillis() + t; + if (time % 3L == 0L) { + sb.append((char) (int) time % '\t'); + } else if (time % 3L == 1L) { + sb.append((char) (int) (65L + time % 26L)); + } else { + sb.append((char) (int) (97L + time % 26L)); + } + } + return sb.toString(); + } + +} \ No newline at end of file diff --git a/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/Binary.java b/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/Binary.java new file mode 100644 index 0000000..55f8a65 --- /dev/null +++ b/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/Binary.java @@ -0,0 +1,58 @@ +/* + * Copyright 2015 Yan Zhenjie + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yolanda.nohttp; + +import com.yolanda.nohttp.able.Cancelable; + +import java.io.OutputStream; + +/** + *

File interface. + * All the methods are called in Son thread.

+ * Created in Oct 12, 2015 4:44:07 PM. + * + * @author Yan Zhenjie. + */ +public interface Binary extends Cancelable { + + /** + * Returns the size of the Binary, if size is 0, the Binary Field will not be sent. The rest of the {@link Binary} method will not be invoked. + * + * @return Long length. + */ + long getLength(); + + /** + * Write your Binary data through flow out. + * + * @param outputStream {@link OutputStream}. + */ + void onWriteBinary(OutputStream outputStream); + + /** + * Return the fileName, Can be null. + * + * @return File name. + */ + String getFileName(); + + /** + * Return mimeType of binary. + * + * @return MimeType. + */ + String getMimeType(); +} diff --git a/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/BitmapBinary.java b/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/BitmapBinary.java new file mode 100644 index 0000000..0d14254 --- /dev/null +++ b/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/BitmapBinary.java @@ -0,0 +1,92 @@ +/* + * Copyright 2015 Yan Zhenjie + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yolanda.nohttp; + +import android.graphics.Bitmap; +import android.util.Log; + +import com.yolanda.nohttp.tools.IOUtils; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; + +/** + *

+ * A default implementation of Binary. + * All the methods are called in Son thread. + *

+ * Created in Oct 17, 2015 12:40:54 PM. + * + * @author Yan Zhenjie. + */ +public class BitmapBinary extends BasicBinary { + + private InputStream inputStream; + + /** + * An input stream {@link Binary}. + * + * @param bitmap image. + * @param fileName file name. Had better pass this value, unless the server tube don't care about the file name. + */ + public BitmapBinary(Bitmap bitmap, String fileName) { + this(bitmap, fileName, null); + } + + /** + * An input stream {@link Binary}. + * + * @param bitmap image. + * @param fileName file name. Had better pass this value, unless the server tube don't care about the file name. + * @param mimeType such as: image/png. + */ + public BitmapBinary(Bitmap bitmap, String fileName, String mimeType) { + super(fileName, mimeType); + + if (bitmap != null && !bitmap.isRecycled()) { + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + bitmap.compress(Bitmap.CompressFormat.PNG, 100, outputStream); + bitmap.recycle(); + IOUtils.closeQuietly(outputStream); + inputStream = new ByteArrayInputStream(outputStream.toByteArray()); + } else { + Log.e("Binary", "Binary was cancelled, because the Bitmap is null or bitmap is recycled."); + super.cancel(); + } + } + + @Override + public void cancel() { + IOUtils.closeQuietly(inputStream); + super.cancel(); + } + + @Override + protected InputStream getInputStream() throws IOException { + return inputStream; + } + + @Override + public long getBinaryLength() { + try { + return inputStream == null ? 0 : inputStream.available(); + } catch (IOException e) { + return 0; + } + } +} \ No newline at end of file diff --git a/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/ByteArrayBinary.java b/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/ByteArrayBinary.java new file mode 100644 index 0000000..e9d9a77 --- /dev/null +++ b/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/ByteArrayBinary.java @@ -0,0 +1,51 @@ +/* + * Copyright 2015 Yan Zhenjie + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yolanda.nohttp; + +import java.io.ByteArrayInputStream; + +/** + *

+ * A default implementation of Binary. + * All the methods are called in Son thread. + *

+ * Created in Oct 17, 2015 12:40:54 PM. + * + * @author Yan Zhenjie. + */ +public class ByteArrayBinary extends InputStreamBinary { + + /** + * A byte array of {@link Binary}. + * + * @param byteArray byte array. + * @param fileName file name. Had better pass this value, unless the server tube don't care about the file name. + */ + public ByteArrayBinary(byte[] byteArray, String fileName) { + super(new ByteArrayInputStream(byteArray), fileName); + } + + /** + * A byte array of {@link Binary}. + * + * @param byteArray byte array. + * @param fileName file name. Had better pass this value, unless the server tube don't care about the file name. + * @param mimeType content type. + */ + public ByteArrayBinary(byte[] byteArray, String fileName, String mimeType) { + super(new ByteArrayInputStream(byteArray), fileName, mimeType); + } +} diff --git a/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/Connection.java b/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/Connection.java new file mode 100644 index 0000000..8959be3 --- /dev/null +++ b/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/Connection.java @@ -0,0 +1,133 @@ +/* + * Copyright 2015 Yan Zhenjie + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yolanda.nohttp; + +import com.yolanda.nohttp.tools.IOUtils; + +import java.io.Closeable; +import java.io.IOException; +import java.io.InputStream; +import java.net.HttpURLConnection; +import java.net.URL; + +/** + * Created in May 3, 2016 11:05:03 PM. + * + * @author Yan Zhenjie. + */ +public class Connection implements Closeable { + + /** + * HttpURLConnection + */ + private HttpURLConnection connection; + /** + * Server response header. + */ + private Headers mResponseHeaders; + /** + * Server data steram. + */ + private InputStream mInputStream; + /** + * Exception of connection. + */ + private Exception mException; + + /** + * Create a response. + * + * @param responseHeaders response headers. + * @param inputStream According to the response code, the incoming data stream server. + * @param exception Connection exceptions that occur in the process. + */ + Connection(HttpURLConnection connection, Headers responseHeaders, InputStream inputStream, Exception exception) { + this.connection = connection; + this.mResponseHeaders = responseHeaders; + this.mInputStream = inputStream; + this.mException = exception; + } + + /** + * Get the {@link URL} of connection. + * + * @return {@link URL}. + */ + public URL getURL() { + return connection.getURL(); + } + + /** + * Get response headers. + * + * @return the responseHeaders. + */ + public Headers responseHeaders() { + return mResponseHeaders; + } + + /** + * Set response headers. + * + * @param responseHeaders the responseHeaders to set. + */ + void setResponseHeaders(Headers responseHeaders) { + this.mResponseHeaders = responseHeaders; + } + + /** + * Get stream from server. + * + * @return the inputStream. + */ + public InputStream serverStream() { + return mInputStream; + } + + /** + * Set the stream from server. + * + * @param inputStream the inputStream to set. + */ + void setServerStream(InputStream inputStream) { + this.mInputStream = inputStream; + } + + /** + * Get exception for execution. + * + * @return the exception. + */ + public Exception exception() { + return mException; + } + + /** + * Set execetpin for execution. + * + * @param exception the exception to set. + */ + void setException(Exception exception) { + this.mException = exception; + } + + @Override + public void close() throws IOException { + IOUtils.closeQuietly(mInputStream); + IOUtils.closeQuietly(connection); + } + +} diff --git a/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/FileBinary.java b/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/FileBinary.java new file mode 100644 index 0000000..e5bbddd --- /dev/null +++ b/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/FileBinary.java @@ -0,0 +1,96 @@ +/* + * Copyright 2015 Yan Zhenjie + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yolanda.nohttp; + +import android.util.Log; + +import com.yolanda.nohttp.tools.IOUtils; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; + +/** + *

+ * A default implementation of Binary. + * All the methods are called in Son thread. + *

+ * Created in Oct 17, 2015 12:40:54 PM. + * + * @author Yan Zhenjie. + */ +public class FileBinary extends BasicBinary { + + private FileInputStream inputStream; + + /** + * File binary. + * + * @param file a file. + */ + public FileBinary(File file) { + this(file, file.getName(), null); + } + + /** + * File binary. + * + * @param file a file. + * @param fileName file name. + */ + public FileBinary(File file, String fileName) { + this(file, fileName, null); + } + + /** + * File binary. + * + * @param file a file. + * @param fileName file name. + * @param mimeType content type. + */ + public FileBinary(File file, String fileName, String mimeType) { + super(fileName, mimeType); + try { + this.inputStream = new FileInputStream(file); + } catch (FileNotFoundException e) { + Log.e("Binary", "Binary was cancelled, because the file does not exist."); + super.cancel(); + } + } + + @Override + public void cancel() { + IOUtils.closeQuietly(inputStream); + super.cancel(); + } + + @Override + public long getBinaryLength() { + try { + return inputStream == null ? 0 : inputStream.available(); + } catch (IOException e) { + return 0; + } + } + + @Override + protected InputStream getInputStream() throws IOException { + return inputStream; + } +} diff --git a/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/Headers.java b/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/Headers.java new file mode 100644 index 0000000..e702c1a --- /dev/null +++ b/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/Headers.java @@ -0,0 +1,361 @@ +/* + * Copyright 2015 Yan Zhenjie + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yolanda.nohttp; + +import com.yolanda.nohttp.tools.MultiValueMap; + +import org.json.JSONException; + +import java.net.CookieHandler; +import java.net.HttpCookie; +import java.net.URI; +import java.util.List; +import java.util.Map; + +/** + *

+ * Http header. + *

+ * Created in Jan 10, 2016 2:29:42 PM. + * + * @author Yan Zhenjie. + */ +public interface Headers extends MultiValueMap { + + /** + * The value is {@value}. + */ + String HEAD_KEY_RESPONSE_CODE = "ResponseCode"; + + /** + * The value is {@value}. + */ + String HEAD_KEY_RESPONSE_MESSAGE = "ResponseMessage"; + + /** + * The value is {@value}. + */ + String HEAD_KEY_ACCEPT = "Accept"; + + /** + * The value is {@value}. + */ + String HEAD_VALUE_ACCEPT_ALL = "application/json,application/xml,application/xhtml+xml,text/html;q=0.9,image/webp,*/*;q=0.8"; + + /** + * The value is {@value}. + */ + String HEAD_VALUE_ACCEPT_APPLICATION_X_WWW_FORM_URLENCODED = "application/x-www-form-urlencoded"; + + /** + * The value is {@value}. + */ + String HEAD_VALUE_ACCEPT_MULTIPART_FORM_DATA = "multipart/form-data"; + + /** + * The value is {@value}. + */ + String HEAD_VALUE_ACCEPT_APPLICATION_OCTET_STREAM = "application/octet-stream"; + + /** + * The value is {@value}. + */ + String HEAD_VALUE_ACCEPT_APPLICATION_JSON = "application/json"; + + /** + * The value is {@value}. + */ + String HEAD_VALUE_ACCEPT_APPLICATION_XML = "application/xml"; + + /** + * The value is {@value}. + */ + String HEAD_KEY_ACCEPT_ENCODING = "Accept-Encoding"; + + /** + * The value is {@value}. + */ + String HEAD_VALUE_ACCEPT_ENCODING_GZIP_DEFLATE = "gzip, deflate";// no sdch + + /** + * The value is {@value}. + */ + String HEAD_KEY_ACCEPT_LANGUAGE = "Accept-Language"; + + /** + * The value is {@value}. + */ + String HEAD_KEY_ACCEPT_RANGE = "Accept-Range"; + + /** + * The value is {@value}. + */ + String HEAD_KEY_CONTENT_DISPOSITION = "Content-Disposition"; + + /** + * The value is {@value}. + */ + String HEAD_KEY_CONTENT_ENCODING = "Content-Encoding"; + + /** + * The value is {@value}. + */ + String HEAD_KEY_CONTENT_LENGTH = "Content-Length"; + + /** + * The value is {@value}. + */ + String HEAD_KEY_CONTENT_RANGE = "Content-Range"; + + /** + * The value is {@value}. + */ + String HEAD_KEY_CONTENT_TYPE = "Content-Type"; + + /** + * The value is {@value}. + */ + String HEAD_KEY_CACHE_CONTROL = "Cache-Control"; + + /** + * The value is {@value}. + */ + String HEAD_KEY_CONNECTION = "Connection"; + + /** + * The value is {@value}. + */ + String HEAD_VALUE_CONNECTION_KEEP_ALIVE = "keep-alive"; + + /** + * The value is {@value}. + */ + String HEAD_VALUE_CONNECTION_CLOSE = "close"; + + /** + * The value is {@value}. + */ + String HEAD_KEY_DATE = "Date"; + + /** + * The value is {@value}. + */ + String HEAD_KEY_EXPIRES = "Expires"; + + /** + * The value is {@value}. + */ + String HEAD_KEY_E_TAG = "ETag"; + + /** + * The value is {@value}. + */ + String HEAD_KEY_PRAGMA = "Pragma"; + + /** + * The value is {@value}. + */ + String HEAD_KEY_IF_MODIFIED_SINCE = "If-Modified-Since"; + + /** + * The value is {@value}. + */ + String HEAD_KEY_IF_NONE_MATCH = "If-None-Match"; + + /** + * The value is {@value}. + */ + String HEAD_KEY_LAST_MODIFIED = "Last-Modified"; + + /** + * The value is {@value}. + */ + String HEAD_KEY_LOCATION = "Location"; + + /** + * The value is {@value}. + */ + String HEAD_KEY_USER_AGENT = "User-Agent"; + + /** + * The value is {@value}. + */ + String HEAD_KEY_COOKIE = "Cookie"; + + /** + * The value is {@value}. + */ + String HEAD_KEY_COOKIE2 = "Cookie2"; + + /** + * The value is {@value}. + */ + String HEAD_KEY_SET_COOKIE = "Set-Cookie"; + + /** + * The value is {@value}. + */ + String HEAD_KEY_SET_COOKIE2 = "Set-Cookie2"; + + /** + * Copy all head to Headers. + * + * @param headers headers. + */ + void addAll(Headers headers); + + /** + * Remove all of the head now, add a new head in. + * + * @param headers headers. + */ + void setAll(Headers headers); + + /** + * Conform to the URI of the Cookie is added to the head. + * + * @param uri url. + * @param cookieHandler cookieHandler. + */ + void addCookie(URI uri, CookieHandler cookieHandler); + + /** + * From the json format String parsing out the {@code Map>} data. + * + * @param jsonString json string. + * @throws JSONException thrown it when format error. + */ + void setJSONString(String jsonString) throws JSONException; + + /** + * Into a json format string. + * + * @return Json format data. + */ + String toJSONString(); + + /** + * Into a single key-value map. + * + * @return Map. + */ + Map toRequestHeaders(); + + /** + * To multiple key - the value of the map. + * + * @return Map format data. + */ + Map> toResponseHeaders(); + + /** + * All the cookies in header information. + * + * @return {@code List}. + */ + List getCookies(); + + /** + * {@value #HEAD_KEY_CACHE_CONTROL}. + * + * @return CacheControl. + */ + String getCacheControl(); + + /** + * {@value HEAD_KEY_CONTENT_DISPOSITION}. + * + * @return {@value HEAD_KEY_CONTENT_DISPOSITION}. + */ + String getContentDisposition(); + + /** + * {@value #HEAD_KEY_CONTENT_ENCODING}. + * + * @return ContentEncoding. + */ + String getContentEncoding(); + + /** + * {@value #HEAD_KEY_CONTENT_LENGTH}. + * + * @return ContentLength. + */ + int getContentLength(); + + /** + * {@value #HEAD_KEY_CONTENT_TYPE}. + * + * @return ContentType. + */ + String getContentType(); + + /** + * {@value #HEAD_KEY_CONTENT_RANGE} or {@value #HEAD_KEY_ACCEPT_RANGE}. + * + * @return {@value #HEAD_KEY_CONTENT_RANGE} or {@value #HEAD_KEY_ACCEPT_RANGE}. + */ + String getContentRange(); + + /** + * {@value #HEAD_KEY_DATE}. + * + * @return Date. + */ + long getDate(); + + /** + * {@value #HEAD_KEY_E_TAG}. + * + * @return ETag. + */ + String getETag(); + + /** + * {@value #HEAD_KEY_EXPIRES}. + * + * @return Expiration. + */ + long getExpiration(); + + /** + * {@value #HEAD_KEY_LAST_MODIFIED}. + * + * @return LastModified. + */ + long getLastModified(); + + /** + * {@value #HEAD_KEY_LOCATION}. + * + * @return Location. + */ + String getLocation(); + + /** + * {@value #HEAD_KEY_RESPONSE_CODE}. + * + * @return ResponseCode. + */ + int getResponseCode(); + + /** + * {@value #HEAD_KEY_RESPONSE_MESSAGE}. + * + * @return ResponseMessage. + */ + String getResponseMessage(); +} diff --git a/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/HttpHeaders.java b/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/HttpHeaders.java new file mode 100644 index 0000000..499a247 --- /dev/null +++ b/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/HttpHeaders.java @@ -0,0 +1,271 @@ +/* + * Copyright 2015 Yan Zhenjie + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yolanda.nohttp; + +import android.text.TextUtils; + +import com.yolanda.nohttp.tools.HeaderUtil; +import com.yolanda.nohttp.tools.TreeMultiValueMap; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import java.io.IOException; +import java.net.CookieHandler; +import java.net.HttpCookie; +import java.net.URI; +import java.text.ParseException; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.HashMap; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + *

+ * {@link Headers} The default implementation. + *

+ * Created in Jan 10, 2016 2:37:06 PM. + * + * @author Yan Zhenjie. + */ +public class HttpHeaders extends TreeMultiValueMap implements Headers { + + public HttpHeaders() { + super(new Comparator() { + @Override + public int compare(String o1, String o2) { + return o1.compareTo(o2); + } + }); + } + + @Override + public void addAll(Headers headers) { + if (headers != null) { + Set keySet = headers.keySet(); + for (String key : keySet) { + add(key, headers.getValues(key)); + } + } + } + + @Override + public void setAll(Headers headers) { + if (headers != null) { + Set keySet = headers.keySet(); + for (String key : keySet) { + set(key, headers.getValues(key)); + } + } + } + + @Override + public void addCookie(URI uri, CookieHandler cookieHandler) { + try { + Map> diskCookies = cookieHandler.get(uri, new HashMap>()); + for (Map.Entry> entry : diskCookies.entrySet()) { + String key = entry.getKey(); + List value = entry.getValue(); + if ((HEAD_KEY_COOKIE.equalsIgnoreCase(key) || HEAD_KEY_COOKIE2.equalsIgnoreCase(key))) { + add(key, TextUtils.join("; ", value)); + } + } + } catch (IOException e) { + Logger.e(e); + } + } + + @Override + public void setJSONString(String jsonString) throws JSONException { + clear(); + JSONObject jsonObject = new JSONObject(jsonString); + Iterator keySet = jsonObject.keys(); + while (keySet.hasNext()) { + String key = keySet.next(); + String value = jsonObject.optString(key); + JSONArray values = new JSONArray(value); + if (values != null) + for (int i = 0; i < values.length(); i++) + add(key, values.optString(i)); + } + } + + @Override + public final String toJSONString() { + JSONObject jsonObject = new JSONObject(); + Set>> entrySet = entrySet(); + for (Map.Entry> entry : entrySet) { + String key = entry.getKey(); + List values = entry.getValue(); + JSONArray value = new JSONArray(values); + try { + jsonObject.put(key, value); + } catch (JSONException e) { + Logger.w(e); + } + } + + return jsonObject.toString(); + } + + @Override + public Map toRequestHeaders() { + Map singleMap = new LinkedHashMap(); + for (Map.Entry> entry : entrySet()) { + String key = entry.getKey(); + List value = entry.getValue(); + String trueValue = TextUtils.join("; ", value); + singleMap.put(key, trueValue); + } + return singleMap; + } + + @Override + public Map> toResponseHeaders() { + return getSource(); + } + + @Override + public List getCookies() { + List cookies = new ArrayList(); + for (String key : keySet()) { + if (key.equalsIgnoreCase(HEAD_KEY_SET_COOKIE) || key.equalsIgnoreCase(HEAD_KEY_SET_COOKIE2)) { + List cookieValues = getValues(key); + for (String cookieStr : cookieValues) { + for (HttpCookie cookie : HttpCookie.parse(cookieStr)) + cookies.add(cookie); + } + } + } + return cookies; + } + + @Override + public String getCacheControl() { + // first http1.1, second http1.0 + List cacheControls = getValues(HEAD_KEY_CACHE_CONTROL); + if (cacheControls == null) + cacheControls = getValues(HEAD_KEY_PRAGMA); + if (cacheControls == null) + cacheControls = new ArrayList(); + return TextUtils.join(",", cacheControls); + } + + @Override + public String getContentDisposition() { + return getValue(HEAD_KEY_CONTENT_DISPOSITION, 0); + } + + @Override + public String getContentEncoding() { + return getValue(HEAD_KEY_CONTENT_ENCODING, 0); + } + + @Override + public int getContentLength() { + String contentLength = getValue(HEAD_KEY_CONTENT_LENGTH, 0); + try { + return Integer.parseInt(contentLength); + } catch (Throwable e) { + } + return 0; + } + + @Override + public String getContentRange() { + String contentRange = getValue(HEAD_KEY_CONTENT_RANGE, 0); + if (contentRange == null) + contentRange = getValue(HEAD_KEY_ACCEPT_RANGE, 0); + return contentRange; + } + + @Override + public int getResponseCode() { + String responseCode = getValue(HEAD_KEY_RESPONSE_CODE, 0); + int code = 0; + try { + code = Integer.parseInt(responseCode); + } catch (Exception e) { + } + return code; + } + + @Override + public String getResponseMessage() { + return getValue(HEAD_KEY_RESPONSE_MESSAGE, 0); + } + + @Override + public String getContentType() { + return getValue(HEAD_KEY_CONTENT_TYPE, 0); + } + + @Override + public long getDate() { + return getDateField(HEAD_KEY_DATE); + } + + @Override + public String getETag() { + return getValue(HEAD_KEY_E_TAG, 0); + } + + @Override + public long getExpiration() { + return getDateField(HEAD_KEY_EXPIRES); + } + + @Override + public long getLastModified() { + return getDateField(HEAD_KEY_LAST_MODIFIED); + } + + @Override + public String getLocation() { + return getValue(HEAD_KEY_LOCATION, 0); + } + + /** + *

+ * Returns the date value in milliseconds since 1970.1.1, 00:00h corresponding to the header field field. The + * defaultValue will be returned if no such field can be found in the response header. + *

+ * + * @param key the header field name. + * @return the header field represented in milliseconds since January 1, 1970 GMT. + */ + private long getDateField(String key) { + String value = getValue(key, 0); + if (!TextUtils.isEmpty(value)) + try { + return HeaderUtil.parseGMTToMillis(value); + } catch (ParseException e) { + Logger.w(e); + } + return 0; + } + + @Override + public String toString() { + return toJSONString(); + } + +} diff --git a/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/IBasicRequest.java b/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/IBasicRequest.java new file mode 100644 index 0000000..325bd79 --- /dev/null +++ b/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/IBasicRequest.java @@ -0,0 +1,545 @@ +/* + * Copyright © Yan Zhenjie. All Rights Reserved + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yolanda.nohttp; + +import com.yolanda.nohttp.able.Cancelable; +import com.yolanda.nohttp.able.Finishable; +import com.yolanda.nohttp.able.Queueable; +import com.yolanda.nohttp.able.SignCancelable; +import com.yolanda.nohttp.able.Startable; +import com.yolanda.nohttp.tools.MultiValueMap; + +import org.json.JSONObject; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.HttpCookie; +import java.net.Proxy; +import java.util.List; +import java.util.Map; + +import javax.net.ssl.HostnameVerifier; +import javax.net.ssl.SSLSocketFactory; + +/** + * Created by Yan Zhenjie on 2016/8/20. + */ +public interface IBasicRequest extends IPriority, Queueable, Startable, Cancelable, SignCancelable, Finishable, Comparable { + + /* + * ===================================================== + * || Client || + * ===================================================== + */ + + /** + * Mandatory set to form pattern to transmit data. + *

MultipartFormEnable is request method is the premise of the POST/PUT/PATCH/DELETE, but the Android system under API level 19 does not support the DELETE.

+ * + * @param enable true enable, other wise false. + */ + void setMultipartFormEnable(boolean enable); + + /** + * Set proxy server. + * + * @param proxy Can use {@code Proxy proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress("64.233.162.83", 80));}. + */ + void setProxy(Proxy proxy); + + /** + * Sets the {@link SSLSocketFactory} for this request. + * + * @param socketFactory {@link SSLSocketFactory}. + */ + void setSSLSocketFactory(SSLSocketFactory socketFactory); + + /** + * Set the {@link HostnameVerifier}. + * + * @param hostnameVerifier {@link HostnameVerifier}. + */ + void setHostnameVerifier(HostnameVerifier hostnameVerifier); + + /** + * Sets the connection timeout time. + * + * @param connectTimeout timeout number, Unit is a millisecond. + */ + void setConnectTimeout(int connectTimeout); + + /** + * Sets the read timeout time. + * + * @param readTimeout timeout number, Unit is a millisecond. + */ + void setReadTimeout(int readTimeout); + + /** + * Add a new key-value header. + * + * @param key key. + * @param value value. + */ + void addHeader(String key, String value); + + /** + * If there is a key to delete, and then add a new key-value header. + * + * @param key key. + * @param value value. + */ + void setHeader(String key, String value); + + /** + *

Add a {@link HttpCookie}.

+ * Just like the: + *
+     *     HttpCookie httpCookie = getHttpCookie();
+     *     if(httpCookie != null)
+     *          request.addHeader("Cookie", cookie.getName() + "=" + cookie.getValue());
+     *     ...
+     * 
+ * + * @param cookie {@link HttpCookie}. + */ + void addHeader(HttpCookie cookie); + + /** + * Remove the key from the information. + * + * @param key key. + */ + void removeHeader(String key); + + /** + * Remove all header. + */ + void removeAllHeader(); + + /** + * Set the accept for head. + * + * @param accept such as: "{@value Headers#HEAD_VALUE_ACCEPT_APPLICATION_JSON}", "{@value Headers#HEAD_VALUE_ACCEPT_APPLICATION_XML}. + */ + void setAccept(String accept); + + /** + * Set the acceptLanguage for head. + * + * @param acceptLanguage such as "zh-CN,zh", "en-US,us". + */ + void setAcceptLanguage(String acceptLanguage); + + /** + * Set the contentType for head. + * + * @param contentType such as: "{@value Headers#HEAD_VALUE_ACCEPT_APPLICATION_JSON}", "{@value Headers#HEAD_VALUE_ACCEPT_APPLICATION_XML}" or "{@value Headers#HEAD_VALUE_ACCEPT_MULTIPART_FORM_DATA}". Note, does not need to include quotation marks. + */ + void setContentType(String contentType); + + /** + * Set the userAgent for head. + * + * @param userAgent such as: {@code Mozilla/5.0 (Android U; Android 5.0) AppleWebKit/533.1 (KHTML, like Gecko) Version/5.0 Safari/533.1}. + */ + void setUserAgent(String userAgent); + + /** + * Set the request fails retry count.The default value is 0, that is to say, after the failure will not go to this to initiate the request again. + * + * @param count the retry count, The default value is 0. + */ + void setRetryCount(int count); + + /** + * Set the params encoding. + * + * @param encoding such as {@code utf-8, gbk, gb2312}. + */ + void setParamsEncoding(String encoding); + + /** + * Add {@link Integer} param. + * + * @param key param name. + * @param value param value. + */ + void add(String key, int value); + + /** + * Add {@link Long} param. + * + * @param key param name. + * @param value param value. + */ + void add(String key, long value); + + /** + * Add {@link Boolean} param. + * + * @param key param name. + * @param value param value. + */ + void add(String key, boolean value); + + /** + * Add {@code char} param. + * + * @param key param name. + * @param value param value. + */ + void add(String key, char value); + + /** + * Add {@link Double} param. + * + * @param key param name. + * @param value param value. + */ + void add(String key, double value); + + /** + * Add {@link Float} param. + * + * @param key param name. + * @param value param value. + */ + void add(String key, float value); + + /** + * Add {@link Short} param. + * + * @param key param name. + * @param value param value. + */ + void add(String key, short value); + + /** + * Add {@link Byte} param. + * + * @param key param name. + * @param value param value, for example, the result is {@code 1} of {@code 0x01}. + */ + void add(String key, byte value); + + /** + * Add {@link String} param. + * + * @param key param name. + * @param value param value. + */ + void add(String key, String value); + + /** + * Add {@link String} param. + * + * @param key param name. + * @param value param value. + */ + void set(String key, String value); + + /** + * Add {@link Binary} param. + * + * @param key param name. + * @param binary param value. + */ + void add(String key, Binary binary); + + /** + * Set {@link Binary} param. + * + * @param key param name. + * @param binary param value. + */ + void set(String key, Binary binary); + + /** + * Add {@link File} param. + * + * @param key param name. + * @param file param value. + */ + void add(String key, File file); + + /** + * Set {@link File} param. + * + * @param key param name. + * @param file param value. + */ + void set(String key, File file); + + /** + * Add {@link Binary} param; + * + * @param key param name. + * @param binaries param value. + */ + void add(String key, List binaries); + + /** + * Set {@link Binary} param. + * + * @param key param name. + * @param binaries param value. + */ + void set(String key, List binaries); + + /** + * Add all param. + * + * @param params params {@link Map}. + */ + void add(Map params); + + /** + * Set all param. + * + * @param params params {@link Map}. + */ + void set(Map params); + + /** + * Remove a request param by key. + * + * @param key key + * @return The object is removed, if there are no returns null. + */ + List remove(String key); + + /** + * Remove all request param. + */ + void removeAll(); + + /** + * Get the parameters of key-value pairs. + * + * @return Not empty Map. + */ + MultiValueMap getParamKeyValues(); + + /** + * Settings you want to push data and contentType. Can only accept {@link java.io.ByteArrayInputStream} and {@link java.io.FileInputStream} type. + *

It is important to note that the request method must be {@link RequestMethod#PUT}, {@link RequestMethod#POST}, {@link RequestMethod#PATCH} in one of them.

+ * + * @param requestBody There can be a file, pictures, any other data flow.You don't need to close it, NoHttp when complete request will be automatically closed. + * @param contentType such as: "{@value Headers#HEAD_VALUE_ACCEPT_APPLICATION_XML}{@code ; charset=}{@value NoHttp#CHARSET_UTF8}", "{@value Headers#HEAD_VALUE_ACCEPT_APPLICATION_JSON}{@code ; charset=}{@value NoHttp#CHARSET_UTF8}" or "{@value Headers#HEAD_VALUE_ACCEPT_MULTIPART_FORM_DATA}". Note, does not need to include quotation marks. + * @see #setDefineRequestBody(String, String) + * @see #setDefineRequestBodyForJson(JSONObject) + * @see #setDefineRequestBodyForJson(String) + * @see #setDefineRequestBodyForXML(String) + */ + void setDefineRequestBody(InputStream requestBody, String contentType); + + + /** + * Sets the request body and content type. + *

It is important to note that the request method must be {@link RequestMethod#PUT}, {@link RequestMethod#POST}, {@link RequestMethod#PATCH} in one of them.

+ * + * @param requestBody string body. + * @param contentType such as: "{@value Headers#HEAD_VALUE_ACCEPT_APPLICATION_JSON}" or "{@value Headers#HEAD_VALUE_ACCEPT_APPLICATION_XML}". Note, does not need to include quotation marks. + *

If ContentType parameter into "" or null, the default for the {@value Headers#HEAD_VALUE_ACCEPT_APPLICATION_JSON}.

+ * @see #setDefineRequestBody(InputStream, String) + * @see #setDefineRequestBodyForJson(JSONObject) + * @see #setDefineRequestBodyForJson(String) + * @see #setDefineRequestBodyForXML(String) + */ + void setDefineRequestBody(String requestBody, String contentType); + + /** + * Set the request json body. + *

It is important to note that the request method must be {@link RequestMethod#PUT}, {@link RequestMethod#POST}, {@link RequestMethod#PATCH} in one of them.

+ *

The content type is {@value Headers#HEAD_VALUE_ACCEPT_APPLICATION_JSON}

+ * + * @param jsonBody json body. + * @see #setDefineRequestBody(InputStream, String) + * @see #setDefineRequestBody(String, String) + * @see #setDefineRequestBodyForJson(JSONObject) + * @see #setDefineRequestBodyForXML(String) + */ + void setDefineRequestBodyForJson(String jsonBody); + + /** + * Set the request json body. + *

It is important to note that the request method must be {@link RequestMethod#PUT}, {@link RequestMethod#POST}, {@link RequestMethod#PATCH} in one of them.

+ *

The content type is {@value Headers#HEAD_VALUE_ACCEPT_APPLICATION_JSON}

+ * + * @param jsonBody json body. + * @see #setDefineRequestBody(InputStream, String) + * @see #setDefineRequestBody(String, String) + * @see #setDefineRequestBodyForJson(String) + * @see #setDefineRequestBodyForXML(String) + */ + void setDefineRequestBodyForJson(JSONObject jsonBody); + + /** + * Set the request XML body. + *

It is important to note that the request method must be {@link RequestMethod#PUT}, {@link RequestMethod#POST}, {@link RequestMethod#PATCH} in one of them.

+ *

The content type is {@value Headers#HEAD_VALUE_ACCEPT_APPLICATION_XML}

+ * + * @param xmlBody xml body. + * @see #setDefineRequestBody(InputStream, String) + * @see #setDefineRequestBody(String, String) + * @see #setDefineRequestBody(String, String) + * @see #setDefineRequestBodyForJson(String) + */ + void setDefineRequestBodyForXML(String xmlBody); + + /** + * Sets redirect interface. + * + * @param redirectHandler {@link RedirectHandler}. + */ + void setRedirectHandler(RedirectHandler redirectHandler); + + /** + * Set tag of task, At the end of the task is returned to you. + * + * @param tag {@link Object}. + */ + void setTag(Object tag); + + /* + * ===================================================== + * || Server || + * ===================================================== + */ + + /** + * Return url of request. + * + * @return Url. + */ + String url(); + + /** + * return method of request. + * + * @return {@link RequestMethod}. + */ + RequestMethod getRequestMethod(); + + /** + * Is MultipartFormEnable ? + *

MultipartFormEnable is request method is the premise of the POST/PUT/PATCH/DELETE, but the Android system under API level 19 does not support the DELETE.

+ * + * @return true enable, other wise false. + */ + boolean isMultipartFormEnable(); + + /** + * Get proxy server. + * + * @return Can use {@code Proxy proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress("64.233.162.83", 80));}. + */ + Proxy getProxy(); + + /** + * Get SSLSocketFactory. + * + * @return {@link SSLSocketFactory}. + */ + SSLSocketFactory getSSLSocketFactory(); + + /** + * Get the HostnameVerifier. + * + * @return {@link HostnameVerifier}. + */ + HostnameVerifier getHostnameVerifier(); + + /** + * Get the connection timeout time, Unit is a millisecond. + * + * @return Connection timeout. + */ + int getConnectTimeout(); + + /** + * Get the read timeout time, Unit is a millisecond. + * + * @return Read timeout. + */ + int getReadTimeout(); + + /** + * Get all Heads. + * + * @return {@code Headers}. + */ + Headers headers(); + + /** + * To get the failure after retries. + * + * @return The default value is 0. + */ + int getRetryCount(); + + /** + * The length of the request body. + * + * @return Such as: {@code 2048}. + */ + long getContentLength(); + + /** + * Get {@value Headers#HEAD_KEY_CONTENT_TYPE}. + * + * @return string, such as: {@value Headers#HEAD_VALUE_ACCEPT_APPLICATION_JSON}. + */ + String getContentType(); + + /** + * Get the params encoding. + * + * @return such as {@code "utf-8, gbk, bg2312"}. + */ + String getParamsEncoding(); + + /** + * Call before carry out the request, you can do some preparation work. + */ + void onPreExecute(); + + /** + * Get the redirect handler. + * + * @return {@link RedirectHandler}. + */ + RedirectHandler getRedirectHandler(); + + /** + * Send request body data. + * + * @param writer {@link OutputStream}. + * @throws IOException write error. + */ + void onWriteRequestBody(OutputStream writer) throws IOException; + + /** + * Should to return the tag of the object. + * + * @return {@link Object}. + */ + Object getTag(); + +} diff --git a/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/IPriority.java b/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/IPriority.java new file mode 100644 index 0000000..2a960f1 --- /dev/null +++ b/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/IPriority.java @@ -0,0 +1,53 @@ +/* + * Copyright 2016 Yan Zhenjie + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yolanda.nohttp; + +/** + * Created on 2016/6/21. + * + * @author Yan Zhenjie. + */ +public interface IPriority { + + /** + * Set the priority of the request object. The default priority is {@link Priority#DEFAULT}. + * + * @param priority {@link Priority}. + */ + void setPriority(Priority priority); + + /** + * Get the priority of the request object. + * + * @return {@link Priority}. + */ + Priority getPriority(); + + /** + * Set the sequence in the queue, under the condition of two requests as priority, {@code left.sequence-right.sequence} decision to order. + * + * @param sequence sequence code. + */ + void setSequence(int sequence); + + /** + * Get the sequence in the queue, under the condition of two requests as priority, {@code left.sequence-right.sequence} decision to order. + * + * @return sequence code. + */ + int getSequence(); + +} diff --git a/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/InputStreamBinary.java b/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/InputStreamBinary.java new file mode 100644 index 0000000..298251b --- /dev/null +++ b/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/InputStreamBinary.java @@ -0,0 +1,86 @@ +/* + * Copyright 2015 Yan Zhenjie + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yolanda.nohttp; + +import android.util.Log; + +import com.yolanda.nohttp.tools.IOUtils; + +import java.io.ByteArrayInputStream; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; + +/** + *

+ * A default implementation of Binary. + * All the methods are called in Son thread. + *

+ * Created in Oct 17, 2015 12:40:54 PM. + * + * @author Yan Zhenjie. + */ +public class InputStreamBinary extends BasicBinary { + + protected InputStream inputStream; + + /** + * An input stream {@link Binary}. + * + * @param inputStream must be {@link FileInputStream}, {@link ByteArrayInputStream}. + * @param fileName file name. Had better pass this value, unless the server tube don't care about the file name. + */ + public InputStreamBinary(InputStream inputStream, String fileName) { + this(inputStream, fileName, null); + } + + /** + * An input stream {@link Binary}. + * + * @param inputStream must be {@link FileInputStream}, {@link ByteArrayInputStream}. + * @param fileName file name. Had better pass this value, unless the server tube don't care about the file name. + * @param mimeType content type. + */ + public InputStreamBinary(InputStream inputStream, String fileName, String mimeType) { + super(fileName, mimeType); + this.inputStream = inputStream; + if (!(inputStream instanceof FileInputStream) && !(inputStream instanceof ByteArrayInputStream)) { + Log.e("Binary", "Binary was cancelled, because the InputStream must be FileInputStream or ByteArrayInputStream."); + super.cancel(); + } + } + + @Override + public void cancel() { + IOUtils.closeQuietly(inputStream); + super.cancel(); + } + + @Override + public long getBinaryLength() { + try { + return inputStream.available(); + } catch (IOException e) { + Logger.e(e); + } + return 0; + } + + @Override + protected InputStream getInputStream() throws IOException { + return inputStream; + } +} \ No newline at end of file diff --git a/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/Logger.java b/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/Logger.java new file mode 100644 index 0000000..564e5ff --- /dev/null +++ b/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/Logger.java @@ -0,0 +1,251 @@ +/* + * Copyright 2015 Yan Zhenjie + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yolanda.nohttp; + +import java.lang.reflect.Method; + +/** + * Created in Jul 28, 2015 7:32:05 PM. + * + * @author Yan Zhenjie. + */ +public class Logger { + + private static final String V = "v"; + private static final String I = "i"; + private static final String D = "d"; + private static final String W = "w"; + private static final String E = "e"; + private static final String WTF = "wtf"; + + /** + * Library debug tag. + */ + private static String STag = "NoHttp"; + /** + * Library debug sign. + */ + private static boolean SDebug = false; + /** + * Default length. + */ + private static final int MAX_LENGTH = 4000; + /** + * Length of message. + */ + private static int maxLength = MAX_LENGTH; + + /** + * Set tag of log. + * + * @param tag tag. + */ + public static void setTag(String tag) { + STag = tag; + } + + /** + * Open debug mode of {@code NoHttp}. + * + * @param debug true open, false close. + */ + public static void setDebug(boolean debug) { + SDebug = debug; + } + + /** + * Set the length of the line of the Log, value is {@value MAX_LENGTH}. + * + * @param length length. + */ + public static void setMessageMaxLength(int length) { + maxLength = length; + } + + public static void i(String msg) { + print(I, msg); + } + + public static void i(Throwable e) { + i(e, ""); + } + + public static void i(Throwable e, String msg) { + print(I, msg, e); + } + + public static void v(String msg) { + print(V, msg); + } + + public static void v(Throwable e) { + v(e, ""); + } + + public static void v(Throwable e, String msg) { + print(V, msg, e); + } + + public static void d(String msg) { + print(D, msg); + } + + public static void d(Throwable e) { + d(e, ""); + } + + public static void d(Throwable e, String msg) { + print(D, msg, e); + } + + public static void e(String msg) { + print(E, msg); + } + + public static void e(Throwable e) { + e(e, ""); + } + + public static void e(Throwable e, String msg) { + print(E, msg, e); + } + + public static void w(String msg) { + print(W, msg); + } + + public static void w(Throwable e) { + w(e, ""); + } + + public static void w(Throwable e, String msg) { + print(W, msg, e); + } + + public static void wtf(String msg) { + print(WTF, msg); + } + + public static void wtf(Throwable e) { + wtf(e, ""); + } + + public static void wtf(Throwable e, String msg) { + print(WTF, msg, e); + } + + /** + * Print log for define method. When information is too long, the Logger can also complete printing. The equivalent of "{@code android.util.Log.i("Tag", "Message")}" "{@code com.yolanda.nohttp.Logger.print("i", "Tag", "Message")}". + * + * @param method such as "{@code v, i, d, w, e, wtf}". + * @param message message. + */ + private static void print(String method, String message) { + print(method, STag, message); + } + + /** + * Print log for define method. When information is too long, the Logger can also complete printing. The equivalent of "{@code android.util.Log.i("Tag", "Message")}" "{@code com.yolanda.nohttp.Logger.print("i", "Tag", "Message")}". + * + * @param method such as "{@code v, i, d, w, e, wtf}". + * @param tag tag. + * @param message message. + */ + public static void print(String method, String tag, String message) { + if (SDebug) { + if (message == null) + message = "null"; + int strLength = message.length(); + if (strLength == 0) + invokePrint(method, tag, message); + else { + for (int i = 0; i < strLength / maxLength + (strLength % maxLength > 0 ? 1 : 0); i++) { + int end = (i + 1) * maxLength; + if (strLength >= end) { + invokePrint(method, tag, message.substring(end - maxLength, end)); + } else { + invokePrint(method, tag, message.substring(end - maxLength)); + } + } + } + } + } + + /** + * Through the reflection to call the print method. + * + * @param method such as "{@code v, i, d, w, e, wtf}". + * @param tag tag. + * @param message message. + */ + private static void invokePrint(String method, String tag, String message) { + try { + Class logClass = android.util.Log.class; + Method logMethod = logClass.getMethod(method, String.class, String.class); + logMethod.setAccessible(true); + logMethod.invoke(null, tag, message); + } catch (Exception e) { + System.out.println(tag + ": " + message); + } + } + + /** + * Print log for define method. When information is too long, the Logger can also complete printing. The equivalent of "{@code android.util.Log.i("Tag", "Message")}" "{@code com.yolanda.nohttp.Logger.print("i", "Tag", "Message")}". + * + * @param method such as "{@code v, i, d, w, e, wtf}". + * @param message message. + * @param e error. + */ + private static void print(String method, String message, Throwable e) { + print(method, STag, message, e); + } + + /** + * Print log for define method. When information is too long, the Logger can also complete printing. The equivalent of "{@code android.util.Log.i("Tag", "Message")}" "{@code com.yolanda.nohttp.Logger.print("i", "Tag", "Message")}". + * + * @param method such as "{@code v, i, d, w, e, wtf}". + * @param tag tag. + * @param message message. + * @param e error. + */ + private static void print(String method, String tag, String message, Throwable e) { + if (SDebug) { + if (message == null) + message = "null"; + invokePrint(method, tag, message, e); + } + } + + /** + * Through the reflection to call the print method. + * + * @param method such as "{@code v, i, d, w, e, wtf}". + * @param tag tag. + * @param message message. + * @param e error. + */ + private static void invokePrint(String method, String tag, String message, Throwable e) { + try { + Class logClass = android.util.Log.class; + Method logMethod = logClass.getMethod(method, String.class, String.class, Throwable.class); + logMethod.setAccessible(true); + logMethod.invoke(null, tag, message, e); + } catch (Exception e1) { + System.out.println(tag + ": " + message); + } + } + +} \ No newline at end of file diff --git a/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/NoHttp.java b/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/NoHttp.java new file mode 100644 index 0000000..1615177 --- /dev/null +++ b/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/NoHttp.java @@ -0,0 +1,674 @@ +/* + * Copyright 2015 Yan Zhenjie + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yolanda.nohttp; + +import android.app.Application; +import android.graphics.Bitmap; +import android.os.Build; +import android.widget.ImageView; + +import com.yolanda.nohttp.cache.Cache; +import com.yolanda.nohttp.cache.CacheEntity; +import com.yolanda.nohttp.cache.DiskCacheStore; +import com.yolanda.nohttp.cookie.DiskCookieStore; +import com.yolanda.nohttp.download.DownloadConnection; +import com.yolanda.nohttp.download.DownloadQueue; +import com.yolanda.nohttp.download.DownloadRequest; +import com.yolanda.nohttp.download.Downloader; +import com.yolanda.nohttp.download.RestDownloadRequest; +import com.yolanda.nohttp.rest.IParserRequest; +import com.yolanda.nohttp.rest.IRestParser; +import com.yolanda.nohttp.rest.IRestProtocol; +import com.yolanda.nohttp.rest.ImageRequest; +import com.yolanda.nohttp.rest.JsonArrayRequest; +import com.yolanda.nohttp.rest.JsonObjectRequest; +import com.yolanda.nohttp.rest.Request; +import com.yolanda.nohttp.rest.RequestQueue; +import com.yolanda.nohttp.rest.Response; +import com.yolanda.nohttp.rest.RestParser; +import com.yolanda.nohttp.rest.RestProtocol; +import com.yolanda.nohttp.rest.StringRequest; +import com.yolanda.nohttp.tools.AndroidVersion; + +import org.json.JSONArray; +import org.json.JSONObject; + +import java.net.Authenticator; +import java.net.CookieHandler; +import java.net.CookieManager; +import java.net.CookiePolicy; +import java.net.PasswordAuthentication; + +/** + *

+ * NoHttp. + *

+ * Created in Jul 28, 2015 7:32:22 PM. + * + * @author Yan Zhenjie. + */ +public class NoHttp { + + /** + * The value is {@value}. + */ + public static final String CHARSET_UTF8 = "UTF-8"; + + /** + * RequestQueue default thread size, value is {@value}. + */ + private static int DEFAULT_REQUEST_THREAD_SIZE = 3; + + /** + * DownloadQueue default thread size, value is {@value}. + */ + private static int DEFAULT_DOWNLOAD_THREAD_SIZE = 3; + + /** + * Default connect timeout. The value is {@value}. + */ + private static int sDefaultConnectTimeout = 10 * 1000; + + /** + * Default read timeout. The value is {@value}. + */ + private static int sDefaultReadTimeout = 10 * 1000; + + /** + * Context. + */ + private static Application sApplication; + + /** + * Cookie. + */ + private static CookieManager sCookieManager; + /** + * Is enable cookies. + */ + private static boolean isEnableCookie = true; + + /** + * Create a new request queue, using NoHttp default configuration. And number of concurrent requests is 3. + * + * @return Returns the request queue, the queue is used to control the entry of the request. + * @see #newRequestQueue(int) + * @see #newRequestQueue(Cache, int) + * @see #newRequestQueue(IRestProtocol, int) + * @see #newRequestQueue(IRestParser, int) + */ + public static RequestQueue newRequestQueue() { + return newRequestQueue(DEFAULT_REQUEST_THREAD_SIZE); + } + + /** + * Create a new request queue, using NoHttp default configuration. + * + * @param threadPoolSize request the number of concurrent. + * @return Returns the request queue, the queue is used to control the entry of the request. + * @see #newRequestQueue() + * @see #newRequestQueue(Cache, int) + * @see #newRequestQueue(IRestProtocol, int) + * @see #newRequestQueue(IRestParser, int) + */ + public static RequestQueue newRequestQueue(int threadPoolSize) { + return newRequestQueue(DiskCacheStore.INSTANCE, threadPoolSize); + } + + /** + * Create a new request queue, using NoHttp default request connection {@link RestProtocol} and default response parser {@link RestParser}. + * + * @param cache cache interface, which is used to cache the request results. + * @param threadPoolSize request the number of concurrent. + * @return Returns the request queue, the queue is used to control the entry of the request. + * @see #newRequestQueue() + * @see #newRequestQueue(int) + * @see #newRequestQueue(IRestProtocol, int) + * @see #newRequestQueue(IRestParser, int) + */ + public static RequestQueue newRequestQueue(Cache cache, int threadPoolSize) { + return newRequestQueue(RestProtocol.getInstance(cache), threadPoolSize); + } + + /** + * Create a new request queue, using NoHttp default request executor {@link RestProtocol} and default response parser {@link RestParser}. + * + * @param iRestProtocol network operating interface, The implementation of the network layer. + * @param threadPoolSize request the number of concurrent. + * @return Returns the request queue, the queue is used to control the entry of the request. + * @see #newRequestQueue() + * @see #newRequestQueue(int) + * @see #newRequestQueue(Cache, int) + * @see #newRequestQueue(IRestParser, int) + */ + public static RequestQueue newRequestQueue(IRestProtocol iRestProtocol, int threadPoolSize) { + return newRequestQueue(RestParser.getInstance(iRestProtocol), threadPoolSize); + } + + /** + * Create a new request queue. + * + * @param implRestParser the response parser, The result of parsing the network layer. + * @param threadPoolSize request the number of concurrent. + * @return Returns the request queue, the queue is used to control the entry of the request. + * @see #newRequestQueue() + * @see #newRequestQueue(int) + * @see #newRequestQueue(Cache, int) + * @see #newRequestQueue(IRestProtocol, int) + */ + public static RequestQueue newRequestQueue(IRestParser implRestParser, int threadPoolSize) { + RequestQueue requestQueue = new RequestQueue(implRestParser, threadPoolSize); + requestQueue.start(); + return requestQueue; + } + + /** + * Create a String type request, the request method is {@link RequestMethod#GET}. + * + * @param url such as: {@code http://www.google.com}. + * @return {@code Request}. + * @see #createStringRequest(String, RequestMethod) + */ + public static Request createStringRequest(String url) { + return new StringRequest(url); + } + + /** + * Create a String type request, custom request method, method from {@link RequestMethod}. + * + * @param url such as: {@code http://www.google.com}. + * @param requestMethod {@link RequestMethod}. + * @return {@code Request}. + * @see #createStringRequest(String) + */ + public static Request createStringRequest(String url, RequestMethod requestMethod) { + return new StringRequest(url, requestMethod); + } + + /** + * Create a JSONObject type request, the request method is {@link RequestMethod#GET}. + * + * @param url such as: {@code http://www.google.com}. + * @return {@code Request}. + * @see #createJsonObjectRequest(String, RequestMethod) + */ + public static Request createJsonObjectRequest(String url) { + return new JsonObjectRequest(url); + } + + /** + * Create a JSONObject type request, custom request method, method from {@link RequestMethod}. + * + * @param url such as: {@code http://www.google.com}. + * @param requestMethod {@link RequestMethod}. + * @return {@code Request}. + * @see #createJsonObjectRequest(String) + */ + public static Request createJsonObjectRequest(String url, RequestMethod requestMethod) { + return new JsonObjectRequest(url, requestMethod); + } + + /** + * Create a JSONArray type request, the request method is {@link RequestMethod#GET}. + * + * @param url such as: {@code http://www.google.com}. + * @return {@code Request}. + * @see #createJsonArrayRequest(String, RequestMethod) + */ + public static Request createJsonArrayRequest(String url) { + return new JsonArrayRequest(url); + } + + /** + * Create a JSONArray type request, custom request method, method from {@link RequestMethod}. + * + * @param url such as: {@code http://www.google.com}. + * @param requestMethod {@link RequestMethod}. + * @return {@code Request}. + * @see #createJsonArrayRequest(String) + */ + public static Request createJsonArrayRequest(String url, RequestMethod requestMethod) { + return new JsonArrayRequest(url, requestMethod); + } + + /** + * Create a Image type request, the request method is {@link RequestMethod#GET}. + * + * @param url such as: {@code http://www.google.com}. + * @return {@code Request}. + * @see #createImageRequest(String, RequestMethod) + * @see #createImageRequest(String, RequestMethod, int, int, Bitmap.Config, ImageView.ScaleType) + */ + public static Request createImageRequest(String url) { + return createImageRequest(url, RequestMethod.GET); + } + + /** + * Create a Image type request. + * + * @param url such as: {@code http://www.google.com}. + * @param requestMethod {@link RequestMethod}. + * @return {@code Request}. + * @see #createImageRequest(String) + * @see #createImageRequest(String, RequestMethod, int, int, Bitmap.Config, ImageView.ScaleType) + */ + public static Request createImageRequest(String url, RequestMethod requestMethod) { + return createImageRequest(url, requestMethod, 1000, 1000, Bitmap.Config.ARGB_8888, ImageView.ScaleType.CENTER_INSIDE); + } + + /** + * Create a Image type request. + * + * @param url such as: {@code http://www.google.com}. + * @param requestMethod {@link RequestMethod}. + * @param maxWidth width. + * @param maxHeight height. + * @param config config. + * @param scaleType scaleType. + * @return {@code Request}. + * @see #createImageRequest(String) + * @see #createImageRequest(String, RequestMethod) + */ + public static Request createImageRequest(String url, RequestMethod requestMethod, int maxWidth, int maxHeight, Bitmap.Config config, ImageView.ScaleType scaleType) { + return new ImageRequest(url, requestMethod, maxWidth, maxHeight, config, scaleType); + } + + /** + * Initiate a synchronization request. + * + * @param request request object. + * @param {@link T}. + * @return {@link Response}. + * @see #startRequestSync(Cache, IParserRequest) + * @see #startRequestSync(IRestProtocol, IParserRequest) + * @see #startRequestSync(IRestParser, IParserRequest) + */ + public static Response startRequestSync(IParserRequest request) { + return startRequestSync(DiskCacheStore.INSTANCE, request); + } + + /** + * Initiate a synchronization request. + * + * @param cache cache interface, which is used to cache the request results. + * @param request tequest object. + * @param {@link T}. + * @return {@link Response}. + * @see #startRequestSync(IParserRequest) + * @see #startRequestSync(IRestProtocol, IParserRequest) + * @see #startRequestSync(IRestParser, IParserRequest) + */ + public static Response startRequestSync(Cache cache, IParserRequest request) { + return startRequestSync(RestProtocol.getInstance(cache), request); + } + + /** + * Initiate a synchronization request. + * + * @param implRestConnection complete implementation of the {@link IRestProtocol}. + * @param request request object. + * @param {@link T}. + * @return {@link Response}. + * @see #startRequestSync(IParserRequest) + * @see #startRequestSync(Cache, IParserRequest) + * @see #startRequestSync(IRestParser, IParserRequest) + */ + public static Response startRequestSync(IRestProtocol implRestConnection, IParserRequest request) { + return startRequestSync(RestParser.getInstance(implRestConnection), request); + } + + /** + * Initiate a synchronization request. + * + * @param implRestParser complete implementation of the {@link IRestParser}. + * @param request request object. + * @param {@link T}. + * @return {@link Response}. + * @see #startRequestSync(IParserRequest) + * @see #startRequestSync(Cache, IParserRequest) + * @see #startRequestSync(IRestProtocol, IParserRequest) + */ + public static Response startRequestSync(IRestParser implRestParser, IParserRequest request) { + return implRestParser.parserRequest(request); + } + + /** + * Create a new download queue, the default thread pool number is 3. + * + * @return {@link DownloadQueue}. + * @see #newDownloadQueue(int) + * @see #newDownloadQueue(Downloader, int) + */ + public static DownloadQueue newDownloadQueue() { + return newDownloadQueue(DEFAULT_DOWNLOAD_THREAD_SIZE); + } + + /** + * Create a new download queue. + * + * @param threadPoolSize thread pool number, here is the number of concurrent tasks. + * @return {@link DownloadQueue}. + * @see #newDownloadQueue() + * @see #newDownloadQueue(Downloader, int) + */ + public static DownloadQueue newDownloadQueue(int threadPoolSize) { + return newDownloadQueue(new DownloadConnection(), threadPoolSize); + } + + /** + * Create a new download queue. + * + * @param downloader {@link Downloader}. + * @param threadPoolSize number of concurrent. + * @return {@link DownloadQueue} + * @see #newDownloadQueue() + * @see #newDownloadQueue(int) + */ + public static DownloadQueue newDownloadQueue(Downloader downloader, int threadPoolSize) { + DownloadQueue downloadQueue = new DownloadQueue(downloader, threadPoolSize); + downloadQueue.start(); + return downloadQueue; + } + + /** + * Create a download object, auto named file. The request method is {@link RequestMethod#GET}. + * + * @param url download address. + * @param fileFolder folder to save file. + * @param isDeleteOld find the same when the file is deleted after download, or on behalf of the download is complete, not to request the network. + * @return {@link DownloadRequest}. + * @see #createDownloadRequest(String, RequestMethod, String, String, boolean, boolean) + */ + public static DownloadRequest createDownloadRequest(String url, String fileFolder, boolean isDeleteOld) { + return createDownloadRequest(url, RequestMethod.GET, fileFolder, isDeleteOld); + } + + /** + * Create a download object, auto named file. + * + * @param url download address. + * @param requestMethod {@link RequestMethod}. + * @param fileFolder folder to save file. + * @param isDeleteOld find the same when the file is deleted after download, or on behalf of the download is complete, not to request the network. + * @return {@link DownloadRequest}. + * @see #createDownloadRequest(String, RequestMethod, String, String, boolean, boolean) + */ + public static DownloadRequest createDownloadRequest(String url, RequestMethod requestMethod, String fileFolder, boolean isDeleteOld) { + return new RestDownloadRequest(url, requestMethod, fileFolder, isDeleteOld); + } + + /** + * Create a download object. The request method is {@link RequestMethod#GET}. + * + * @param url download address. + * @param fileFolder folder to save file. + * @param filename filename. + * @param isRange whether the breakpoint continuing. + * @param isDeleteOld find the same when the file is deleted after download, or on behalf of the download is complete, not to request the network. + * @return {@link DownloadRequest}. + * @see #createDownloadRequest(String, RequestMethod, String, String, boolean, boolean) + */ + public static DownloadRequest createDownloadRequest(String url, String fileFolder, String filename, boolean isRange, boolean isDeleteOld) { + return createDownloadRequest(url, RequestMethod.GET, fileFolder, filename, isRange, isDeleteOld); + } + + /** + * Create a download object. + * + * @param url download address. + * @param requestMethod {@link RequestMethod}. + * @param fileFolder folder to save file. + * @param filename filename. + * @param isRange whether the breakpoint continuing. + * @param isDeleteOld find the same when the file is deleted after download, or on behalf of the download is complete, not to request the network. + * @return {@link DownloadRequest}. + * @see #createDownloadRequest(String, String, String, boolean, boolean) + */ + public static DownloadRequest createDownloadRequest(String url, RequestMethod requestMethod, String fileFolder, String filename, boolean isRange, boolean isDeleteOld) { + return new RestDownloadRequest(url, requestMethod, fileFolder, filename, isRange, isDeleteOld); + } + + /** + * Get version name of NoHttp. + * + * @return {@link String}. + */ + public static String versionName() { + return "1.0.5"; + } + + /** + * Get version code of NoHttp. + * + * @return {@link Integer}. + */ + public static int versionCode() { + return 105; + } + + /** + * Initialization NoHttp, Should invoke on {@link Application#onCreate()}. + * + * @param application {@link Application}. + * @deprecated use {@link #initialize(Application)} instead. + */ + @Deprecated + public static void init(Application application) { + initialize(application); + } + + /** + * Initialization NoHttp, Should invoke on {@link Application#onCreate()}. + * + * @param application {@link Application}. + */ + public static void initialize(Application application) { + if (sApplication == null) { + sApplication = application; + sCookieManager = new CookieManager(DiskCookieStore.INSTANCE, CookiePolicy.ACCEPT_ALL); + + if (Build.VERSION.SDK_INT < AndroidVersion.KITKAT) { + System.setProperty("http.keepAlive", "false"); + System.setProperty("http.maxConnections", String.valueOf(5)); + } + } + } + + /** + * Get application of app. + * + * @return {@link Application}. + */ + public static Application getContext() { + if (sApplication == null) + throw new ExceptionInInitializerError("Please invoke NoHttp.initialize(Application) on Application#onCreate()"); + return sApplication; + } + + /** + * It will be called whenever the realm that the URL is pointing to requires authorization. + * + * @param passwordAuthentication passwordAuthentication which has to be set as default. + */ + public static void setDefaultAuthenticator(final PasswordAuthentication passwordAuthentication) { + Authenticator.setDefault(new Authenticator() { + @Override + protected PasswordAuthentication getPasswordAuthentication() { + return passwordAuthentication; + } + }); + } + + /** + * Set default connect timeout. + * + * @param timeout ms. + */ + public static void setDefaultConnectTimeout(int timeout) { + sDefaultConnectTimeout = timeout; + } + + /** + * Get default connect timeout. + * + * @return ms. + */ + public static int getDefaultConnectTimeout() { + return sDefaultConnectTimeout; + } + + /** + * Set default read timeout. + * + * @param timeout ms. + */ + public static void setDefaultReadTimeout(int timeout) { + sDefaultReadTimeout = timeout; + } + + /** + * Get default read timeout. + * + * @return ms. + */ + public static int getDefaultReadTimeout() { + return sDefaultReadTimeout; + } + + /** + * Set default request thread pool size. + * + * @param size count. + */ + public static void setDefaultRequestThreadSize(int size) { + DEFAULT_REQUEST_THREAD_SIZE = size; + } + + /** + * Get default request thread pool size. + * + * @return count. + */ + public static int getDefaultRequestThreadSize() { + return DEFAULT_REQUEST_THREAD_SIZE; + } + + /** + * Set default download thread pool size. + * + * @param size count. + */ + public static void setDefaultDownloadThreadSize(int size) { + DEFAULT_DOWNLOAD_THREAD_SIZE = size; + } + + /** + * Get default donwload thread pool size. + * + * @return count. + */ + public static int getDefaultDownloadThreadSize() { + return DEFAULT_DOWNLOAD_THREAD_SIZE; + } + + /** + * Set to enable cookies. + * + * @param enableCookie ture enable, false disenable. + */ + public static void setEnableCookie(boolean enableCookie) { + isEnableCookie = enableCookie; + } + + /** + * Get NoHttp Cookie manager by default. + * + * @return {@link CookieHandler}. + * @see #setDefaultCookieManager(CookieManager) + */ + public static CookieManager getDefaultCookieManager() { + return sCookieManager; + } + + /** + * Sets the system-wide cookie handler. + * + * @param cookieHandler {@link CookieHandler}. + * @see #getDefaultCookieManager() + */ + public static void setDefaultCookieManager(CookieManager cookieHandler) { + if (cookieHandler == null) + throw new IllegalArgumentException("cookieHandler == null"); + sCookieManager = cookieHandler; + } + + /** + * Is enable cookie. + * + * @return true enable, false disenable. + */ + public static boolean isEnableCookie() { + return isEnableCookie; + } + + private NoHttp() { + } + + /* + * ================================================= + * || Instance || + * ================================================= + */ + + /** + * Default thread pool size for request queue. + */ + private static RequestQueue sRequestQueueInstance; + + /** + * Default thread pool size for request queue. + */ + private static DownloadQueue sDownloadQueueInstance; + + /** + * Get default RequestQueue. + * + * @return {@link RequestQueue}. + */ + public static RequestQueue getRequestQueueInstance() { + synchronized (NoHttp.class) { + if (sRequestQueueInstance == null) { + sRequestQueueInstance = newRequestQueue(); + } + } + return sRequestQueueInstance; + } + + /** + * Get default DownloadQueue. + * + * @return {@link DownloadQueue}. + */ + public static DownloadQueue getDownloadQueueInstance() { + synchronized (NoHttp.class) { + if (sDownloadQueueInstance == null) { + sDownloadQueueInstance = newDownloadQueue(); + } + } + return sDownloadQueueInstance; + } + +} diff --git a/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/OnUploadListener.java b/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/OnUploadListener.java new file mode 100644 index 0000000..c6d61aa --- /dev/null +++ b/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/OnUploadListener.java @@ -0,0 +1,66 @@ +/* + * Copyright 2015 Yan Zhenjie + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yolanda.nohttp; + +/** + * Created in Jan 29, 2016 10:56:37 AM. + * + * @author Yan Zhenjie. + */ +public interface OnUploadListener { + + /** + * At the start of the upload is invoked. + * + * @param what what of {@link FileBinary#setUploadListener(int, OnUploadListener)}. + * @see FileBinary#setUploadListener(int, OnUploadListener) + */ + void onStart(int what); + + /** + * Called when the upload was cancelled. + * + * @param what what of {@link FileBinary#setUploadListener(int, OnUploadListener)}. + * @see FileBinary#setUploadListener(int, OnUploadListener) + */ + void onCancel(int what); + + /** + * Invoked when the upload progress changes. + * + * @param what what of {@link FileBinary#setUploadListener(int, OnUploadListener)}. + * @param progress progress + * @see FileBinary#setUploadListener(int, OnUploadListener) + */ + void onProgress(int what, int progress); + + /** + * Upload is complete is invoked. + * + * @param what what of {@link FileBinary#setUploadListener(int, OnUploadListener)}. + * @see FileBinary#setUploadListener(int, OnUploadListener) + */ + void onFinish(int what); + + /** + * Upload error is called. + * + * @param what what of {@link FileBinary#setUploadListener(int, OnUploadListener)}. * @param exception upload is called when an error occurs. + * @param exception error type. + * @see BasicBinary#setUploadListener(int, OnUploadListener) + */ + void onError(int what, Exception exception); +} diff --git a/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/PosterHandler.java b/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/PosterHandler.java new file mode 100644 index 0000000..13e22ce --- /dev/null +++ b/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/PosterHandler.java @@ -0,0 +1,43 @@ +/* + * Copyright © Yan Zhenjie. All Rights Reserved + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yolanda.nohttp; + +import android.os.Handler; +import android.os.Looper; + +/** + *

Poster.

+ * Created on 2016/6/7. + * + * @author Yan Zhenjie. + */ +public final class PosterHandler extends Handler { + + private static PosterHandler instance; + + public static PosterHandler getInstance() { + synchronized (PosterHandler.class) { + if (instance == null) { + instance = new PosterHandler(); + } + } + return instance; + } + + private PosterHandler() { + super(Looper.getMainLooper()); + } +} diff --git a/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/Priority.java b/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/Priority.java new file mode 100644 index 0000000..d2c411b --- /dev/null +++ b/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/Priority.java @@ -0,0 +1,46 @@ +/* + * Copyright 2015 Yan Zhenjie + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yolanda.nohttp; + +/** + *

+ * Request priority. + *

+ * Created in Mar 20, 2016 11:40:03 PM. + * + * @author Yan Zhenjie. + */ +public enum Priority { + /** + * Low. + */ + LOW, + + /** + * Default. + */ + DEFAULT, + + /** + * Height. + */ + HEIGHT, + + /** + * The highest. + */ + HIGHEST +} diff --git a/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/RedirectHandler.java b/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/RedirectHandler.java new file mode 100644 index 0000000..3056d56 --- /dev/null +++ b/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/RedirectHandler.java @@ -0,0 +1,41 @@ +/* + * Copyright 2015 Yan Zhenjie + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yolanda.nohttp; + +/** + * Created in Jan 31, 2016 8:45:37 PM. + * + * @author Yan Zhenjie. + */ +public interface RedirectHandler { + + /** + * When the server's response code is 302 or 303 corresponding need to redirect is invoked. + * + * @param responseHeaders the service side head accordingly. + * @return {@link IBasicRequest}. + */ + IBasicRequest onRedirect(Headers responseHeaders); + + /** + * Whether to allow the redirection, if not redirect will not be {@code #onRedirect(Headers)} callback method, at the same time will ban NoHttp automatic redirection.If allowed to redirect, first + * call {@code #onRedirect(Headers)} method, if {@code #onRedirect(Headers)} method returns null, execute NoHttp default redirect. + * + * @param responseHeaders the service side head accordingly. + * @return returns true said allow redirection, returns false said do not allow the redirection. + */ + boolean isDisallowedRedirect(Headers responseHeaders); +} diff --git a/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/RequestMethod.java b/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/RequestMethod.java new file mode 100644 index 0000000..ff128e3 --- /dev/null +++ b/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/RequestMethod.java @@ -0,0 +1,80 @@ +/* + * Copyright 2015 Yan Zhenjie + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yolanda.nohttp; + +import android.os.Build; + +import com.yolanda.nohttp.tools.AndroidVersion; + +/** + *

+ * HTTP request method. + *

+ * Created in Oct 10, 2015 8:00:48 PM. + * + * @author Yan Zhenjie. + */ +public enum RequestMethod { + + GET("GET"), + + POST("POST"), + + PUT("PUT"), + + MOVE("MOVE"), + + COPY("COPY"), + + DELETE("DELETE"), + + HEAD("HEAD"), + + PATCH("PATCH"), + + OPTIONS("OPTIONS"), + + TRACE("TRACE"), + + CONNECT("CONNECT"); + + private final String value; + + RequestMethod(String value) { + this.value = value; + } + + @Override + public String toString() { + return this.value; + } + + public boolean allowRequestBody() { + boolean allowRequestBody = false; + switch (this) { + case POST: + case PUT: + case PATCH: + case DELETE: + allowRequestBody = true; + break; + } + if (Build.VERSION.SDK_INT < AndroidVersion.LOLLIPOP) + return allowRequestBody && this != DELETE; + return allowRequestBody; + } + +} diff --git a/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/SimpleUploadListener.java b/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/SimpleUploadListener.java new file mode 100644 index 0000000..7b6861b --- /dev/null +++ b/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/SimpleUploadListener.java @@ -0,0 +1,43 @@ +/* + * Copyright © Yan Zhenjie. All Rights Reserved + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yolanda.nohttp; + +/** + * Created on 2016/6/7. + * + * @author Yan Zhenjie. + */ +public abstract class SimpleUploadListener implements OnUploadListener { + @Override + public void onStart(int what) { + } + + @Override + public void onProgress(int what, int progress) { + } + + @Override + public void onCancel(int what) { + } + + @Override + public void onFinish(int what) { + } + + @Override + public void onError(int what, Exception exception) { + } +} diff --git a/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/UserAgent.java b/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/UserAgent.java new file mode 100644 index 0000000..3bc2a9b --- /dev/null +++ b/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/UserAgent.java @@ -0,0 +1,106 @@ +/* + * Copyright 2015 Yan Zhenjie + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yolanda.nohttp; + +import android.os.Build; +import android.text.TextUtils; + +import java.lang.reflect.Field; +import java.util.Locale; + +/** + * Created in Oct 15, 2015 12:39:06 PM. + * + * @author Yan Zhenjie. + */ +public class UserAgent { + + /** + * UserAgent. + */ + private static String userAgent; + + /** + * Get the singleton UA. + * + * @return String. + * @see #newInstance() + */ + public static String instance() { + if (TextUtils.isEmpty(userAgent)) + userAgent = newInstance(); + return userAgent; + } + + /** + * Get User-Agent of System. + * + * @return UA. + */ + public static String newInstance() { + String webUserAgent = null; + try { + Class sysResCls = Class.forName("com.android.internal.R$string"); + Field webUserAgentField = sysResCls.getDeclaredField("web_user_agent"); + Integer resId = (Integer) webUserAgentField.get(null); + webUserAgent = NoHttp.getContext().getString(resId); + } catch (Exception e) { + // We have nothing to do + } + if (TextUtils.isEmpty(webUserAgent)) { + webUserAgent = "Mozilla/5.0 (Linux; U; Android %s) AppleWebKit/533.1 (KHTML, like Gecko) Version/5.0 %sSafari/533.1"; + } + + Locale locale = Locale.getDefault(); + StringBuffer buffer = new StringBuffer(); + // Add version + final String version = Build.VERSION.RELEASE; + if (version.length() > 0) { + buffer.append(version); + } else { + // default to "1.0" + buffer.append("1.0"); + } + buffer.append("; "); + final String language = locale.getLanguage(); + if (language != null) { + buffer.append(language.toLowerCase(locale)); + final String country = locale.getCountry(); + if (!TextUtils.isEmpty(country)) { + buffer.append("-"); + buffer.append(country.toLowerCase(locale)); + } + } else { + // default to "en" + buffer.append("en"); + } + // add the model for the release build + if ("REL".equals(Build.VERSION.CODENAME)) { + final String model = Build.MODEL; + if (model.length() > 0) { + buffer.append("; "); + buffer.append(model); + } + } + final String id = Build.ID; + if (id.length() > 0) { + buffer.append(" Build/"); + buffer.append(id); + } + return String.format(webUserAgent, buffer, "Mobile "); + } + +} diff --git a/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/able/Cancelable.java b/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/able/Cancelable.java new file mode 100644 index 0000000..745a4a7 --- /dev/null +++ b/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/able/Cancelable.java @@ -0,0 +1,38 @@ +/* + * Copyright 2015 Yan Zhenjie + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yolanda.nohttp.able; + +/** + *

Cancel interface.

+ * Created in Dec 17, 2015 11:42:10 AM. + * + * @author Yan Zhenjie; + */ +public interface Cancelable { + + /** + * Cancel handle. + */ + void cancel(); + + /** + * Whether has been cancelled. + * + * @return true: canceled, false: there is no cancel. + */ + boolean isCanceled(); + +} diff --git a/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/able/Finishable.java b/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/able/Finishable.java new file mode 100644 index 0000000..bef6aad --- /dev/null +++ b/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/able/Finishable.java @@ -0,0 +1,37 @@ +/* + * Copyright 2015 Yan Zhenjie + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yolanda.nohttp.able; + +/** + *

Finish interface.

+ * Created in Jan 13, 2016 10:34:48 PM. + * + * @author Yan Zhenjie; + */ +public interface Finishable { + + /** + * Finish handle. + */ + void finish(); + + /** + * Whether they have been completed. + * + * @return true: finished, false: unfinished. + */ + boolean isFinished(); +} diff --git a/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/able/Queueable.java b/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/able/Queueable.java new file mode 100644 index 0000000..f083aef --- /dev/null +++ b/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/able/Queueable.java @@ -0,0 +1,42 @@ +/* + * Copyright 2015 Yan Zhenjie + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yolanda.nohttp.able; + +import java.util.concurrent.BlockingQueue; + +/** + *

Queue interface.

+ * Created in Nov 12, 2015 5:59:29 PM. + * + * @author Yan Zhenjie; + */ +public interface Queueable { + + /** + * Set the request in the queue. + * + * @param queue queue. + */ + void setQueue(BlockingQueue queue); + + /** + * In the queue? + * + * @return true: in the queue, false: not in the queue. + */ + boolean inQueue(); + +} diff --git a/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/able/SignCancelable.java b/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/able/SignCancelable.java new file mode 100644 index 0000000..2d2943f --- /dev/null +++ b/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/able/SignCancelable.java @@ -0,0 +1,40 @@ +/* + * Copyright 2015 Yan Zhenjie + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yolanda.nohttp.able; + +/** + *

According to the sign cancel interface.

+ * Created in Nov 12, 2015 5:11:56 PM. + * + * @author Yan Zhenjie; + */ +public interface SignCancelable { + + /** + * Cancel operation by contrast the sign. + * + * @param sign an object that can be null. + */ + void cancelBySign(Object sign); + + /** + * Set cancel sign. + * + * @param object a object. + */ + void setCancelSign(Object object); + +} diff --git a/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/able/Startable.java b/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/able/Startable.java new file mode 100644 index 0000000..131d205 --- /dev/null +++ b/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/able/Startable.java @@ -0,0 +1,37 @@ +/* + * Copyright 2015 Yan Zhenjie + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yolanda.nohttp.able; + +/** + *

Start interface.

+ * Created in Nov 12, 2015 5:03:54 PM. + * + * @author Yan Zhenjie; + */ +public interface Startable { + + /** + * Start handle. + */ + void start(); + + /** + * Whether has already begun. + * + * @return true: has already started, false: haven't started. + */ + boolean isStarted(); +} diff --git a/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/cache/Cache.java b/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/cache/Cache.java new file mode 100644 index 0000000..4444f39 --- /dev/null +++ b/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/cache/Cache.java @@ -0,0 +1,57 @@ +/* + * Copyright 2015 Yan Zhenjie + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yolanda.nohttp.cache; + +/** + *

Cache interface.

+ * Created in Dec 14, 2015 5:52:41 PM. + * + * @author Yan Zhenjie; + */ +public interface Cache { + + /** + * According to the key to get the cache data. + * + * @param key unique key. + * @return cache data. + */ + T get(String key); + + /** + * According to the key to replace or save the data. + * + * @param key unique key. + * @param data cache data. + * @return cache data. + */ + T replace(String key, T data); + + /** + * According to the key to remove the data. + * + * @param key unique. + * @return cache data. + */ + boolean remove(String key); + + /** + * Clear all data. + * + * @return return to true to clear the failure when the false is cleared. + */ + boolean clear(); +} diff --git a/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/cache/CacheDisk.java b/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/cache/CacheDisk.java new file mode 100644 index 0000000..7ce13e5 --- /dev/null +++ b/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/cache/CacheDisk.java @@ -0,0 +1,81 @@ +/* + * Copyright 2015 Yan Zhenjie + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yolanda.nohttp.cache; + +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteOpenHelper; + +import com.yolanda.nohttp.NoHttp; +import com.yolanda.nohttp.db.Field; + +/** + *

Cache database operation class.

+ * Created in Jan 10, 2016 12:39:15 AM. + * + * @author Yan Zhenjie; + */ +class CacheDisk extends SQLiteOpenHelper implements Field { + + public static final String DB_CACHE_NAME = "_nohttp_cache_db.db"; + public static final int DB_CACHE_VERSION = 2; + + public static final String TABLE_NAME = "cache_table"; + public static final String KEY = "key"; + public static final String HEAD = "head"; + public static final String DATA = "data"; + public static final String LOCAL_EXPIRES = "local_expires"; + + private static final String SQL_CREATE_TABLE = "CREATE TABLE cache_table(_id INTEGER PRIMARY KEY AUTOINCREMENT, key TEXT, head TEXT, data BLOB, local_expires INTEGER)"; + private static final String SQL_CREATE_UNIQUE_INDEX = "CREATE UNIQUE INDEX cache_unique_index ON cache_table(\"key\")"; + private static final String SQL_DELETE_TABLE = "DROP TABLE IF EXISTS cache_table"; + private static final String SQL_DELETE_UNIQUE_INDEX = "DROP INDEX IF EXISTS cache_unique_index"; + + public CacheDisk() { + super(NoHttp.getContext(), DB_CACHE_NAME, null, DB_CACHE_VERSION); + } + + @Override + public void onCreate(SQLiteDatabase db) { + db.beginTransaction(); + try { + db.execSQL(SQL_CREATE_TABLE); + db.execSQL(SQL_CREATE_UNIQUE_INDEX); + db.setTransactionSuccessful(); + } finally { + db.endTransaction(); + } + } + + @Override + public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { + if (newVersion != oldVersion) { + db.beginTransaction(); + try { + db.execSQL(SQL_DELETE_TABLE); + db.execSQL(SQL_DELETE_UNIQUE_INDEX); + db.execSQL(SQL_CREATE_TABLE); + db.execSQL(SQL_CREATE_UNIQUE_INDEX); + db.setTransactionSuccessful(); + } finally { + db.endTransaction(); + } + } + } + + public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) { + onUpgrade(db, oldVersion, newVersion); + } +} diff --git a/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/cache/CacheDiskManager.java b/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/cache/CacheDiskManager.java new file mode 100644 index 0000000..1084ff8 --- /dev/null +++ b/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/cache/CacheDiskManager.java @@ -0,0 +1,115 @@ +/* + * Copyright 2015 Yan Zhenjie + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yolanda.nohttp.cache; + +import android.content.ContentValues; +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; + +import com.yolanda.nohttp.Logger; +import com.yolanda.nohttp.db.DBManager; + +import java.util.ArrayList; +import java.util.List; + +/** + *

Cache database manager.

+ * Created in Jan 10, 2016 12:42:29 AM. + * + * @author Yan Zhenjie; + */ +class CacheDiskManager extends DBManager { + + private static DBManager _Instance; + + private CacheDiskManager() { + super(new CacheDisk()); + } + + public synchronized static DBManager getInstance() { + if (_Instance == null) { + _Instance = new CacheDiskManager(); + } + return _Instance; + } + + @Override + public long replace(CacheEntity cacheEntity) { + SQLiteDatabase execute = getWriter(); + ContentValues values = new ContentValues(); + values.put(CacheDisk.KEY, cacheEntity.getKey()); + values.put(CacheDisk.HEAD, cacheEntity.getResponseHeadersJson()); + values.put(CacheDisk.DATA, cacheEntity.getData()); + values.put(CacheDisk.LOCAL_EXPIRES, cacheEntity.getLocalExpire()); + long id = -1; + try { + id = execute.replace(getTableName(), null, values); + } catch (Throwable e) { + Logger.e(e); + } + closeWriter(execute); + return id; + } + + @Override + public List get(String querySql) { + SQLiteDatabase execute = getReader(); + + List cacheEntities = new ArrayList(); + Cursor cursor = null; + try { + cursor = execute.rawQuery(querySql, null); + while (!cursor.isClosed() && cursor.moveToNext()) { + try { + CacheEntity cacheEntity = new CacheEntity(); + int idIndex = cursor.getColumnIndex(CacheEntity.ID); + if (idIndex >= 0) + cacheEntity.setId(cursor.getInt(idIndex)); + + int keyIndex = cursor.getColumnIndex(CacheDisk.KEY); + if (keyIndex >= 0) + cacheEntity.setKey(cursor.getString(keyIndex)); + + int headIndex = cursor.getColumnIndex(CacheDisk.HEAD); + if (headIndex >= 0) + cacheEntity.setResponseHeadersJson(cursor.getString(headIndex)); + + int dataIndex = cursor.getColumnIndex(CacheDisk.DATA); + if (dataIndex >= 0) + cacheEntity.setData(cursor.getBlob(dataIndex)); + + int expiresIndex = cursor.getColumnIndex(CacheDisk.LOCAL_EXPIRES); + if (expiresIndex >= 0) + cacheEntity.setLocalExpire(cursor.getLong(expiresIndex)); + + cacheEntities.add(cacheEntity); + } catch (Throwable e) { + Logger.w(e); + } + } + } catch (Throwable e) { + Logger.e(e); + } + closeReader(execute, cursor); + return cacheEntities; + } + + @Override + protected String getTableName() { + return CacheDisk.TABLE_NAME; + } + +} diff --git a/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/cache/CacheEntity.java b/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/cache/CacheEntity.java new file mode 100644 index 0000000..f4b5fd5 --- /dev/null +++ b/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/cache/CacheEntity.java @@ -0,0 +1,170 @@ +/* + * Copyright 2015 Yan Zhenjie + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yolanda.nohttp.cache; + +import com.yolanda.nohttp.Headers; +import com.yolanda.nohttp.HttpHeaders; +import com.yolanda.nohttp.Logger; +import com.yolanda.nohttp.db.DBId; +import com.yolanda.nohttp.db.Field; + +import org.json.JSONException; + +import java.io.Serializable; + +/** + *

Cache entity class.

+ * Created in Jan 10, 2016 12:43:10 AM. + * + * @author Yan Zhenjie; + */ +public class CacheEntity implements DBId, Field, Serializable { + + private static final long serialVersionUID = 12348534793L; + + private long id; + + /** + * The cache key. + */ + private String key; + /** + * The server response headers. + */ + private Headers responseHeaders = new HttpHeaders(); + + /** + * Cache data. + */ + private byte[] data = {}; + + /** + * Cached in the local expiration time. + */ + private long localExpire; + + public CacheEntity() { + } + + /** + * @param id id. + * @param key key. + * @param responseHeaders http response headers. + * @param data http response data. + * @param localExpire local expire time. + */ + public CacheEntity(long id, String key, Headers responseHeaders, byte[] data, long localExpire) { + this.id = id; + this.key = key; + this.responseHeaders = responseHeaders; + this.data = data; + this.localExpire = localExpire; + } + + /** + * @return the id. + */ + @Override + public long getId() { + return id; + } + + /** + * @param id the id to set. + */ + public void setId(long id) { + this.id = id; + } + + /** + * @return the key. + */ + public String getKey() { + return key; + } + + /** + * @param key the key to set. + */ + public void setKey(String key) { + this.key = key; + } + + /** + * @return the responseHeaders. + */ + public Headers getResponseHeaders() { + return responseHeaders; + } + + /** + * @param responseHeaders the responseHeaders to set. + */ + public void setResponseHeaders(Headers responseHeaders) { + this.responseHeaders = responseHeaders; + } + + /** + * Set the {@link Headers#setJSONString(String)} can Parse the json data, format conforms to the corresponding Http header format. + * + * @param jsonString conform to the relevant head of the Json data format. + */ + public void setResponseHeadersJson(String jsonString) { + try { + this.responseHeaders.setJSONString(jsonString); + } catch (JSONException e) { + Logger.e(e); + } + } + + /** + * To get the json data format of the head. + * + * @return json. + */ + public String getResponseHeadersJson() { + return this.responseHeaders.toJSONString(); + } + + /** + * @return the data. + */ + public byte[] getData() { + return data; + } + + /** + * @param data the data to set. + */ + public void setData(byte[] data) { + this.data = data; + } + + /** + * @return the localExpire. + */ + public long getLocalExpire() { + return localExpire; + } + + /** + * @param localExpire the localExpire to set. + */ + public void setLocalExpire(long localExpire) { + this.localExpire = localExpire; + } + +} diff --git a/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/cache/DiskCacheStore.java b/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/cache/DiskCacheStore.java new file mode 100644 index 0000000..83c5a46 --- /dev/null +++ b/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/cache/DiskCacheStore.java @@ -0,0 +1,97 @@ +/* + * Copyright 2015 Yan Zhenjie + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yolanda.nohttp.cache; + +import com.yolanda.nohttp.db.DBManager; +import com.yolanda.nohttp.db.Where; +import com.yolanda.nohttp.db.Where.Options; + +import java.util.List; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; + +/** + *

Http cache interface implementation.

+ * Created in Jan 10, 2016 12:45:34 AM. + * + * @author Yan Zhenjie; + */ +public enum DiskCacheStore implements Cache { + + INSTANCE; + + /** + * Database sync lock. + */ + private Lock mLock; + /** + * Database manager. + */ + private DBManager mManager; + + DiskCacheStore() { + mLock = new ReentrantLock(); + mManager = CacheDiskManager.getInstance(); + } + + @Override + public CacheEntity get(String key) { + mLock.lock(); + try { + Where where = new Where(CacheDisk.KEY, Options.EQUAL, key); + List cacheEntities = mManager.get(CacheDisk.ALL, where.get(), null, null, null); + return cacheEntities.size() > 0 ? cacheEntities.get(0) : null; + } finally { + mLock.unlock(); + } + } + + @Override + public CacheEntity replace(String key, CacheEntity entrance) { + mLock.lock(); + try { + entrance.setKey(key); + mManager.replace(entrance); + return entrance; + } finally { + mLock.unlock(); + } + } + + @Override + public boolean remove(String key) { + mLock.lock(); + try { + if (key == null) + return true; + Where where = new Where(CacheDisk.KEY, Options.EQUAL, key); + return mManager.delete(where.toString()); + } finally { + mLock.unlock(); + } + } + + @Override + public boolean clear() { + mLock.lock(); + try { + return mManager.deleteAll(); + } finally { + mLock.unlock(); + } + } + +} diff --git a/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/cookie/CookieDisk.java b/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/cookie/CookieDisk.java new file mode 100644 index 0000000..fd89319 --- /dev/null +++ b/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/cookie/CookieDisk.java @@ -0,0 +1,89 @@ +/* + * Copyright 2015 Yan Zhenjie + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yolanda.nohttp.cookie; + +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteOpenHelper; + +import com.yolanda.nohttp.NoHttp; +import com.yolanda.nohttp.db.Field; + +/** + *

Cookie database operation class.

+ * Created in Dec 18, 2015 6:30:59 PM. + * + * @author Yan Zhenjie. + */ +class CookieDisk extends SQLiteOpenHelper implements Field { + + public static final String DB_COOKIE_NAME = "_nohttp_cookies_db.db"; + public static final int DB_COOKIE_VERSION = 2; + + public static final String TABLE_NAME = "cookies_table"; + public static final String URI = "uri"; + public static final String NAME = "name"; + public static final String VALUE = "value"; + public static final String COMMENT = "comment"; + public static final String COMMENT_URL = "comment_url"; + public static final String DISCARD = "discard"; + public static final String DOMAIN = "domain"; + public static final String EXPIRY = "expiry"; + public static final String PATH = "path"; + public static final String PORT_LIST = "port_list"; + public static final String SECURE = "secure"; + public static final String VERSION = "version"; + + private static final String SQL_CREATE_TABLE = "CREATE TABLE cookies_table(_id INTEGER PRIMARY KEY AUTOINCREMENT, uri TEXT, name TEXT, value TEXT, comment TEXT, comment_url TEXT, discard TEXT, domain TEXT, expiry INTEGER, path TEXT, port_list TEXT, secure TEXT, version INTEGER)"; + private static final String SQL_CREATE_UNIQUE_INDEX = "CREATE UNIQUE INDEX cookie_unique_index ON cookies_table(\"name\", \"domain\", \"path\")"; + private static final String SQL_DELETE_TABLE = "DROP TABLE IF EXISTS cookies_table"; + private static final String SQL_DELETE_UNIQUE_INDEX = "DROP INDEX IF EXISTS cookie_unique_index"; + + public CookieDisk() { + super(NoHttp.getContext(), DB_COOKIE_NAME, null, DB_COOKIE_VERSION); + } + + @Override + public void onCreate(SQLiteDatabase db) { + db.beginTransaction(); + try { + db.execSQL(SQL_CREATE_TABLE); + db.execSQL(SQL_CREATE_UNIQUE_INDEX); + db.setTransactionSuccessful(); + } finally { + db.endTransaction(); + } + } + + @Override + public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { + if (newVersion != oldVersion) { + db.beginTransaction(); + try { + db.execSQL(SQL_DELETE_TABLE); + db.execSQL(SQL_DELETE_UNIQUE_INDEX); + db.execSQL(SQL_CREATE_TABLE); + db.execSQL(SQL_CREATE_UNIQUE_INDEX); + db.setTransactionSuccessful(); + } finally { + db.endTransaction(); + } + } + } + + public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) { + onUpgrade(db, oldVersion, newVersion); + } +} diff --git a/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/cookie/CookieDiskManager.java b/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/cookie/CookieDiskManager.java new file mode 100644 index 0000000..a2f1afc --- /dev/null +++ b/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/cookie/CookieDiskManager.java @@ -0,0 +1,160 @@ +/* + * Copyright 2015 Yan Zhenjie + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yolanda.nohttp.cookie; + +import android.content.ContentValues; +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; + +import com.yolanda.nohttp.Logger; +import com.yolanda.nohttp.db.DBManager; + +import java.util.ArrayList; +import java.util.List; + +/** + *

Cookie database manager.

+ * Created in Dec 18, 2015 7:01:31 PM. + * + * @author Yan Zhenjie. + */ +class CookieDiskManager extends DBManager { + + private static DBManager _Instance; + + private CookieDiskManager() { + super(new CookieDisk()); + } + + public synchronized static DBManager getInstance() { + if (_Instance == null) + _Instance = new CookieDiskManager(); + return _Instance; + } + + /** + * Add or update by index(name, domain, path). + * + * @param cookie cookie entity. + */ + @Override + public long replace(CookieEntity cookie) { + SQLiteDatabase execute = getWriter(); + ContentValues values = new ContentValues(); + values.put(CookieDisk.URI, cookie.getUri()); + values.put(CookieDisk.NAME, cookie.getName()); + values.put(CookieDisk.VALUE, cookie.getValue()); + values.put(CookieDisk.COMMENT, cookie.getComment()); + values.put(CookieDisk.COMMENT_URL, cookie.getCommentURL()); + values.put(CookieDisk.DISCARD, String.valueOf(cookie.isDiscard())); + values.put(CookieDisk.DOMAIN, cookie.getDomain()); + values.put(CookieDisk.EXPIRY, cookie.getExpiry()); + values.put(CookieDisk.PATH, cookie.getPath()); + values.put(CookieDisk.PORT_LIST, cookie.getPortList()); + values.put(CookieDisk.SECURE, String.valueOf(cookie.isSecure())); + values.put(CookieDisk.VERSION, cookie.getVersion()); + long id = -1; + try { + print(values.toString()); + id = execute.replace(CookieDisk.TABLE_NAME, null, values); + } catch (Throwable e) { + Logger.w(e); + } + closeWriter(execute); + return id; + } + + @Override + public List get(String querySql) { + SQLiteDatabase execute = getReader(); + + List cookies = new ArrayList(); + Cursor cursor = null; + try { + cursor = execute.rawQuery(querySql, null); + while (!cursor.isClosed() && cursor.moveToNext()) { + try { + CookieEntity cookie = new CookieEntity(); + int idIndex = cursor.getColumnIndex(CookieDisk.ID); + if (idIndex >= 0) + cookie.setId(cursor.getInt(idIndex)); + + int uriIndex = cursor.getColumnIndex(CookieDisk.URI); + if (uriIndex >= 0) + cookie.setUri(cursor.getString(uriIndex)); + + int nameIndex = cursor.getColumnIndex(CookieDisk.NAME); + if (nameIndex >= 0) + cookie.setName(cursor.getString(nameIndex)); + + int valueIndex = cursor.getColumnIndex(CookieDisk.VALUE); + if (valueIndex >= 0) + cookie.setValue(cursor.getString(valueIndex)); + + int commentIndex = cursor.getColumnIndex(CookieDisk.COMMENT); + if (commentIndex >= 0) + cookie.setComment(cursor.getString(commentIndex)); + + int commentUriIndex = cursor.getColumnIndex(CookieDisk.COMMENT_URL); + if (commentUriIndex >= 0) + cookie.setCommentURL(cursor.getString(commentUriIndex)); + + int discardIndex = cursor.getColumnIndex(CookieDisk.DISCARD); + if (discardIndex >= 0) + cookie.setDiscard("true".equals(cursor.getString(discardIndex))); + + int domainIndex = cursor.getColumnIndex(CookieDisk.DOMAIN); + if (domainIndex >= 0) + cookie.setDomain(cursor.getString(domainIndex)); + + int expiryIndex = cursor.getColumnIndex(CookieDisk.EXPIRY); + if (expiryIndex >= 0) + cookie.setExpiry(cursor.getLong(expiryIndex)); + + int pathIndex = cursor.getColumnIndex(CookieDisk.PATH); + if (pathIndex >= 0) + cookie.setPath(cursor.getString(pathIndex)); + + int portListIndex = cursor.getColumnIndex(CookieDisk.PORT_LIST); + if (portListIndex >= 0) + cookie.setPortList(cursor.getString(portListIndex)); + + int secureIndex = cursor.getColumnIndex(CookieDisk.SECURE); + if (secureIndex >= 0) + cookie.setSecure("true".equals(cursor.getString(secureIndex))); + + int versionIndex = cursor.getColumnIndex(CookieDisk.VERSION); + if (versionIndex >= 0) + cookie.setVersion(cursor.getInt(versionIndex)); + + print(cookie.toString()); + cookies.add(cookie); + } catch (Throwable e) { + Logger.e(e); + } + } + } catch (Throwable e) { + Logger.e(e); + } + closeReader(execute, cursor); + return cookies; + } + + @Override + protected String getTableName() { + return CookieDisk.TABLE_NAME; + } +} \ No newline at end of file diff --git a/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/cookie/CookieEntity.java b/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/cookie/CookieEntity.java new file mode 100644 index 0000000..502991a --- /dev/null +++ b/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/cookie/CookieEntity.java @@ -0,0 +1,299 @@ +/* + * Copyright 2015 Yan Zhenjie + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yolanda.nohttp.cookie; + +import android.text.TextUtils; + +import com.yolanda.nohttp.db.DBId; +import com.yolanda.nohttp.tools.HeaderUtil; + +import java.io.Serializable; +import java.net.HttpCookie; +import java.net.URI; + +/** + *

Cookie entity.

+ * Created in Dec 17, 2015 7:21:16 PM. + * + * @author Yan Zhenjie. + */ +class CookieEntity implements DBId, Serializable { + + private static final long serialVersionUID = 6374381323722046732L; + + private long id = -1; + private String uri; // cookie add by this uri. + private String name; + private String value; + private String comment; + private String commentURL; + private boolean discard; + private String domain; + private long expiry; + private String path; + private String portList; + private boolean secure; + private int version = 1; + + public CookieEntity() { + } + + /** + * Cookie building database entities. + * + * @param uri cookie corresponding uri. + * @param cookie cookie. + */ + public CookieEntity(URI uri, HttpCookie cookie) { + this.uri = uri == null ? null : uri.toString(); + this.name = cookie.getName(); + this.value = cookie.getValue(); + this.comment = cookie.getComment(); + this.commentURL = cookie.getCommentURL(); + this.discard = cookie.getDiscard(); + this.domain = cookie.getDomain(); + long maxAge = cookie.getMaxAge(); + if (maxAge != -1 && maxAge > 0) { + this.expiry = (maxAge * 1000L) + System.currentTimeMillis(); + if (this.expiry < 0L) // 溢出 + this.expiry = HeaderUtil.getMaxExpiryMillis(); + } else + this.expiry = -1L; + + this.path = cookie.getPath(); + if (!TextUtils.isEmpty(path) && path.length() > 1 && path.endsWith("/")) { + this.path = path.substring(0, path.length() - 1); + } + this.portList = cookie.getPortlist(); + this.secure = cookie.getSecure(); + this.version = cookie.getVersion(); + } + + /** + * Into {@link HttpCookie}. + * + * @return {@link HttpCookie}. + */ + public HttpCookie toHttpCookie() { + HttpCookie cookie = new HttpCookie(name, value); + cookie.setComment(comment); + cookie.setCommentURL(commentURL); + cookie.setDiscard(discard); + cookie.setDomain(domain); + if (expiry == -1L) + cookie.setMaxAge(-1L); + else + cookie.setMaxAge((expiry - System.currentTimeMillis()) / 1000L); + cookie.setPath(path); + cookie.setPortlist(portList); + cookie.setSecure(secure); + cookie.setVersion(version); + return cookie; + } + + /** + * @return the id. + */ + @Override + public long getId() { + return id; + } + + /** + * @param id the id to set. + */ + public void setId(long id) { + this.id = id; + } + + /** + * @return the uri. + */ + public String getUri() { + return uri; + } + + /** + * @param uri the uri to set. + */ + public void setUri(String uri) { + this.uri = uri; + } + + /** + * @return the name. + */ + public String getName() { + return name; + } + + /** + * @param name the name to set. + */ + public void setName(String name) { + this.name = name; + } + + /** + * @return the value. + */ + public String getValue() { + return value; + } + + /** + * @param value the value to set. + */ + public void setValue(String value) { + this.value = value; + } + + /** + * @return the comment. + */ + public String getComment() { + return comment; + } + + /** + * @param comment the comment to set. + */ + public void setComment(String comment) { + this.comment = comment; + } + + /** + * @return the commentURL. + */ + public String getCommentURL() { + return commentURL; + } + + /** + * @param commentURL the commentURL to set. + */ + public void setCommentURL(String commentURL) { + this.commentURL = commentURL; + } + + /** + * @return the discard. + */ + public boolean isDiscard() { + return discard; + } + + /** + * @param discard the discard to set. + */ + public void setDiscard(boolean discard) { + this.discard = discard; + } + + /** + * @return the domain. + */ + public String getDomain() { + return domain; + } + + /** + * @param domain the domain to set. + */ + public void setDomain(String domain) { + this.domain = domain; + } + + /** + * @return the expiry. + */ + public long getExpiry() { + return expiry; + } + + /** + * @param expiry the expiry to set. + */ + public void setExpiry(long expiry) { + this.expiry = expiry; + } + + /** + * @return the path. + */ + public String getPath() { + return path; + } + + /** + * @param path the path to set. + */ + public void setPath(String path) { + this.path = path; + } + + /** + * @return the portList. + */ + public String getPortList() { + return portList; + } + + /** + * @param portList the portList to set. + */ + public void setPortList(String portList) { + this.portList = portList; + } + + /** + * @return the secure. + */ + public boolean isSecure() { + return secure; + } + + /** + * @param secure the secure to set. + */ + public void setSecure(boolean secure) { + this.secure = secure; + } + + /** + * @return the version. + */ + public int getVersion() { + return version; + } + + /** + * @param version the version to set. + */ + public void setVersion(int version) { + this.version = version; + } + + /** + * Cookie is expired ? + * + * @return expired return true, other wise false. + */ + public boolean isExpired() { + return expiry != -1L && expiry < System.currentTimeMillis(); + } + +} diff --git a/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/cookie/CookieStoreListener.java b/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/cookie/CookieStoreListener.java new file mode 100644 index 0000000..65f8cee --- /dev/null +++ b/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/cookie/CookieStoreListener.java @@ -0,0 +1,45 @@ +/* + * Copyright 2015 Yan Zhenjie + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yolanda.nohttp.cookie; + +import java.net.HttpCookie; +import java.net.URI; + +/** + *

The listener when save or delete the Cookie.

+ * Created in Dec 22, 2015 8:23:49 PM. + * + * @author Yan Zhenjie. + */ +public interface CookieStoreListener { + + /** + * When saving a Cookie callback. + * + * @param uri cookie corresponding uri. + * @param cookie {@link HttpCookie}. + */ + void onSaveCookie(URI uri, HttpCookie cookie); + + /** + * The callback when deleting cookies. + * + * @param uri cookie corresponding uri. + * @param cookie {@link HttpCookie}. + */ + void onRemoveCookie(URI uri, HttpCookie cookie); + +} diff --git a/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/cookie/DiskCookieStore.java b/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/cookie/DiskCookieStore.java new file mode 100644 index 0000000..022481c --- /dev/null +++ b/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/cookie/DiskCookieStore.java @@ -0,0 +1,248 @@ +/* + * Copyright 2015 Yan Zhenjie + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yolanda.nohttp.cookie; + +import android.text.TextUtils; + +import com.yolanda.nohttp.Logger; +import com.yolanda.nohttp.db.DBManager; +import com.yolanda.nohttp.db.Field; +import com.yolanda.nohttp.db.Where; +import com.yolanda.nohttp.db.Where.Options; + +import java.net.CookieStore; +import java.net.HttpCookie; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; + +/** + * Created in Dec 17, 2015 7:20:52 PM. + * + * @author Yan Zhenjie. + */ +public enum DiskCookieStore implements CookieStore { + + INSTANCE; + + /** + * Cookie max count in disk. + */ + private final static int MAX_COOKIE_SIZE = 8888; + /** + * Database sync lock. + */ + private Lock mLock; + /** + * Database Manager. + */ + private DBManager mManager; + /** + * When Add and remove cookie notify. + */ + private CookieStoreListener mCookieStoreListener; + + DiskCookieStore() { + mLock = new ReentrantLock(); + mManager = CookieDiskManager.getInstance(); + + // Delete temp cookie. + Where where = new Where(CookieDisk.EXPIRY, Options.EQUAL, -1L); + mManager.delete(where.get()); + } + + /** + * The callback when adding and deleting cookies. + * + * @param cookieStoreListener {@link CookieStoreListener}. + */ + public void setCookieStoreListener(CookieStoreListener cookieStoreListener) { + mCookieStoreListener = cookieStoreListener; + } + + @Override + public void add(URI uri, HttpCookie cookie) { + mLock.lock(); + try { + if (uri != null && cookie != null) { + uri = getEffectiveURI(uri); + if (mCookieStoreListener != null) + mCookieStoreListener.onSaveCookie(uri, cookie); + mManager.replace(new CookieEntity(uri, cookie)); + trimSize(); + } + } finally { + mLock.unlock(); + } + } + + @Override + public List get(URI uri) { + mLock.lock(); + try { + if (uri == null) + return Collections.emptyList(); + + uri = getEffectiveURI(uri); + Where where = new Where(); + + String host = uri.getHost(); + if (!TextUtils.isEmpty(host)) { + Where subWhere = new Where(CookieDisk.DOMAIN, Options.EQUAL, host).or(CookieDisk.DOMAIN, Options.EQUAL, "." + host); + + int firstDot = host.indexOf("."); + int lastDot = host.lastIndexOf("."); + if (firstDot > 0 && lastDot > firstDot) { + String domain = host.substring(firstDot, host.length()); + if (!TextUtils.isEmpty(domain)) { + subWhere.or(CookieDisk.DOMAIN, Options.EQUAL, domain); + } + } + where.set(subWhere.get()); + } + + String path = uri.getPath(); + if (!TextUtils.isEmpty(path)) { + Where subWhere = new Where(CookieDisk.PATH, Options.EQUAL, path).or(CookieDisk.PATH, Options.EQUAL, "/").orNull(CookieDisk.PATH); + int lastSplit = path.lastIndexOf("/"); + while (lastSplit > 0) { + path = path.substring(0, lastSplit); + subWhere.or(CookieDisk.PATH, Options.EQUAL, path); + lastSplit = path.lastIndexOf("/"); + } + subWhere.bracket(); + where.and(subWhere); + } + + where.or(CookieDisk.URI, Options.EQUAL, uri.toString()); + + List cookieList = mManager.get(Field.ALL, where.get(), null, null, null); + List returnedCookies = new ArrayList(); + for (CookieEntity cookieEntity : cookieList) + if (!cookieEntity.isExpired()) + returnedCookies.add(cookieEntity.toHttpCookie()); + return returnedCookies; + } finally { + mLock.unlock(); + } + } + + @Override + public List getCookies() { + mLock.lock(); + try { + List rt = new ArrayList(); + List cookieEntityList = mManager.getAll(); + for (CookieEntity cookieEntity : cookieEntityList) + if (!cookieEntity.isExpired()) + rt.add(cookieEntity.toHttpCookie()); + return rt; + } finally { + mLock.unlock(); + } + } + + @Override + public List getURIs() { + mLock.lock(); + try { + List uris = new ArrayList(); + List uriList = mManager.getAll(CookieDisk.URI); + for (CookieEntity cookie : uriList) { + String uri = cookie.getUri(); + if (!TextUtils.isEmpty(uri)) + try { + uris.add(new URI(uri)); + } catch (Throwable e) { + Logger.w(e); + StringBuilder where = new StringBuilder(CookieDisk.URI).append('=').append(uri); + mManager.delete(where.toString()); + } + } + return uris; + } finally { + mLock.unlock(); + } + } + + @Override + public boolean remove(URI uri, HttpCookie httpCookie) { + mLock.lock(); + try { + if (httpCookie == null) + return true; + if (mCookieStoreListener != null) + mCookieStoreListener.onRemoveCookie(uri, httpCookie); + Where where = new Where(CookieDisk.NAME, Options.EQUAL, httpCookie.getName()); + + String domain = httpCookie.getDomain(); + if (!TextUtils.isEmpty(domain)) + where.and(CookieDisk.DOMAIN, Options.EQUAL, domain); + + String path = httpCookie.getPath(); + if (!TextUtils.isEmpty(path)) { + if (path.length() > 1 && path.endsWith("/")) { + path = path.substring(0, path.length() - 1); + } + where.and(CookieDisk.PATH, Options.EQUAL, path); + } + return mManager.delete(where.toString()); + } finally { + mLock.unlock(); + } + } + + @Override + public boolean removeAll() { + mLock.lock(); + try { + return mManager.deleteAll(); + } finally { + mLock.unlock(); + } + } + + /** + * Trim the Cookie list. + */ + private void trimSize() { + int count = mManager.count(); + if (count > MAX_COOKIE_SIZE + 10) { + List rmList = mManager.get(Field.ALL, null, null, Integer.toString(count - MAX_COOKIE_SIZE), null); + if (rmList != null) + mManager.delete(rmList); + } + } + + /** + * Get effective URI. + * + * @param uri cookie corresponding uri. + */ + private URI getEffectiveURI(final URI uri) { + URI effectiveURI; + try { + effectiveURI = new URI("http", uri.getHost(), uri.getPath(), null, null); + } catch (URISyntaxException e) { + effectiveURI = uri; + } + return effectiveURI; + } +} \ No newline at end of file diff --git a/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/db/DBId.java b/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/db/DBId.java new file mode 100644 index 0000000..9eff3a0 --- /dev/null +++ b/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/db/DBId.java @@ -0,0 +1,33 @@ +/* + * Copyright 2015 Yan Zhenjie + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yolanda.nohttp.db; + +/** + *

The entity class id of the interface.

+ * Created in Jan 10, 2016 11:03:21 PM. + * + * @author Yan Zhenjie. + */ +public interface DBId { + + /** + * Get the object id. + * + * @return {@link Long}. + */ + long getId(); + +} diff --git a/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/db/DBManager.java b/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/db/DBManager.java new file mode 100644 index 0000000..63d145f --- /dev/null +++ b/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/db/DBManager.java @@ -0,0 +1,280 @@ +/* + * Copyright 2015 Yan Zhenjie + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yolanda.nohttp.db; + +import android.database.Cursor; +import android.database.SQLException; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteOpenHelper; +import android.text.TextUtils; +import android.util.Log; + +import com.yolanda.nohttp.Logger; + +import java.util.List; + +/** + *

Database management generic class, has realized the basic functions, inheritance of the subclass only need to implement {@link #replace(DBId)}, {@link #get(String)} and + * {@link #getTableName()}.

+ * Created in Jan 10, 2016 8:18:28 PM. + * + * @author Yan Zhenjie. + */ +public abstract class DBManager { + + private static final boolean DEBUG = false; + /** + * A helper class to manage database creation and version management. + */ + private SQLiteOpenHelper disk; + + public DBManager(SQLiteOpenHelper disk) { + this.disk = disk; + } + + /** + * Open the database when the read data. + * + * @return {@link SQLiteDatabase}. + */ + protected final SQLiteDatabase getReader() { + return disk.getReadableDatabase(); + } + + /** + * Open the database when the write data. + * + * @return {@link SQLiteDatabase}. + */ + protected final SQLiteDatabase getWriter() { + return disk.getWritableDatabase(); + } + + /** + * Close the database when reading data. + * + * @param execute {@link SQLiteDatabase}. + * @param cursor {@link Cursor}. + */ + protected final void closeReader(SQLiteDatabase execute, Cursor cursor) { + if (cursor != null && !cursor.isClosed()) + cursor.close(); + closeWriter(execute); + } + + /** + * Close the database when writing data. + * + * @param execute {@link SQLiteDatabase}. + */ + protected final void closeWriter(SQLiteDatabase execute) { + if (execute != null && execute.isOpen()) { + execute.close(); + } + } + + /** + * The query id number. + * + * @return int format. + */ + public final int count() { + return countColumn(Field.ID); + } + + /** + * According to the "column" query "column" number. + * + * @param columnName ColumnName. + * @return column count. + */ + public final int countColumn(String columnName) { + StringBuilder sqlBuild = new StringBuilder("SELECT COUNT(").append(columnName).append(") FROM ").append(getTableName()); + return count(sqlBuild.toString()); + } + + /** + * According to the "column" query number. + * + * @param sql sql. + * @return count + */ + public final int count(String sql) { + SQLiteDatabase execute = getReader(); + print(sql); + Cursor cursor = execute.rawQuery(sql, null); + int count = 0; + if (cursor.moveToNext()) { + count = cursor.getInt(0); + } + closeReader(execute, cursor); + return count; + } + + /** + * Delete all data. + * + * @return a boolean value, whether deleted successfully. + */ + public final boolean deleteAll() { + return delete("1=1"); + } + + /** + * Must have the id. + * + * @param ts delete the queue list. + * @return a boolean value, whether deleted successfully. + */ + public final boolean delete(List ts) { + StringBuilder where = new StringBuilder(Field.ID).append(" IN("); + for (T t : ts) { + long id = t.getId(); + if (id > 0) { + where.append(','); + where.append(id); + } + } + where.append(')'); + if (',' == where.charAt(6)) + where.deleteCharAt(6); + return delete(where.toString()); + } + + /** + * According to the where to delete data. + * + * @param where performs conditional. + * @return a boolean value, whether deleted successfully. + */ + public final boolean delete(String where) { + if (TextUtils.isEmpty(where)) + return true; + SQLiteDatabase execute = getWriter(); + StringBuilder sqlBuild = new StringBuilder("DELETE FROM ").append(getTableName()).append(" WHERE ").append(where); + boolean result = true; + try { + String sql = sqlBuild.toString(); + print(sql); + execute.execSQL(sql); + } catch (SQLException e) { + Logger.e(e); + result = false; + } + closeWriter(execute); + return result; + } + + /** + * Query all data. + * + * @return list data. + */ + public final List getAll() { + return getAll(Field.ALL); + } + + /** + * All the data query a column. + * + * @param columnName columnName. + * @return list data. + */ + public final List getAll(String columnName) { + return get(columnName, null, null, null, null); + } + + /** + * All the data query a column. + * + * @param columnName such as: {@code "*"}. + * @param where such as: {@code age > 20}. + * @param orderBy such as: {@code "age"}. + * @param limit such as. {@code '20'}. + * @param offset offset. + * @return list data. + */ + public final List get(String columnName, String where, String orderBy, String limit, String offset) { + return get(getSelectSql(columnName, where, orderBy, limit, offset)); + } + + /** + * Create query sql + * + * @param columnName columnName. + * @param where where. + * @param orderBy orderBy. + * @param limit limit. + * @param offset offset. + * @return {@link String}. + */ + private String getSelectSql(String columnName, String where, String orderBy, String limit, String offset) { + StringBuilder sqlBuild = new StringBuilder("SELECT ").append(columnName).append(" FROM ").append(getTableName()); + if (!TextUtils.isEmpty(where)) { + sqlBuild.append(" WHERE "); + sqlBuild.append(where); + } + if (!TextUtils.isEmpty(orderBy)) { + sqlBuild.append(" ORDER BY "); + sqlBuild.append(orderBy); + } + if (!TextUtils.isEmpty(limit)) { + sqlBuild.append(" LIMIT "); + sqlBuild.append(limit); + } + if (!TextUtils.isEmpty(limit) && !TextUtils.isEmpty(offset)) { + sqlBuild.append(" OFFSET "); + sqlBuild.append(offset); + } + String sql = sqlBuild.toString(); + print(sql); + return sqlBuild.toString(); + } + + /** + * According to the SQL query data list. + * + * @param querySql sql. + * @return list data. + */ + public abstract List get(String querySql); + + /** + * According to the unique index adds or updates a row data. + * + * @param t {@link T}. + * @return long. + */ + public abstract long replace(T t); + + /** + * Table name should be. + * + * @return table name. + */ + protected abstract String getTableName(); + + /** + * Print the test data. + * + * @param print string. + */ + protected void print(String print) { + if (DEBUG) + Log.d("NoHttp", print); + } + +} diff --git a/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/db/Field.java b/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/db/Field.java new file mode 100644 index 0000000..be3991f --- /dev/null +++ b/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/db/Field.java @@ -0,0 +1,30 @@ +/* + * Copyright 2015 Yan Zhenjie + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yolanda.nohttp.db; + +/** + *

General field class.

+ * Created in Jan 11, 2016 12:46:38 PM. + * + * @author Yan Zhenjie. + */ +public interface Field { + + String ID = "_id"; + + String ALL = "*"; + +} diff --git a/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/db/Where.java b/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/db/Where.java new file mode 100644 index 0000000..1a28ac3 --- /dev/null +++ b/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/db/Where.java @@ -0,0 +1,192 @@ +/* + * Copyright 2015 Yan Zhenjie + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yolanda.nohttp.db; + +import java.util.List; + +/** + * Created in Dec 19, 2015 4:16:24 PM. + * + * @author Yan Zhenjie. + */ +public class Where { + + /** + * Structure where the symbols. + */ + public enum Options { + + IN("IN"), EQUAL("="), NO_EQUAL("!="), ThAN_LARGE(">"), THAN_SMALL("<"); + + private String value; + + Options(String value) { + this.value = value; + } + + @Override + public final String toString() { + return this.value; + } + } + + private StringBuilder builder; + + public Where() { + builder = new StringBuilder(); + } + + /** + * @param columnName columnName. + * @param op such as: {@code >, =, <, IN}, but it's come from {@link Options}. + * @param value {@link Character}, {@link Integer}, {@link Long}, {@link Short}, {@link Double}, {@link Float}. + */ + public Where(CharSequence columnName, Options op, Object value) { + builder = new StringBuilder(); + add(columnName, op, value); + } + + public final Where clear() { + builder.delete(0, builder.length()); + return this; + } + + public final Where append(Object row) { + builder.append(row); + return this; + } + + public final Where set(String row) { + clear().append(row); + return this; + } + + public final Where isNull(CharSequence columnName) { + builder.append("\"").append(columnName).append("\" ").append("IS ").append("NULL"); + return this; + } + + private Where addColumnName(CharSequence columnName, Options op) { + builder.append("\"").append(columnName).append("\" ").append(op.toString()).append(' '); + return this; + } + + /** + * @param columnName columnName. + * @param op such as: {@code >, =, <, IN}, but it's come from {@link Options}. + * @param value {@link Character}, {@link Integer}, {@link Long}, {@link Short}, {@link Double}, {@link Float}. + * @return {@link Where}. + */ + public final Where add(CharSequence columnName, Options op, Object value) { + if (Options.EQUAL.equals(op) || Options.ThAN_LARGE.equals(op) || Options.THAN_SMALL.equals(op) || Options.NO_EQUAL.equals(op)) { + addColumnName(columnName, op); + if (isNumber(value)) + builder.append(value); + else + builder.append("'").append(value).append("'"); + } else if (Options.IN.equals(op) && value instanceof List) + addColumnName(columnName, op).append(value).in((List) value); + else + throw new IllegalArgumentException("Value is not supported by the data type"); + return this; + } + + private Where in(List values) { + builder.append(Options.IN).append(" ("); + String sep = ", "; + for (T value : values) { + if (value instanceof CharSequence) + builder.append("'").append(value).append("'"); + else if (value instanceof Integer || value instanceof Long || value instanceof Short) + builder.append(value); + builder.append(sep); + } + if (builder.lastIndexOf(sep) > 0) + builder.delete(builder.length() - 2, builder.length()); + builder.append(")"); + return this; + } + + private Where and() { + if (builder.length() > 0) + builder.append(" AND "); + return this; + } + + /** + * @param columnName columnName. + * @param op such as: {@code >, =, <, IN}, but it's come from {@link Options}. + * @param value {@link Character}, {@link Integer}, {@link Long}, {@link Short}, {@link Double}, {@link Float}. + * @return {@link Where}. + */ + public final Where and(CharSequence columnName, Options op, Object value) { + return and().add(columnName, op, value); + } + + public final Where andNull(CharSequence columnName) { + return and().isNull(columnName); + } + + public final Where and(Where where) { + return and().append(where); + } + + private Where or() { + if (builder.length() > 0) + builder.append(" OR "); + return this; + } + + /** + * @param columnName columnName. + * @param op such as: {@code >, =, <, IN}, but it's come from {@link Options}. + * @param value {@link Character}, {@link Integer}, {@link Long}, {@link Short}, {@link Double}, {@link Float}. + * @return {@link Where}. + */ + public final Where or(CharSequence columnName, Options op, Object value) { + return or().add(columnName, op, value); + } + + public final Where orNull(CharSequence columnName) { + return or().isNull(columnName); + } + + public final Where or(Where where) { + return or().append(where); + } + + public final Where bracket() { + return insert(0, "(").append(')'); + } + + public final Where insert(int index, CharSequence s) { + builder.insert(index, s); + return this; + } + + public final String get() { + return builder.toString(); + } + + @Override + public String toString() { + return builder.toString(); + } + + public static boolean isNumber(Object value) { + return value != null && (value instanceof Character || value instanceof Integer || value instanceof Long || value instanceof Short || value instanceof Double || value instanceof Float); + } +} diff --git a/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/download/DownloadConnection.java b/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/download/DownloadConnection.java new file mode 100644 index 0000000..1fbb093 --- /dev/null +++ b/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/download/DownloadConnection.java @@ -0,0 +1,263 @@ +/* + * Copyright 2015 Yan Zhenjie + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yolanda.nohttp.download; + +import android.text.TextUtils; +import android.util.Log; + +import com.yolanda.nohttp.BasicConnection; +import com.yolanda.nohttp.Connection; +import com.yolanda.nohttp.Headers; +import com.yolanda.nohttp.Logger; +import com.yolanda.nohttp.error.ArgumentError; +import com.yolanda.nohttp.error.NetworkError; +import com.yolanda.nohttp.error.ServerError; +import com.yolanda.nohttp.error.StorageReadWriteError; +import com.yolanda.nohttp.error.StorageSpaceNotEnoughError; +import com.yolanda.nohttp.error.TimeoutError; +import com.yolanda.nohttp.error.URLError; +import com.yolanda.nohttp.error.UnKnownHostError; +import com.yolanda.nohttp.tools.HeaderUtil; +import com.yolanda.nohttp.tools.IOUtils; +import com.yolanda.nohttp.tools.NetUtil; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.RandomAccessFile; +import java.net.MalformedURLException; +import java.net.SocketTimeoutException; +import java.net.URLDecoder; +import java.net.UnknownHostException; + +/** + *

+ * The network layer to download missions. + *

+ * Created in Jul 31, 2015 9:11:55 AM. + * + * @author Yan Zhenjie. + */ +public class DownloadConnection extends BasicConnection implements Downloader { + + public DownloadConnection() { + } + + @Override + public void download(int what, DownloadRequest downloadRequest, DownloadListener downloadListener) { + Connection connection = null; + if (downloadRequest == null) + throw new IllegalArgumentException("downloadRequest == null."); + if (downloadListener == null) + throw new IllegalArgumentException("downloadListener == null."); + + RandomAccessFile randomAccessFile = null; + String savePathDir = downloadRequest.getFileDir(); + String fileName = downloadRequest.getFileName(); + try { + if (TextUtils.isEmpty(savePathDir)) + throw new ArgumentError("Error saving the location of the target file, please check whether fileFolder parameter is empty."); + + File file = new File(savePathDir); + if (file.exists() && file.isFile()) + IOUtils.delFileOrFolder(file); + + if (!IOUtils.createFolder(savePathDir)) + throw new StorageReadWriteError("Failed to create the folder " + savePathDir + ", please check storage devices."); + + if (!NetUtil.isNetworkAvailable()) + throw new NetworkError("Network is not available."); + + if (TextUtils.isEmpty(fileName))// aotu named. + fileName = Long.toString(System.currentTimeMillis()); + + File tempFile = new File(savePathDir, fileName + ".nohttp"); + // 根据临时文件处理断点头。 + long rangeSize = 0L;// 断点开始处。 + if (tempFile.exists()) { + if (tempFile.isDirectory()) + try { + IOUtils.createNewFile(tempFile); + } catch (Throwable e) { + throw new StorageReadWriteError("Failed to create the file, please check storage devices."); + } + + if (downloadRequest.isRange()) { + rangeSize = tempFile.length(); + // 例如:从1024开始下载:Range:bytes=1024-。 + downloadRequest.setHeader("Range", "bytes=" + rangeSize + "-"); + } else { + tempFile.delete(); + } + } + + // 连接服务器。 + connection = getConnection(downloadRequest); + Exception exception = connection.exception(); + if (exception != null) + throw exception; + + Logger.i("----------Response Start----------"); + Headers responseHeaders = connection.responseHeaders(); + int responseCode = responseHeaders.getResponseCode(); + + // get filename from server. + if (downloadRequest.autoNameByHead()) { + String contentDisposition = responseHeaders.getContentDisposition(); + if (!TextUtils.isEmpty(contentDisposition)) { + fileName = HeaderUtil.parseHeadValue(contentDisposition, "filename", null); + if (!TextUtils.isEmpty(fileName)) { + fileName = URLDecoder.decode(fileName, downloadRequest.getParamsEncoding()); + if (fileName.startsWith("\"") && fileName.endsWith("\"")) { + fileName = fileName.substring(1, fileName.length() - 1); + } + } + } + + if (TextUtils.isEmpty(fileName)) { + // handle redirect url. + String tempUrl = connection.getURL().toString(); + if (TextUtils.isEmpty(tempUrl)) + tempUrl = downloadRequest.url(); + String[] slash = tempUrl.split("/"); + fileName = slash[slash.length - 1]; + int paramIndex = fileName.indexOf("?"); + if (paramIndex > 0) { + fileName = fileName.substring(0, paramIndex); + } + } + } + + InputStream serverStream = connection.serverStream(); + if (responseCode >= 400) { + ServerError error = new ServerError("Download fails, the server response code is " + responseCode + ": " + downloadRequest.url()); + error.setErrorBody(IOUtils.toString(serverStream)); + throw error; + } else { + long contentLength = 0; + // 文件总大小 + if (responseCode == 206) { + // Content-Range: bytes [文件块的开始字节]-[文件的总大小 - 1]/[文件的总大小]。 + String range = responseHeaders.getContentRange(); // 事例:Accept-Range:bytes 1024-2047/2048。 + try { + contentLength = Long.parseLong(range.substring(range.indexOf('/') + 1));// 截取'/'之后的总大小。 + } catch (Throwable e) { + throw new ServerError("ResponseCode is 206, but content-Range error in Server HTTP header information: " + range + "."); + } + } else if (responseCode == 200) { + contentLength = responseHeaders.getContentLength();// 直接下载。 + rangeSize = 0L; // 没有contentLength时断点移动到头部。 + } else if (responseCode == 304) { + int httpContentLength = responseHeaders.getContentLength(); + downloadListener.onStart(what, true, httpContentLength, responseHeaders, httpContentLength); + downloadListener.onProgress(what, 100, httpContentLength); + Logger.d("-------Download finish-------"); + downloadListener.onFinish(what, savePathDir + File.separator + fileName); + return; + } + + // 验证文件已经存在。 + File lastFile = new File(savePathDir, fileName); + if (lastFile.exists()) { + if (downloadRequest.isDeleteOld()) + lastFile.delete(); + else { + downloadListener.onStart(what, true, lastFile.length(), responseHeaders, lastFile.length()); + downloadListener.onProgress(what, 100, lastFile.length()); + Logger.d("-------Download finish-------"); + downloadListener.onFinish(what, lastFile.getAbsolutePath()); + return; + } + } + + // 需要重新下载,生成临时文件。 + if ((responseCode == 200 || responseCode >= 400) && !IOUtils.createNewFile(tempFile)) + throw new StorageReadWriteError("Failed to create the file, please check storage devices."); + + if (IOUtils.getDirSize(savePathDir) < contentLength) + throw new StorageSpaceNotEnoughError("The folder is not enough space to save the downloaded file: " + savePathDir + "."); + + if (downloadRequest.isCanceled()) { + Log.i("NoHttpDownloader", "Download request is canceled."); + downloadListener.onCancel(what); + return; + } + + // 通知开始下载了。 + Logger.d("-------Download start-------"); + downloadListener.onStart(what, rangeSize > 0, rangeSize, responseHeaders, contentLength); + + randomAccessFile = new RandomAccessFile(tempFile, "rws"); + randomAccessFile.seek(rangeSize); + + byte[] buffer = new byte[4096]; + int len; + + int oldProgress = 0;// 旧的进度记录,防止重复通知。 + long count = rangeSize;// 追加目前已经下载的进度。 + + while (((len = serverStream.read(buffer)) != -1)) { + if (downloadRequest.isCanceled()) { + Log.i("NoHttpDownloader", "Download request is canceled."); + downloadListener.onCancel(what); + break; + } else { + randomAccessFile.write(buffer, 0, len); + count += len; + if (contentLength != 0) { + int progress = (int) (count * 100 / contentLength); + if ((0 == progress % 2 || 0 == progress % 3 || 0 == progress % 5 || 0 == progress % 7) && oldProgress != progress) { + oldProgress = progress; + downloadListener.onProgress(what, oldProgress, count);// 进度通知。 + } + } + } + } + if (!downloadRequest.isCanceled() && (tempFile.length() == contentLength || contentLength == 0)) { + tempFile.renameTo(lastFile); + Logger.d("-------Download finish-------"); + downloadListener.onFinish(what, lastFile.getAbsolutePath()); + } + } + } catch (MalformedURLException e) { + Logger.e(e); + downloadListener.onDownloadError(what, new URLError(e.getMessage())); + } catch (UnknownHostException e) { + Logger.e(e); + downloadListener.onDownloadError(what, new UnKnownHostError(e.getMessage())); + } catch (SocketTimeoutException e) { + Logger.e(e); + downloadListener.onDownloadError(what, new TimeoutError(e.getMessage())); + } catch (IOException e) { + Exception newException = e; + if (!IOUtils.canWrite(savePathDir)) + newException = new StorageReadWriteError("This folder cannot be written to the file: " + savePathDir + "."); + else if (IOUtils.getDirSize(savePathDir) < 1024) + newException = new StorageSpaceNotEnoughError("The folder is not enough space to save the downloaded file: " + savePathDir + "."); + Logger.e(newException); + downloadListener.onDownloadError(what, newException); + } catch (Exception e) {// NetworkError | ServerError | StorageCantWriteError | StorageSpaceNotEnoughError + if (!NetUtil.isNetworkAvailable()) + e = new NetworkError("The network is not available."); + Logger.e(e); + downloadListener.onDownloadError(what, e); + } finally { + Logger.i("----------Response End----------"); + IOUtils.closeQuietly(randomAccessFile); + IOUtils.closeQuietly(connection); + } + } +} diff --git a/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/download/DownloadDispatcher.java b/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/download/DownloadDispatcher.java new file mode 100644 index 0000000..a6e0ee7 --- /dev/null +++ b/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/download/DownloadDispatcher.java @@ -0,0 +1,221 @@ +/* + * Copyright 2015 Yan Zhenjie + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yolanda.nohttp.download; + +import android.os.Process; + +import com.yolanda.nohttp.Headers; +import com.yolanda.nohttp.Logger; +import com.yolanda.nohttp.PosterHandler; + +import java.util.concurrent.BlockingQueue; + +/** + *

+ * Download queue polling thread. + *

+ * Created in Oct 21, 2015 2:46:23 PM. + * + * @author Yan Zhenjie. + */ +class DownloadDispatcher extends Thread { + + /** + * Un finish task queue. + */ + private final BlockingQueue mUnFinishQueue; + /** + * Download task queue. + */ + private final BlockingQueue mDownloadQueue; + /** + * Perform network request interface. + */ + private final Downloader mDownloader; + /** + * Are you out of this thread. + */ + private boolean mQuit = false; + + /** + * Create a thread that executes the download queue. + * + * @param unFinishQueue un finish queue. + * @param downloadQueue download queue to be polled. + * @param downloader perform network request interface. + */ + public DownloadDispatcher(BlockingQueue unFinishQueue, BlockingQueue downloadQueue, Downloader downloader) { + mUnFinishQueue = unFinishQueue; + mDownloadQueue = downloadQueue; + mDownloader = downloader; + } + + /** + * Quit this thread. + */ + public void quit() { + mQuit = true; + interrupt(); + } + + @Override + public void run() { + Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); + while (!mQuit) { + final DownloadRequest request; + try { + request = mDownloadQueue.take(); + } catch (InterruptedException e) { + if (mQuit) + return; + continue; + } + + if (request.isCanceled()) { + Logger.d(request.url() + " is canceled."); + continue; + } + + request.start(); + mDownloader.download(request.what(), request, new DownloadListener() { + + @Override + public void onStart(int what, boolean isResume, long beforeLength, Headers headers, long allCount) { + ThreadPoster threadPoster = new ThreadPoster(request.what(), request.downloadListener()); + threadPoster.onStart(isResume, beforeLength, headers, allCount); + PosterHandler.getInstance().post(threadPoster); + } + + @Override + public void onDownloadError(int what, Exception exception) { + ThreadPoster threadPoster = new ThreadPoster(request.what(), request.downloadListener()); + threadPoster.onError(exception); + PosterHandler.getInstance().post(threadPoster); + } + + @Override + public void onProgress(int what, int progress, long fileCount) { + ThreadPoster threadPoster = new ThreadPoster(request.what(), request.downloadListener()); + threadPoster.onProgress(progress, fileCount); + PosterHandler.getInstance().post(threadPoster); + } + + @Override + public void onFinish(int what, String filePath) { + ThreadPoster threadPoster = new ThreadPoster(request.what(), request.downloadListener()); + threadPoster.onFinish(filePath); + PosterHandler.getInstance().post(threadPoster); + } + + @Override + public void onCancel(int what) { + ThreadPoster threadPoster = new ThreadPoster(request.what(), request.downloadListener()); + threadPoster.onCancel(); + PosterHandler.getInstance().post(threadPoster); + } + }); + request.finish(); + mUnFinishQueue.remove(request); + } + } + + private class ThreadPoster implements Runnable { + + public static final int COMMAND_START = 0; + public static final int COMMAND_PROGRESS = 1; + public static final int COMMAND_ERROR = 2; + public static final int COMMAND_FINISH = 3; + public static final int COMMAND_CANCEL = 4; + + private final int what; + private final DownloadListener downloadListener; + + // command + private int command; + + // start + private Headers responseHeaders; + private long allCount; + private boolean isResume; + private long beforeLength; + + // progress + private int progress; + private long fileCount; + + // error + private Exception exception; + + // finish + private String filePath; + + public ThreadPoster(int what, DownloadListener downloadListener) { + this.what = what; + this.downloadListener = downloadListener; + } + + public void onStart(boolean isResume, long beforeLength, Headers responseHeaders, long allCount) { + this.command = COMMAND_START; + this.isResume = isResume; + this.beforeLength = beforeLength; + this.responseHeaders = responseHeaders; + this.allCount = allCount; + } + + public void onProgress(int progress, long fileCount) { + this.command = COMMAND_PROGRESS; + this.progress = progress; + this.fileCount = fileCount; + } + + public void onError(Exception exception) { + this.command = COMMAND_ERROR; + this.exception = exception; + } + + public void onCancel() { + this.command = COMMAND_CANCEL; + } + + public void onFinish(String filePath) { + this.command = COMMAND_FINISH; + this.filePath = filePath; + } + + @Override + public void run() { + switch (command) { + case COMMAND_START: + downloadListener.onStart(what, isResume, beforeLength, responseHeaders, allCount); + break; + case COMMAND_PROGRESS: + downloadListener.onProgress(what, progress, fileCount); + break; + case COMMAND_ERROR: + downloadListener.onDownloadError(what, exception); + break; + case COMMAND_FINISH: + downloadListener.onFinish(what, filePath); + break; + case COMMAND_CANCEL: + downloadListener.onCancel(what); + break; + default: + break; + } + } + } +} diff --git a/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/download/DownloadListener.java b/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/download/DownloadListener.java new file mode 100644 index 0000000..acbe70e --- /dev/null +++ b/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/download/DownloadListener.java @@ -0,0 +1,78 @@ +/* + * Copyright 2015 Yan Zhenjie + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yolanda.nohttp.download; + +import com.yolanda.nohttp.Headers; + +/** + *

The download process monitor.

+ * Created in Jul 31, 2015 9:12:55 AM; + * + * @author Yan Zhenjie. + */ +public interface DownloadListener { + + /** + * An error occurred while downloading. + * + * @param what which is used to mark the download tasks. + * @param exception error types. Error types include the following:

{@link com.yolanda.nohttp.error.ArgumentError} Need to check the download parameters are correct.

+ *

{@link com.yolanda.nohttp.error.NetworkError} The network is not available, please check the network.

+ *

{@link com.yolanda.nohttp.error.ServerError} When the response code is more than 400, you need to look at the response code specific how much is the judgement is something wrong.

+ *

{@link com.yolanda.nohttp.error.StorageReadWriteError} An error occurred when read/write memory CARDS, please check the memory card.

+ *

{@link com.yolanda.nohttp.error.StorageSpaceNotEnoughError} There is insufficient space on the memory card, please check the memory card.

+ *

{@link com.yolanda.nohttp.error.TimeoutError} Timeout connecting to the server or read the file.

+ *

{@link com.yolanda.nohttp.error.UnKnownHostError} Is not found in the network of the target server.

+ *

{@link com.yolanda.nohttp.error.URLError} Download url is wrong.

+ */ + void onDownloadError(int what, Exception exception); + + /** + * When this download task starts the callback method. + * + * @param what which is used to mark the download tasks. + * @param isResume whether to continue to download, if it is true that has download before, and have already. + * download the file size is not zero. + * @param rangeSize hTTP starting point size, the size of the data already exists. + * @param responseHeaders server response headers. + * @param allCount total file size. + */ + void onStart(int what, boolean isResume, long rangeSize, Headers responseHeaders, long allCount); + + /** + * When the download process change. + * + * @param what which is used to mark the download tasks. + * @param progress this method is the time to change the progress of the download. + * @param fileCount have downloaded the file size. + */ + void onProgress(int what, int progress, long fileCount); + + /** + * Download is complete. + * + * @param what which is used to mark the download tasks. + * @param filePath where is the file after the download is complete. + */ + void onFinish(int what, String filePath); + + /** + * Download request is canceled. + * + * @param what which is used to mark the download tasks. + */ + void onCancel(int what); +} \ No newline at end of file diff --git a/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/download/DownloadQueue.java b/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/download/DownloadQueue.java new file mode 100644 index 0000000..e09452f --- /dev/null +++ b/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/download/DownloadQueue.java @@ -0,0 +1,146 @@ +/* + * Copyright 2015 Yan Zhenjie + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yolanda.nohttp.download; + +import com.yolanda.nohttp.Logger; + +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.LinkedBlockingDeque; +import java.util.concurrent.PriorityBlockingQueue; +import java.util.concurrent.atomic.AtomicInteger; + +/** + *

+ * Download queue. + *

+ * Created in Oct 21, 2015 2:44:19 PM. + * + * @author Yan Zhenjie. + */ +public class DownloadQueue { + + private AtomicInteger mInteger = new AtomicInteger(); + /** + * Save un finish task. + */ + private final BlockingQueue mUnFinishQueue = new LinkedBlockingDeque(); + /** + * Save download task. + */ + private final BlockingQueue mDownloadQueue = new PriorityBlockingQueue(); + /** + * Download Network task execution interface. + */ + private final Downloader mDownloader; + /** + * Download queue polling thread array. + */ + private DownloadDispatcher[] mDispatchers; + + /** + * Create download queue manager. + * + * @param downloader download the network task execution interface, where you need to implement the download tasks that have been implemented. + * @param threadPoolSize number of thread pool. + */ + public DownloadQueue(Downloader downloader, int threadPoolSize) { + mDownloader = downloader; + mDispatchers = new DownloadDispatcher[threadPoolSize]; + } + + /** + * Start polling the download queue, a one of the implementation of the download task, if you have started to poll the download queue, then it will stop all the threads, to re create thread + * execution. + */ + public void start() { + stop(); + for (int i = 0; i < mDispatchers.length; i++) { + DownloadDispatcher networkDispatcher = new DownloadDispatcher(mUnFinishQueue, mDownloadQueue, mDownloader); + mDispatchers[i] = networkDispatcher; + networkDispatcher.start(); + } + } + + /** + * Add a download task to download queue, waiting for execution, if there is no task in the queue or the number of tasks is less than the number of thread pool, will be executed immediately. + * + * @param what used to distinguish Download. + * @param downloadRequest download request object. + * @param downloadListener download results monitor. + */ + public void add(int what, DownloadRequest downloadRequest, DownloadListener downloadListener) { + if (downloadRequest.inQueue()) + Logger.w("This request has been in the queue"); + else { + downloadRequest.setQueue(mUnFinishQueue); + downloadRequest.onPreResponse(what, downloadListener); + downloadRequest.setSequence(mInteger.incrementAndGet()); + mUnFinishQueue.add(downloadRequest); + mDownloadQueue.add(downloadRequest); + } + } + + /** + * Don't start return request queue size. + * + * @return size. + */ + public int unStartSize() { + return mDownloadQueue.size(); + } + + /** + * Returns have started but not the end of the request queue size. + * + * @return size. + */ + public int unFinishSize() { + return mUnFinishQueue.size(); + } + + /** + * Polling the queue will not be executed, and this will not be canceled. + */ + public void stop() { + for (DownloadDispatcher dispatcher : mDispatchers) { + if (dispatcher != null) + dispatcher.quit(); + } + } + + /** + * All requests for the sign specified in the queue, if you are executing, will interrupt the download task. + * + * @param sign this sign will be the same as sign's DownloadRequest, and if it is the same, then cancel the task. + */ + public void cancelBySign(Object sign) { + synchronized (mUnFinishQueue) { + for (DownloadRequest downloadRequest : mUnFinishQueue) + downloadRequest.cancelBySign(sign); + } + } + + /** + * Cancel all requests, Already in the execution of the request can't use this method. + */ + public void cancelAll() { + synchronized (mUnFinishQueue) { + for (DownloadRequest downloadRequest : mUnFinishQueue) + downloadRequest.cancel(); + } + } + +} diff --git a/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/download/DownloadRequest.java b/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/download/DownloadRequest.java new file mode 100644 index 0000000..3a618d3 --- /dev/null +++ b/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/download/DownloadRequest.java @@ -0,0 +1,113 @@ +/* + * Copyright 2015 Yan Zhenjie + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yolanda.nohttp.download; + +import com.yolanda.nohttp.IBasicRequest; + +/** + *

+ * Download task request interface. + *

+ * Created in Oct 21, 2015 11:09:04 AM. + * + * @author Yan Zhenjie. + */ +public interface DownloadRequest extends IBasicRequest { + + /** + * Also didn't download to start download again. + */ + int STATUS_RESTART = 0; + /** + * Part has been downloaded, continue to download last time. + */ + int STATUS_RESUME = 1; + /** + * Has the download is complete, not to download operation. + */ + int STATUS_FINISH = 2; + + /** + * Return the mFileDir. + * + * @return it won't be empty. + */ + String getFileDir(); + + /** + * Return the mFileName. + * + * @return it won't be empty. + */ + String getFileName(); + + /** + * According to the Http header named files automatically. + * + * @return true need, false not need. + */ + boolean autoNameByHead(); + + /** + * Return the isRange. + * + * @return true: breakpoint continuing, false: don't need a breakpoint continuing. + */ + boolean isRange(); + + /** + * If there is a old files, whether to delete the old files. + * + * @return true: deleted, false: don't delete. + */ + boolean isDeleteOld(); + + /** + *

+ * Query before download status {@link #STATUS_RESTART} representative no download do to download again; Download {@link #STATUS_RESUME} represents a part of, to continue to download; {@link #STATUS_FINISH} representatives have finished downloading. + *

+ * + * @return int value, compared with the {@value #STATUS_RESTART}, {@value #STATUS_RESUME}, {@value #STATUS_FINISH}. + * @see #STATUS_RESTART + * @see #STATUS_RESUME + * @see #STATUS_FINISH + */ + int checkBeforeStatus(); + + /** + * Prepare the callback parameter, while waiting for the response callback with thread. + * + * @param what the callback mark. + * @param downloadListener {@link DownloadListener}. + */ + void onPreResponse(int what, DownloadListener downloadListener); + + /** + * The callback mark. + * + * @return Return when {@link #onPreResponse(int, DownloadListener)} incoming credit. + * @see #onPreResponse(int, DownloadListener) + */ + int what(); + + /** + * The request of the listener. + * + * @return Return when {@link #onPreResponse(int, DownloadListener)} incoming credit. + * @see #onPreResponse(int, DownloadListener) + */ + DownloadListener downloadListener(); +} diff --git a/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/download/Downloader.java b/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/download/Downloader.java new file mode 100644 index 0000000..2cf0ba8 --- /dev/null +++ b/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/download/Downloader.java @@ -0,0 +1,35 @@ +/* + * Copyright 2015 Yan Zhenjie + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yolanda.nohttp.download; + +/** + *

Download Network Interface.

+ * Created in Oct 20, 2015 4:13:04 PM. + * + * @author Yan Zhenjie. + */ +public interface Downloader { + + /** + * Execute a download task. + * + * @param what what of task. + * @param downloadRequest download request parameter. + * @param downloadListener the download process monitor. + */ + void download(int what, DownloadRequest downloadRequest, DownloadListener downloadListener); + +} diff --git a/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/download/RestDownloadRequest.java b/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/download/RestDownloadRequest.java new file mode 100644 index 0000000..fd48492 --- /dev/null +++ b/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/download/RestDownloadRequest.java @@ -0,0 +1,171 @@ +/* + * Copyright 2015 Yan Zhenjie + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yolanda.nohttp.download; + +import com.yolanda.nohttp.BasicRequest; +import com.yolanda.nohttp.RedirectHandler; +import com.yolanda.nohttp.RequestMethod; + +import java.io.File; + +/** + *

+ * Download the implementation class of the parameter request, and convert it to the object of the network download. + *

+ * Created in Jul 31, 2015 10:38:10 AM. + * + * @author Yan Zhenjie.. + */ +public class RestDownloadRequest extends BasicRequest implements DownloadRequest { + + /** + * The callback mark. + */ + private int what; + /** + * The request of the listener. + */ + private DownloadListener downloadListener; + /** + * File the target folder. + */ + private final String mFileDir; + /** + * The file target name. + */ + private final String mFileName; + /** + * According to the Http header named files automatically. + */ + private final boolean mAutoNameByHead; + /** + * If is to download a file, whether the breakpoint continuing. + */ + private final boolean isRange; + /** + * If there is a old files, whether to delete the old files. + */ + private final boolean isDeleteOld; + + /** + * Create download request. + * + * @param url url. + * @param requestMethod {@link RequestMethod}. + * @param fileFolder file save folder. + * @param isDeleteOld find the same when the file is deleted after download, or on behalf of the download is complete, not to request the network. + * @see #RestDownloadRequest(String, RequestMethod, String, String, boolean, boolean) + */ + public RestDownloadRequest(String url, RequestMethod requestMethod, String fileFolder, boolean isDeleteOld) { + this(url, requestMethod, fileFolder, null, true, false, isDeleteOld); + } + + /** + * Create a download object. + * + * @param url download address. + * @param requestMethod {@link RequestMethod}. + * @param fileFolder folder to save file. + * @param filename filename. + * @param isRange whether the breakpoint continuing. + * @param isDeleteOld find the same when the file is deleted after download, or on behalf of the download is complete, not to request the network. + * @see #RestDownloadRequest(String, RequestMethod, String, boolean) + */ + public RestDownloadRequest(String url, RequestMethod requestMethod, String fileFolder, String filename, boolean isRange, boolean isDeleteOld) { + this(url, requestMethod, fileFolder, filename, false, isRange, isDeleteOld); + } + + /** + * Create a download object. + * + * @param url download address. + * @param requestMethod {@link RequestMethod}. + * @param fileFolder folder to save file. + * @param filename filename. + * @param autoNameByHead according to the Http header named files automatically. + * @param isRange whether the breakpoint continuing. + * @param isDeleteOld find the same when the file is deleted after download, or on behalf of the download is complete, not to request the network. + */ + private RestDownloadRequest(String url, RequestMethod requestMethod, String fileFolder, String filename, boolean autoNameByHead, boolean isRange, boolean isDeleteOld) { + super(url, requestMethod); + this.mFileDir = fileFolder; + this.mFileName = filename; + this.mAutoNameByHead = autoNameByHead; + this.isRange = isRange; + this.isDeleteOld = isDeleteOld; + } + + @Override + public String getFileDir() { + return this.mFileDir; + } + + @Override + public String getFileName() { + return this.mFileName; + } + + @Override + public boolean autoNameByHead() { + return mAutoNameByHead; + } + + @Override + public boolean isRange() { + return this.isRange; + } + + @Override + public boolean isDeleteOld() { + return this.isDeleteOld; + } + + @Override + public int checkBeforeStatus() { + if (this.isRange) { + try { + File lastFile = new File(mFileDir, mFileName); + if (lastFile.exists() && !isDeleteOld) + return STATUS_FINISH; + File tempFile = new File(mFileDir, mFileName + ".nohttp"); + if (tempFile.exists()) + return STATUS_RESUME; + } catch (Exception e) { + } + } + return STATUS_RESTART; + } + + @Override + public void onPreResponse(int what, DownloadListener downloadListener) { + this.what = what; + this.downloadListener = downloadListener; + } + + @Override + public int what() { + return what; + } + + @Override + public DownloadListener downloadListener() { + return downloadListener; + } + + @Override + public void setRedirectHandler(RedirectHandler redirectHandler) { + } +} diff --git a/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/error/ArgumentError.java b/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/error/ArgumentError.java new file mode 100644 index 0000000..80502c5 --- /dev/null +++ b/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/error/ArgumentError.java @@ -0,0 +1,41 @@ +/* + * Copyright 2015 Yan Zhenjie + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yolanda.nohttp.error; + +/** + * Created in 2016/2/26 19:03. + * + * @author Yan Zhenjie. + */ +public class ArgumentError extends Exception { + + private static final long serialVersionUID = 1516L; + + public ArgumentError() { + } + + public ArgumentError(String detailMessage) { + super(detailMessage); + } + + public ArgumentError(String detailMessage, Throwable throwable) { + super(detailMessage, throwable); + } + + public ArgumentError(Throwable throwable) { + super(throwable); + } +} diff --git a/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/error/NetworkError.java b/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/error/NetworkError.java new file mode 100644 index 0000000..6f0e87f --- /dev/null +++ b/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/error/NetworkError.java @@ -0,0 +1,42 @@ +/* + * Copyright 2015 Yan Zhenjie + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yolanda.nohttp.error; + +/** + *

Network error when requested.

+ * Created in 2016/2/25 9:48. + * + * @author Yan Zhenjie. + */ +public class NetworkError extends Exception { + + private static final long serialVersionUID = 11548468L; + + public NetworkError() { + } + + public NetworkError(String detailMessage) { + super(detailMessage); + } + + public NetworkError(String detailMessage, Throwable throwable) { + super(detailMessage, throwable); + } + + public NetworkError(Throwable throwable) { + super(throwable); + } +} diff --git a/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/error/NotFoundCacheError.java b/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/error/NotFoundCacheError.java new file mode 100644 index 0000000..3af0031 --- /dev/null +++ b/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/error/NotFoundCacheError.java @@ -0,0 +1,41 @@ +/* + * Copyright 2015 Yan Zhenjie + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yolanda.nohttp.error; + +/** + * Created in 2016/3/3 13:34. + * + * @author Yan Zhenjie. + */ +public class NotFoundCacheError extends Exception { + + private static final long serialVersionUID = 115481468L; + + public NotFoundCacheError() { + } + + public NotFoundCacheError(String detailMessage) { + super(detailMessage); + } + + public NotFoundCacheError(String detailMessage, Throwable throwable) { + super(detailMessage, throwable); + } + + public NotFoundCacheError(Throwable throwable) { + super(throwable); + } +} diff --git a/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/error/ParseError.java b/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/error/ParseError.java new file mode 100644 index 0000000..57c60df --- /dev/null +++ b/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/error/ParseError.java @@ -0,0 +1,40 @@ +/* + * Copyright 2015 Yan Zhenjie + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yolanda.nohttp.error; + +/** + * Created on 2016/6/30. + * + * @author Yan Zhenjie. + */ +public class ParseError extends Exception { + + public ParseError() { + } + + public ParseError(String detailMessage) { + super(detailMessage); + } + + public ParseError(String detailMessage, Throwable throwable) { + super(detailMessage, throwable); + } + + public ParseError(Throwable throwable) { + super(throwable); + } + +} diff --git a/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/error/ServerError.java b/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/error/ServerError.java new file mode 100644 index 0000000..7d77119 --- /dev/null +++ b/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/error/ServerError.java @@ -0,0 +1,61 @@ +/* + * Copyright 2015 Yan Zhenjie + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yolanda.nohttp.error; + +/** + * Created in 2016/4/17 22:40. + * + * @author Yan Zhenjie. + */ +public class ServerError extends Exception { + + private static final long serialVersionUID = 1854642L; + + private String errorBody; + + public ServerError() { + } + + public ServerError(String detailMessage) { + super(detailMessage); + } + + public ServerError(String detailMessage, Throwable throwable) { + super(detailMessage, throwable); + } + + public ServerError(Throwable throwable) { + super(throwable); + } + + /** + * To get the wrong information. + * + * @return the error message. + */ + public String getErrorBody() { + return errorBody; + } + + /** + * To set the wrong information. + * + * @param errorBody the error message. + */ + public void setErrorBody(String errorBody) { + this.errorBody = errorBody; + } +} diff --git a/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/error/StorageReadWriteError.java b/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/error/StorageReadWriteError.java new file mode 100644 index 0000000..8f49acb --- /dev/null +++ b/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/error/StorageReadWriteError.java @@ -0,0 +1,41 @@ +/* + * Copyright 2015 Yan Zhenjie + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yolanda.nohttp.error; + +/** + * Created in 2016/2/26 19:14. + * + * @author Yan Zhenjie; + */ +public class StorageReadWriteError extends Exception { + + private static final long serialVersionUID = 178946465L; + + public StorageReadWriteError() { + } + + public StorageReadWriteError(String detailMessage) { + super(detailMessage); + } + + public StorageReadWriteError(String detailMessage, Throwable throwable) { + super(detailMessage, throwable); + } + + public StorageReadWriteError(Throwable throwable) { + super(throwable); + } +} diff --git a/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/error/StorageSpaceNotEnoughError.java b/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/error/StorageSpaceNotEnoughError.java new file mode 100644 index 0000000..79d4a00 --- /dev/null +++ b/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/error/StorageSpaceNotEnoughError.java @@ -0,0 +1,43 @@ +/* + * Copyright 2015 Yan Zhenjie + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yolanda.nohttp.error; + +/** + *

Specify the location of the file space is not enough.

+ * Created in 2016/2/25 11:50. + * + * @author Yan Zhenjie. + */ +public class StorageSpaceNotEnoughError extends Exception { + + private static final long serialVersionUID = 11786348L; + + public StorageSpaceNotEnoughError() { + } + + public StorageSpaceNotEnoughError(String detailMessage) { + super(detailMessage); + } + + public StorageSpaceNotEnoughError(String detailMessage, Throwable throwable) { + super(detailMessage, throwable); + } + + public StorageSpaceNotEnoughError(Throwable throwable) { + super(throwable); + } + +} diff --git a/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/error/TimeoutError.java b/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/error/TimeoutError.java new file mode 100644 index 0000000..9d16383 --- /dev/null +++ b/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/error/TimeoutError.java @@ -0,0 +1,43 @@ +/* + * Copyright 2015 Yan Zhenjie + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yolanda.nohttp.error; + +/** + *

Request connection timeout.

+ * Created in 2016/2/25 10:17. + * + * @author Yan Zhenjie. + */ +public class TimeoutError extends Exception { + + private static final long serialVersionUID = 1164986L; + + public TimeoutError() { + } + + public TimeoutError(String detailMessage) { + super(detailMessage); + } + + public TimeoutError(String detailMessage, Throwable throwable) { + super(detailMessage, throwable); + } + + public TimeoutError(Throwable throwable) { + super(throwable); + } + +} diff --git a/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/error/URLError.java b/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/error/URLError.java new file mode 100644 index 0000000..79f117d --- /dev/null +++ b/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/error/URLError.java @@ -0,0 +1,43 @@ +/* + * Copyright 2015 Yan Zhenjie + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yolanda.nohttp.error; + +/** + *

The URL specified is incorrect.

+ * Created in 2016/2/25 9:49. + * + * @author Yan Zhenjie. + */ +public class URLError extends Exception { + + private static final long serialVersionUID = 114946L; + + public URLError() { + } + + public URLError(String detailMessage) { + super(detailMessage); + } + + public URLError(String detailMessage, Throwable throwable) { + super(detailMessage, throwable); + } + + public URLError(Throwable throwable) { + super(throwable); + } + +} diff --git a/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/error/UnKnownHostError.java b/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/error/UnKnownHostError.java new file mode 100644 index 0000000..d4a023d --- /dev/null +++ b/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/error/UnKnownHostError.java @@ -0,0 +1,43 @@ +/* + * Copyright 2015 Yan Zhenjie + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yolanda.nohttp.error; + +/** + *

The target host not found.

+ * Created in 2016/2/25 10:49. + * + * @author Yan Zhenjie. + */ +public class UnKnownHostError extends Exception { + + private static final long serialVersionUID = 1149646L; + + public UnKnownHostError() { + } + + public UnKnownHostError(String detailMessage) { + super(detailMessage); + } + + public UnKnownHostError(String detailMessage, Throwable throwable) { + super(detailMessage, throwable); + } + + public UnKnownHostError(Throwable throwable) { + super(throwable); + } + +} diff --git a/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/rest/ByteArrayRequest.java b/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/rest/ByteArrayRequest.java new file mode 100644 index 0000000..a85f199 --- /dev/null +++ b/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/rest/ByteArrayRequest.java @@ -0,0 +1,38 @@ +/* + * Copyright © Yan Zhenjie. All Rights Reserved + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yolanda.nohttp.rest; + +import com.yolanda.nohttp.Headers; +import com.yolanda.nohttp.RequestMethod; + +/** + * Created by Yan Zhenjie on 2016/8/20. + */ +public class ByteArrayRequest extends RestRequest { + + public ByteArrayRequest(String url) { + this(url, RequestMethod.GET); + } + + public ByteArrayRequest(String url, RequestMethod requestMethod) { + super(url, requestMethod); + } + + @Override + public byte[] parseResponse(Headers responseHeaders, byte[] responseBody) throws Throwable { + return responseBody == null ? new byte[0] : responseBody; + } +} diff --git a/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/rest/CacheMode.java b/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/rest/CacheMode.java new file mode 100644 index 0000000..c71d299 --- /dev/null +++ b/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/rest/CacheMode.java @@ -0,0 +1,73 @@ +/* + * Copyright 2015 Yan Zhenjie + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yolanda.nohttp.rest; + +/** + *

+ * NoHttp caching pattern, the default value is {@link CacheMode#DEFAULT}, other value may be {@link CacheMode#REQUEST_NETWORK_FAILED_READ_CACHE}, {@link CacheMode#ONLY_READ_CACHE}, + * {@link CacheMode#ONLY_REQUEST_NETWORK}, {@link CacheMode#NONE_CACHE_REQUEST_NETWORK}. + *

+ * Created in 2016/3/20 23:17. + * + * @author Yan Zhenjie. + */ +public enum CacheMode { + + /** + * The default mode, according to the standard HTTP protocol cache, such as response header is 304. + */ + DEFAULT, + + /** + *

+ * Request fails to read the cache, if the request to the server success or cache exists invoke {@link OnResponseListener#onSucceed(int, Response)}, if the request to the server failure or cache + * does not exist invoke {@link OnResponseListener#onFailed(int, Response)}. + *

+ */ + REQUEST_NETWORK_FAILED_READ_CACHE, + + /** + * If there is no cache request, it returns the cache cache exists. + */ + NONE_CACHE_REQUEST_NETWORK, + + /** + *

+ * If the cache exists invoke {@link OnResponseListener#onSucceed(int, Response)}, otherwise invoke {@link OnResponseListener#onFailed(int, Response)}. + *

+ */ + ONLY_READ_CACHE, + + /** + * It does not deal with anything related to the cache, only to get the data from the network. + */ + ONLY_REQUEST_NETWORK; + + /** + * Whether caching pattern is in compliance with the Http protocol. + * + * @return true or false. + */ + public boolean isStandardHttpProtocol() { + switch (this) { + case DEFAULT: + case ONLY_REQUEST_NETWORK: + return true; + default: + return false; + } + } +} \ No newline at end of file diff --git a/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/rest/IParserRequest.java b/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/rest/IParserRequest.java new file mode 100644 index 0000000..6b47548 --- /dev/null +++ b/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/rest/IParserRequest.java @@ -0,0 +1,34 @@ +/* + * Copyright © Yan Zhenjie. All Rights Reserved + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yolanda.nohttp.rest; + +import com.yolanda.nohttp.Headers; + +/** + * Created by Yan Zhenjie on 2016/8/20. + */ +public interface IParserRequest extends IProtocolRequest { + + /** + * Parse request results for generic objects. + * + * @param responseHeaders response headers of server. + * @param responseBody response data of server. + * @return your response result. + * @throws Throwable parse error. + */ + T parseResponse(Headers responseHeaders, byte[] responseBody) throws Throwable; +} diff --git a/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/rest/IProtocolRequest.java b/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/rest/IProtocolRequest.java new file mode 100644 index 0000000..c9b6aa4 --- /dev/null +++ b/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/rest/IProtocolRequest.java @@ -0,0 +1,54 @@ +/* + * Copyright © Yan Zhenjie. All Rights Reserved + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yolanda.nohttp.rest; + +import com.yolanda.nohttp.IBasicRequest; + +/** + *

For the Request to encapsulate some Http protocol related properties.

+ * Created by Yan Zhenjie on 2016/8/20. + */ +public interface IProtocolRequest extends IBasicRequest { + + /** + * Set the request cache primary key, it should be globally unique. + * + * @param key unique key. + */ + void setCacheKey(String key); + + /** + * Get key of cache data. + * + * @return cache key. + */ + String getCacheKey(); + + /** + * Set the cache mode. + * + * @param cacheMode The value from {@link CacheMode}. + */ + void setCacheMode(CacheMode cacheMode); + + /** + * He got the request cache mode. + * + * @return value from {@link CacheMode}. + */ + CacheMode getCacheMode(); + +} diff --git a/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/rest/IRestParser.java b/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/rest/IRestParser.java new file mode 100644 index 0000000..f769444 --- /dev/null +++ b/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/rest/IRestParser.java @@ -0,0 +1,35 @@ +/* + * Copyright 2015 Yan Zhenjie + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yolanda.nohttp.rest; + +/** + *

Request network and parse the results.

+ * Created in Jan 25, 2016 3:57:45 PM. + * + * @author Yan Zhenjie. + */ +public interface IRestParser { + + /** + * Request network and parse the results. + * + * @param request request. + * @param T. + * @return {@link Response}. + */ + Response parserRequest(IParserRequest request); + +} diff --git a/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/rest/IRestProtocol.java b/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/rest/IRestProtocol.java new file mode 100644 index 0000000..95f0fe7 --- /dev/null +++ b/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/rest/IRestProtocol.java @@ -0,0 +1,34 @@ +/* + * Copyright 2015 Yan Zhenjie + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yolanda.nohttp.rest; + +/** + *

Parsing the Http protocol related attributes, complete and the interaction of the network.

+ * Created in Oct 20, 2015 4:12:16 PM. + * + * @author Yan Zhenjie. + */ +public interface IRestProtocol { + + /** + * Parsing the Http protocol related attributes, complete and the interaction of the network. + * + * @param request request. + * @return {@link ProtocolResult}. + */ + ProtocolResult requestNetwork(IProtocolRequest request); + +} diff --git a/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/rest/ImageRequest.java b/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/rest/ImageRequest.java new file mode 100644 index 0000000..3c5c252 --- /dev/null +++ b/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/rest/ImageRequest.java @@ -0,0 +1,157 @@ +/* + * Copyright 2015 Yan Zhenjie + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yolanda.nohttp.rest; + +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.widget.ImageView; + +import com.yolanda.nohttp.Headers; +import com.yolanda.nohttp.Logger; +import com.yolanda.nohttp.RequestMethod; + +import java.util.Locale; + +/** + *

Image request parameter.

+ * Created in Oct 17, 2015 12:17:57 PM. + * + * @author Yan Zhenjie. + */ +public class ImageRequest extends RestRequest { + + private final int mMaxWidth; + private final int mMaxHeight; + private final Bitmap.Config mDecodeConfig; + private ImageView.ScaleType mScaleType; + + /** + * Decoding lock so that we don't decode more than one image at a time (to avoid OOM's). + */ + private static final Object DECODE_LOCK = new Object(); + + public ImageRequest(String url, RequestMethod requestMethod, int maxWidth, int maxHeight, Bitmap.Config decodeConfig, ImageView.ScaleType scaleType) { + super(url, requestMethod); + this.mMaxWidth = maxWidth; + this.mMaxHeight = maxHeight; + this.mDecodeConfig = decodeConfig; + this.mScaleType = scaleType; + setAccept("image/*"); + } + + @Override + public Bitmap parseResponse(Headers responseHeaders, byte[] responseBody) throws Throwable { + synchronized (DECODE_LOCK) { + Bitmap bitmap = null; + if (responseBody != null) { + try { + bitmap = doResponse(responseBody); + } catch (OutOfMemoryError e) { + String errorMessage = String.format(Locale.US, "Caught OOM for %d byte image, url=%s", responseBody.length, url()); + Logger.e(e, errorMessage); + } + } + return bitmap; + } + } + + /** + * The real guts of AnalyzeResponse. Broken out for readability. + */ + private Bitmap doResponse(byte[] byteArray) throws OutOfMemoryError { + BitmapFactory.Options decodeOptions = new BitmapFactory.Options(); + Bitmap bitmap; + if (mMaxWidth == 0 && mMaxHeight == 0) { + decodeOptions.inPreferredConfig = mDecodeConfig; + bitmap = BitmapFactory.decodeByteArray(byteArray, 0, byteArray.length, decodeOptions); + } else { + decodeOptions.inJustDecodeBounds = true; + BitmapFactory.decodeByteArray(byteArray, 0, byteArray.length, decodeOptions); + int actualWidth = decodeOptions.outWidth; + int actualHeight = decodeOptions.outHeight; + + int desiredWidth = getResizedDimension(mMaxWidth, mMaxHeight, actualWidth, actualHeight, mScaleType); + int desiredHeight = getResizedDimension(mMaxHeight, mMaxWidth, actualHeight, actualWidth, mScaleType); + + decodeOptions.inJustDecodeBounds = false; + decodeOptions.inSampleSize = findBestSampleSize(actualWidth, actualHeight, desiredWidth, desiredHeight); + Bitmap tempBitmap = BitmapFactory.decodeByteArray(byteArray, 0, byteArray.length, decodeOptions); + + if (tempBitmap != null && (tempBitmap.getWidth() > desiredWidth || tempBitmap.getHeight() > desiredHeight)) { + bitmap = Bitmap.createScaledBitmap(tempBitmap, desiredWidth, desiredHeight, true); + tempBitmap.recycle(); + } else { + bitmap = tempBitmap; + } + } + return bitmap; + } + + private static int getResizedDimension(int maxPrimary, int maxSecondary, int actualPrimary, int actualSecondary, ImageView.ScaleType scaleType) { + + // If no dominant value at all, just return the actual. + if ((maxPrimary == 0) && (maxSecondary == 0)) { + return actualPrimary; + } + + // If ScaleType.FIT_XY fill the whole rectangle, ignore ratio. + if (scaleType == ImageView.ScaleType.FIT_XY) { + if (maxPrimary == 0) { + return actualPrimary; + } + return maxPrimary; + } + + // If primary is unspecified, scale primary to match secondary's scaling ratio. + if (maxPrimary == 0) { + double ratio = (double) maxSecondary / (double) actualSecondary; + return (int) (actualPrimary * ratio); + } + + if (maxSecondary == 0) { + return maxPrimary; + } + + double ratio = (double) actualSecondary / (double) actualPrimary; + int resized = maxPrimary; + + // If ScaleType.CENTER_CROP fill the whole rectangle, preserve aspect ratio. + if (scaleType == ImageView.ScaleType.CENTER_CROP) { + if ((resized * ratio) < maxSecondary) { + resized = (int) (maxSecondary / ratio); + } + return resized; + } + + if ((resized * ratio) > maxSecondary) { + resized = (int) (maxSecondary / ratio); + } + return resized; + } + + // Visible for testing. + public static int findBestSampleSize(int actualWidth, int actualHeight, int desiredWidth, int desiredHeight) { + double wr = (double) actualWidth / desiredWidth; + double hr = (double) actualHeight / desiredHeight; + double ratio = Math.min(wr, hr); + float n = 1.0f; + while ((n * 2) <= ratio) { + n *= 2; + } + return (int) n; + } + +} \ No newline at end of file diff --git a/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/rest/JsonArrayRequest.java b/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/rest/JsonArrayRequest.java new file mode 100644 index 0000000..478add9 --- /dev/null +++ b/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/rest/JsonArrayRequest.java @@ -0,0 +1,46 @@ +/* + * Copyright 2015 Yan Zhenjie + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yolanda.nohttp.rest; + +import com.yolanda.nohttp.Headers; +import com.yolanda.nohttp.RequestMethod; + +import org.json.JSONArray; + +/** + *

JsonArray is returned by the server data, using the request object.

+ * Created in Jan 19, 2016 3:32:28 PM. + * + * @author Yan Zhenjie. + */ +public class JsonArrayRequest extends RestRequest { + + public JsonArrayRequest(String url) { + this(url, RequestMethod.GET); + } + + public JsonArrayRequest(String url, RequestMethod requestMethod) { + super(url, requestMethod); + setAccept(Headers.HEAD_VALUE_ACCEPT_APPLICATION_JSON); + } + + @Override + public JSONArray parseResponse(Headers responseHeaders, byte[] responseBody) throws Throwable { + String jsonStr = StringRequest.parseResponseString(responseHeaders, responseBody); + return new JSONArray(jsonStr); + } + +} diff --git a/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/rest/JsonObjectRequest.java b/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/rest/JsonObjectRequest.java new file mode 100644 index 0000000..c2fd31a --- /dev/null +++ b/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/rest/JsonObjectRequest.java @@ -0,0 +1,45 @@ +/* + * Copyright 2015 Yan Zhenjie + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yolanda.nohttp.rest; + +import com.yolanda.nohttp.Headers; +import com.yolanda.nohttp.RequestMethod; + +import org.json.JSONObject; + +/** + *

JsonObject is returned by the server data, using the request object.

+ * Created in Jan 19, 2016 3:27:35 PM. + * + * @author Yan Zhenjie. + */ +public class JsonObjectRequest extends RestRequest { + + public JsonObjectRequest(String url) { + this(url, RequestMethod.GET); + } + + public JsonObjectRequest(String url, RequestMethod requestMethod) { + super(url, requestMethod); + setAccept(Headers.HEAD_VALUE_ACCEPT_APPLICATION_JSON); + } + + @Override + public JSONObject parseResponse(Headers responseHeaders, byte[] responseBody) throws Throwable { + String jsonStr = StringRequest.parseResponseString(responseHeaders, responseBody); + return new JSONObject(jsonStr); + } +} \ No newline at end of file diff --git a/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/rest/OnResponseListener.java b/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/rest/OnResponseListener.java new file mode 100644 index 0000000..d32116a --- /dev/null +++ b/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/rest/OnResponseListener.java @@ -0,0 +1,56 @@ +/* + * Copyright 2015 Yan Zhenjie + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yolanda.nohttp.rest; + +/** + * Created in Jul 28, 2015 7:32:53 PM. + * + * @param a generic, on behalf of you can accept the result type, .It should be with the {@link Request}, {@link Response}. + * @author Yan Zhenjie. + */ +public interface OnResponseListener { + + /** + * When the request starts. + * + * @param what the credit of the incoming request is used to distinguish between multiple requests. + */ + void onStart(int what); + + /** + * Server correct response to callback when an HTTP request. + * + * @param what the credit of the incoming request is used to distinguish between multiple requests. + * @param response successful callback. + */ + void onSucceed(int what, Response response); + + /** + * When there was an error correction. + * + * @param what the credit of the incoming request is used to distinguish between multiple requests. + * @param response failure callback. + */ + void onFailed(int what, Response response); + + /** + * When the request finish. + * + * @param what the credit of the incoming request is used to distinguish between multiple requests. + */ + void onFinish(int what); + +} \ No newline at end of file diff --git a/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/rest/ParseRequest.java b/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/rest/ParseRequest.java new file mode 100644 index 0000000..477a268 --- /dev/null +++ b/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/rest/ParseRequest.java @@ -0,0 +1,43 @@ +/* + * Copyright © Yan Zhenjie. All Rights Reserved + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yolanda.nohttp.rest; + +import com.yolanda.nohttp.RequestMethod; + +/** + * Created by Yan Zhenjie on 2016/8/20. + */ +public abstract class ParseRequest extends ProtocolRequest implements IParserRequest { + + /** + * Create a request, RequestMethod is {@link RequestMethod#GET}. + * + * @param url request address, like: http://www.yanzhenjie.com. + */ + public ParseRequest(String url) { + this(url, RequestMethod.GET); + } + + /** + * Create a request. + * + * @param url request address, such as: http://www.yanzhenjie.com. + * @param requestMethod it's come from {@link CacheMode}. + */ + public ParseRequest(String url, RequestMethod requestMethod) { + super(url, requestMethod); + } +} diff --git a/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/rest/ProtocolRequest.java b/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/rest/ProtocolRequest.java new file mode 100644 index 0000000..e7746ed --- /dev/null +++ b/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/rest/ProtocolRequest.java @@ -0,0 +1,77 @@ +/* + * Copyright © Yan Zhenjie. All Rights Reserved + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yolanda.nohttp.rest; + +import android.text.TextUtils; + +import com.yolanda.nohttp.BasicRequest; +import com.yolanda.nohttp.RequestMethod; + +/** + *

For the Request to encapsulate some Http protocol related properties.

+ * Created by Yan Zhenjie on 2016/8/20. + */ +public abstract class ProtocolRequest extends BasicRequest implements IProtocolRequest { + + /** + * Cache key. + */ + private String mCacheKey; + /** + * If just read from cache. + */ + private CacheMode mCacheMode = CacheMode.DEFAULT; + + /** + * Create a request, RequestMethod is {@link RequestMethod#GET}. + * + * @param url request address, like: http://www.google.com. + */ + public ProtocolRequest(String url) { + this(url, RequestMethod.GET); + } + + /** + * Create a request + * + * @param url request address, like: http://www.google.com. + * @param requestMethod request method, like {@link RequestMethod#GET}, {@link RequestMethod#POST}. + */ + public ProtocolRequest(String url, RequestMethod requestMethod) { + super(url, requestMethod); + } + + @Override + public void setCacheKey(String key) { + this.mCacheKey = key; + } + + @Override + public String getCacheKey() { + return TextUtils.isEmpty(mCacheKey) ? url() : mCacheKey; + } + + @Override + public void setCacheMode(CacheMode cacheMode) { + this.mCacheMode = cacheMode; + } + + @Override + public CacheMode getCacheMode() { + return mCacheMode; + } + +} diff --git a/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/rest/ProtocolResult.java b/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/rest/ProtocolResult.java new file mode 100644 index 0000000..72c474f --- /dev/null +++ b/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/rest/ProtocolResult.java @@ -0,0 +1,123 @@ +/* + * Copyright 2015 Yan Zhenjie + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yolanda.nohttp.rest; + +import com.yolanda.nohttp.Headers; + +/** + * Created in Jan 6, 2016 5:19:13 PM. + * + * @author Yan Zhenjie. + */ +public class ProtocolResult { + + /** + * Server response header. + */ + private Headers mResponseHeaders; + /** + * Is the data from the cache. + */ + private boolean isFromCache; + /** + * Data. + */ + private byte[] mResponseBody; + /** + * Exception of connection. + */ + private Exception mException; + + ProtocolResult(Headers responseHeaders, byte[] responseBody, boolean isFromCache, Exception exception) { + this.mResponseHeaders = responseHeaders; + this.mResponseBody = responseBody; + this.isFromCache = isFromCache; + this.mException = exception; + } + + /** + * Get response headers of server. + * + * @return response headers. + */ + public Headers responseHeaders() { + return mResponseHeaders; + } + + /** + * Set the response headers of server. + * + * @param responseHeaders {@link Headers}. + */ + void setResponseHeaders(Headers responseHeaders) { + this.mResponseHeaders = responseHeaders; + } + + /** + * Get Data. + * + * @return the responseBody. + */ + public byte[] responseBody() { + return mResponseBody; + } + + /** + * Set Data. + * + * @param responseBody the responseBody to set. + */ + void setResponseBody(byte[] responseBody) { + this.mResponseBody = responseBody; + } + + /** + * Is the data from the cache. + * + * @return the isFromCache. + */ + public boolean isFromCache() { + return isFromCache; + } + + /** + * Set is from cache. + * + * @param isFromCache the isFromCache to set. + */ + void setFromCache(boolean isFromCache) { + this.isFromCache = isFromCache; + } + + /** + * Get exception of connection. + * + * @return exception. + */ + public Exception exception() { + return mException; + } + + /** + * Set Exception of connection. + * + * @param exception the types of error system. + */ + void setException(Exception exception) { + this.mException = exception; + } + +} diff --git a/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/rest/Request.java b/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/rest/Request.java new file mode 100644 index 0000000..91dbb09 --- /dev/null +++ b/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/rest/Request.java @@ -0,0 +1,52 @@ +/* + * Copyright 2015 Yan Zhenjie + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yolanda.nohttp.rest; + +/** + *

+ * Extended {@link IParserRequest} class, to increase the method of recording response. + *

+ * Created in Oct 16, 2015 8:22:06 PM. + * + * @param a generic, on behalf of you can accept the result type, .It should be with the {@link OnResponseListener}, {@link Response}. + * @author Yan Zhenjie. + */ +public interface Request extends IParserRequest { + + /** + * Prepare the callback parameter, while waiting for the response callback with thread. + * + * @param what the callback mark. + * @param responseListener {@link OnResponseListener}. + */ + void onPreResponse(int what, OnResponseListener responseListener); + + /** + * The callback mark. + * + * @return Return when {@link #onPreResponse(int, OnResponseListener)} incoming credit. + * @see #onPreResponse(int, OnResponseListener) + */ + int what(); + + /** + * The request of the listener. + * + * @return Return when {@link #onPreResponse(int, OnResponseListener)} incoming credit. + * @see #onPreResponse(int, OnResponseListener) + */ + OnResponseListener responseListener(); +} \ No newline at end of file diff --git a/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/rest/RequestDispatcher.java b/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/rest/RequestDispatcher.java new file mode 100644 index 0000000..e35e4a1 --- /dev/null +++ b/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/rest/RequestDispatcher.java @@ -0,0 +1,170 @@ +/* + * Copyright 2015 Yan Zhenjie + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yolanda.nohttp.rest; + +import android.os.Process; + +import com.yolanda.nohttp.Logger; +import com.yolanda.nohttp.PosterHandler; + +import java.util.concurrent.BlockingQueue; + +/** + *

+ * Request queue polling thread. + *

+ * Created in Oct 19, 2015 8:35:35 AM. + * + * @author Yan Zhenjie. + */ +public class RequestDispatcher extends Thread { + /** + * Request queue. + */ + private final BlockingQueue> mRequestQueue; + /** + * Un finish task queue. + */ + private final BlockingQueue> mUnFinishQueue; + /** + * HTTP request parse interface. + */ + private final IRestParser mIRestParser; + /** + * Whether the current request queue polling thread is out of. + */ + private volatile boolean mQuit = false; + + /** + * Create a request queue polling thread. + * + * @param unFinishQueue un finish queue. + * @param requestQueue request queue. + * @param implRestParser network request task actuator. + */ + public RequestDispatcher(BlockingQueue> unFinishQueue, BlockingQueue> requestQueue, IRestParser implRestParser) { + mUnFinishQueue = unFinishQueue; + mRequestQueue = requestQueue; + mIRestParser = implRestParser; + } + + /** + * Exit polling thread. + */ + public void quit() { + mQuit = true; + interrupt(); + } + + @Override + public void run() { + Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); + while (!mQuit) { + final Request request; + try { + request = mRequestQueue.take(); + } catch (InterruptedException e) { + if (!mQuit) + return; + continue; + } + + if (request.isCanceled()) { + Logger.d(request.url() + " is canceled."); + continue; + } + + final int what = request.what(); + final OnResponseListener responseListener = request.responseListener(); + + request.start(); + // start + final ThreadPoster startThread = new ThreadPoster(what, responseListener); + startThread.onStart(); + PosterHandler.getInstance().post(startThread); + + // request. + Response response = mIRestParser.parserRequest(request); + + // remove it from queue. + mUnFinishQueue.remove(request); + + // finish. + final ThreadPoster finishThread = new ThreadPoster(what, responseListener); + finishThread.onFinished(); + PosterHandler.getInstance().post(finishThread); + request.finish(); + + // response + if (request.isCanceled()) + Logger.d(request.url() + " finish, but it's canceled."); + else { + final ThreadPoster responseThread = new ThreadPoster(what, responseListener); + responseThread.onResponse(response); + PosterHandler.getInstance().post(responseThread); + } + } + } + + @SuppressWarnings({"unchecked", "rawtypes"}) + private class ThreadPoster implements Runnable { + + public static final int COMMAND_START = 0; + public static final int COMMAND_RESPONSE = 1; + public static final int COMMAND_FINISH = 2; + + private final int what; + private final OnResponseListener responseListener; + + private int command; + private Response response; + + public ThreadPoster(int what, OnResponseListener responseListener) { + this.what = what; + this.responseListener = responseListener; + } + + public void onStart() { + this.command = COMMAND_START; + } + + public void onResponse(Response response) { + this.command = COMMAND_RESPONSE; + this.response = response; + } + + public void onFinished() { + this.command = COMMAND_FINISH; + } + + @Override + public void run() { + if (responseListener != null) { + if (command == COMMAND_START) + responseListener.onStart(what); + else if (command == COMMAND_FINISH) + responseListener.onFinish(what); + else if (command == COMMAND_RESPONSE) { + if (response.isSucceed()) { + responseListener.onSucceed(what, response); + } else { + responseListener.onFailed(what, response); + } + } + } + } + } +} diff --git a/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/rest/RequestQueue.java b/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/rest/RequestQueue.java new file mode 100644 index 0000000..535befa --- /dev/null +++ b/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/rest/RequestQueue.java @@ -0,0 +1,147 @@ +/* + * Copyright 2015 Yan Zhenjie + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yolanda.nohttp.rest; + +import com.yolanda.nohttp.Logger; + +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.LinkedBlockingDeque; +import java.util.concurrent.PriorityBlockingQueue; +import java.util.concurrent.atomic.AtomicInteger; + +/** + *

+ * Request Queue. + *

+ * Created in Oct 19, 2015 8:36:22 AM. + * + * @author Yan Zhenjie. + */ +public class RequestQueue { + + private AtomicInteger mInteger = new AtomicInteger(); + /** + * Save un finish task. + */ + private final BlockingQueue> mUnFinishQueue = new LinkedBlockingDeque>(); + /** + * Save request task. + */ + private final BlockingQueue> mRequestQueue = new PriorityBlockingQueue>(); + /** + * HTTP request actuator interface. + */ + private final IRestParser mIRestParser; + + /** + * Request queue polling thread array. + */ + private RequestDispatcher[] mDispatchers; + + /** + * Create request queue manager. + * + * @param iRestParser download the network task execution interface, where you need to implement the download tasks that have been implemented. + * @param threadPoolSize number of thread pool. + */ + public RequestQueue(IRestParser iRestParser, int threadPoolSize) { + mIRestParser = iRestParser; + mDispatchers = new RequestDispatcher[threadPoolSize]; + } + + /** + * Start polling the request queue, a one of the implementation of the download task, if you have started to poll the download queue, then it will stop all the threads, to re create thread + * execution. + */ + public void start() { + stop(); + for (int i = 0; i < mDispatchers.length; i++) { + RequestDispatcher networkDispatcher = new RequestDispatcher(mUnFinishQueue, mRequestQueue, mIRestParser); + mDispatchers[i] = networkDispatcher; + networkDispatcher.start(); + } + } + + /** + * Add a request task to download queue, waiting for execution, if there is no task in the queue or the number of tasks is less than the number of thread pool, will be executed immediately. + * + * @param what the "what" will be the response is returned to you, so you can introduce multiple {@link Request} results in an A with what, please distinguish which is the result of the + * {@link Request}. + * @param request {@link Request} + * @param responseListener {@link OnResponseListener} + * @param {@link T} + */ + public void add(int what, Request request, OnResponseListener responseListener) { + if (request.inQueue()) + Logger.w("This request has been in the queue"); + else { + request.setQueue(mUnFinishQueue); + request.onPreResponse(what, responseListener); + request.setSequence(mInteger.incrementAndGet()); + mUnFinishQueue.add(request); + mRequestQueue.add(request); + } + } + + /** + * Don't start return request queue size. + * + * @return size. + */ + public int unStartSize() { + return mRequestQueue.size(); + } + + /** + * Returns have started but not the end of the request queue size. + * + * @return size. + */ + public int unFinishSize() { + return mUnFinishQueue.size(); + } + + /** + * Polling the queue will not be executed, and this will not be canceled. + */ + public void stop() { + for (RequestDispatcher dispatcher : mDispatchers) + if (dispatcher != null) + dispatcher.quit(); + } + + /** + * All requests for the sign specified in the queue, if you are executing, will interrupt the task + * + * @param sign this sign will be the same as sign's Request, and if it is the same, then cancel the task. + */ + public void cancelBySign(Object sign) { + synchronized (mUnFinishQueue) { + for (Request request : mUnFinishQueue) + request.cancelBySign(sign); + } + } + + /** + * Cancel all requests, Already in the execution of the request can't use this method + */ + public void cancelAll() { + synchronized (mUnFinishQueue) { + for (Request request : mUnFinishQueue) + request.cancel(); + } + } +} \ No newline at end of file diff --git a/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/rest/Response.java b/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/rest/Response.java new file mode 100644 index 0000000..54a7102 --- /dev/null +++ b/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/rest/Response.java @@ -0,0 +1,106 @@ +/* + * Copyright 2015 Yan Zhenjie + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yolanda.nohttp.rest; + +/** + *

Http response, Including header information and response packets.

+ * Created in Oct 15, 2015 8:55:37 PM. + * + * @param a generic, on behalf of you can accept the result type,.It should be with the {@link Request}, {@link OnResponseListener}. + * @author Yan Zhenjie. + */ + +import com.yolanda.nohttp.Headers; + +/** + *

Http response, Including header information and response packets.

+ * Created in Oct 15, 2015 8:55:37 PM. + * + * @param a generic, on behalf of you can accept the result type,.It should be with the {@link Request}, {@link OnResponseListener}. + * @author Yan Zhenjie. + */ +public interface Response { + + /** + * Get the {@code IParserRequest} object. + * + * @return {@link IParserRequest}. + */ + IParserRequest request(); + + /** + * Get the response code of request. + * + * @return response code. + */ + int responseCode(); + + /** + * Get the response message of request. + * + * @return response message. + */ + String responseMessage(); + + /** + * Request is executed successfully. + * + * @return True: Succeed, false: failed. + */ + boolean isSucceed(); + + /** + * Whether the data returned from the cache. + * + * @return True: the data from cache, false: the data from server. + */ + boolean isFromCache(); + + /** + * Get http response headers. + * + * @return {@link Headers}. + */ + Headers getHeaders(); + + /** + * Get request results. + * + * @return {@link T}. + */ + T get(); + + /** + * When the request fail to get the exception type. + * + * @return The exception. + */ + Exception getException(); + + /** + * Gets the tag of request. + * + * @return {@link Object}. + */ + Object getTag(); + + /** + * Gets the millisecond of request. + * + * @return {@link Long}. + */ + long getNetworkMillis(); +} \ No newline at end of file diff --git a/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/rest/RestParser.java b/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/rest/RestParser.java new file mode 100644 index 0000000..a0790af --- /dev/null +++ b/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/rest/RestParser.java @@ -0,0 +1,68 @@ +/* + * Copyright 2015 Yan Zhenjie + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yolanda.nohttp.rest; + +import android.os.SystemClock; + +import com.yolanda.nohttp.Headers; +import com.yolanda.nohttp.error.ParseError; + +/** + *

+ * The response parser, The result of parsing the network layer. + *

+ * Created in Jan 25, 2016 4:17:40 PM. + * + * @author Yan Zhenjie. + */ +public class RestParser implements IRestParser { + + private static RestParser _INSTANCE; + + private final IRestProtocol mIRestProtocol; + + public static IRestParser getInstance(IRestProtocol iRestProtocol) { + synchronized (RestParser.class) { + if (_INSTANCE == null) + _INSTANCE = new RestParser(iRestProtocol); + return _INSTANCE; + } + } + + private RestParser(IRestProtocol iRestProtocol) { + this.mIRestProtocol = iRestProtocol; + } + + @Override + public Response parserRequest(IParserRequest request) { + long startTime = SystemClock.elapsedRealtime(); + ProtocolResult httpResponse = mIRestProtocol.requestNetwork(request); + boolean isFromCache = httpResponse.isFromCache(); + Headers responseHeaders = httpResponse.responseHeaders(); + Exception exception = httpResponse.exception(); + T result = null; + byte[] responseBody = httpResponse.responseBody(); + if (exception == null) { + try { + result = request.parseResponse(responseHeaders, responseBody); + } catch (Throwable e) { + exception = new ParseError("Parse data error: " + e.getMessage()); + } + } + return new RestResponse(request, isFromCache, responseHeaders, result, SystemClock.elapsedRealtime() - startTime, exception); + } + +} diff --git a/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/rest/RestProtocol.java b/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/rest/RestProtocol.java new file mode 100644 index 0000000..806123a --- /dev/null +++ b/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/rest/RestProtocol.java @@ -0,0 +1,202 @@ +/* + * Copyright 2015 Yan Zhenjie + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yolanda.nohttp.rest; + +import com.yolanda.nohttp.BasicConnection; +import com.yolanda.nohttp.Connection; +import com.yolanda.nohttp.Headers; +import com.yolanda.nohttp.cache.Cache; +import com.yolanda.nohttp.cache.CacheEntity; +import com.yolanda.nohttp.error.NotFoundCacheError; +import com.yolanda.nohttp.tools.HeaderUtil; +import com.yolanda.nohttp.tools.IOUtils; + +import java.io.IOException; + +/** + *

+ * Parsing the Http protocol related attributes, complete and the interaction of the network. + *

+ * Created in Jul 28, 2015 7:33:22 PM. + * + * @author Yan Zhenjie. + */ +public class RestProtocol extends BasicConnection implements IRestProtocol { + + private static RestProtocol instance; + + private Cache mCache; + + public static IRestProtocol getInstance(Cache cache) { + synchronized (RestProtocol.class) { + if (instance == null) + instance = new RestProtocol(cache); + return instance; + } + } + + private RestProtocol(Cache cache) { + mCache = cache; + } + + @Override + public ProtocolResult requestNetwork(IProtocolRequest request) { + // Handle cache header. + CacheMode cacheMode = request.getCacheMode(); + String cacheKey = request.getCacheKey(); + CacheEntity cacheEntity = mCache.get(cacheKey); + + ProtocolResult httpResponse; + switch (cacheMode) { + case ONLY_READ_CACHE:// Only read cache. + if (cacheEntity == null) { + return new ProtocolResult(null, null, true, new NotFoundCacheError("The cache mode is ONLY_READ_CACHE, but Did not find the cache.")); + } else { + return new ProtocolResult(cacheEntity.getResponseHeaders(), cacheEntity.getData(), true, null); + } +// break; + case ONLY_REQUEST_NETWORK:// Only request network. + httpResponse = getHttpResponse(request); + break; + case NONE_CACHE_REQUEST_NETWORK:// Cache none request network. + if (cacheEntity == null) { + httpResponse = getHttpResponse(request); + } else { + return new ProtocolResult(cacheEntity.getResponseHeaders(), cacheEntity.getData(), true, null); + } + break; + case REQUEST_NETWORK_FAILED_READ_CACHE:// Request network failed read cache. + if (cacheEntity != null) + setRequestCacheHeader(request, cacheEntity); + httpResponse = getHttpResponse(request); + break; + default:// Default, Comply with the RFC2616. + if (cacheEntity != null) { + if (cacheEntity.getLocalExpire() > System.currentTimeMillis())// Cache valid. + return new ProtocolResult(cacheEntity.getResponseHeaders(), cacheEntity.getData(), true, null); + setRequestCacheHeader(request, cacheEntity); + } + httpResponse = getHttpResponse(request); + break; + } + return handleResponseCache(request, cacheEntity, httpResponse); + } + + /** + * Perform the request before, Handle the cache headers. + * + * @param request the request object. + * @param cacheEntity cached entities. + */ + private void setRequestCacheHeader(IProtocolRequest request, CacheEntity cacheEntity) { + if (cacheEntity == null) { + request.headers().remove(Headers.HEAD_KEY_IF_NONE_MATCH); + request.headers().remove(Headers.HEAD_KEY_IF_MODIFIED_SINCE); + } else { + Headers headers = cacheEntity.getResponseHeaders(); + String eTag = headers.getETag(); + if (eTag != null) { + request.headers().set(Headers.HEAD_KEY_IF_NONE_MATCH, eTag); + } + + long lastModified = headers.getLastModified(); + if (lastModified > 0) { + request.headers().set(Headers.HEAD_KEY_IF_MODIFIED_SINCE, HeaderUtil.formatMillisToGMT(lastModified)); + } + } + } + + /** + * Handle retries, and complete the request network here. + * + * @param request request object. + * @return {@link ProtocolResult}. + */ + private ProtocolResult getHttpResponse(IProtocolRequest request) { + byte[] responseBody = null; + Connection connection = getConnection(request); + Headers responseHeaders = connection.responseHeaders(); + Exception exception = connection.exception(); + if (exception == null) { + if (hasResponseBody(request.getRequestMethod(), responseHeaders.getResponseCode())) + try { + responseBody = IOUtils.toByteArray(connection.serverStream()); + } catch (IOException e) {// IOException. + exception = e; + } + } + IOUtils.closeQuietly(connection); + return new ProtocolResult(responseHeaders, responseBody, exception != null, exception); + } + + /** + * Process the response cache. + * + * @param request {@link IProtocolRequest}, The original request object. + * @param localCacheEntity {@link CacheEntity}, This request the corresponding local cached entities, which can be null. + * @param httpResponse {@link ProtocolResult}, Request the server to generate the response entity. + * @return {@link RestProtocol}, According to the response headers and local server cache to regenerate the response entity, you should use this response entity. + */ + private ProtocolResult handleResponseCache(IProtocolRequest request, CacheEntity localCacheEntity, ProtocolResult httpResponse) { + boolean isFromCache = false; + Headers responseHeaders = httpResponse.responseHeaders(); + byte[] responseBody = httpResponse.responseBody(); + Exception exception = httpResponse.exception(); + + CacheMode cacheMode = request.getCacheMode(); + + int responseCode = responseHeaders.getResponseCode(); + if (exception == null) {// 请求成功 + if (responseCode == 304) { + isFromCache = true; + + if (localCacheEntity == null) { // Fix server error for 304. + responseBody = new byte[0]; + } else { + // Update response header. + localCacheEntity.getResponseHeaders().setAll(responseHeaders); + responseHeaders = localCacheEntity.getResponseHeaders(); + + // Update localExpires. + localCacheEntity.setLocalExpire(HeaderUtil.getLocalExpires(responseHeaders)); + + responseBody = localCacheEntity.getData(); + } + } else if (responseBody != null) {// Redirect data need cache ? + if (localCacheEntity == null) { + localCacheEntity = HeaderUtil.parseCacheHeaders(responseHeaders, responseBody, !cacheMode.isStandardHttpProtocol());// Standard protocol not force. + // Maybe null: Http CacheControl: (no-cache || no-store) && !cacheMode.isStandardHttpProtocol(). + } else { + localCacheEntity.getResponseHeaders().setAll(responseHeaders); + + // Update localExpires. + localCacheEntity.setLocalExpire(HeaderUtil.getLocalExpires(responseHeaders)); + + localCacheEntity.setData(responseBody); + } + } + if (localCacheEntity != null) + mCache.replace(request.getCacheKey(), localCacheEntity); + + } else if (cacheMode == CacheMode.REQUEST_NETWORK_FAILED_READ_CACHE && localCacheEntity != null) { + exception = null; + isFromCache = true; + responseHeaders = localCacheEntity.getResponseHeaders(); + responseBody = localCacheEntity.getData(); + } + return new ProtocolResult(responseHeaders, responseBody, isFromCache, exception); + } +} \ No newline at end of file diff --git a/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/rest/RestRequest.java b/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/rest/RestRequest.java new file mode 100644 index 0000000..9a16199 --- /dev/null +++ b/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/rest/RestRequest.java @@ -0,0 +1,74 @@ +/* + * Copyright 2015 Yan Zhenjie + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yolanda.nohttp.rest; + +import com.yolanda.nohttp.RequestMethod; + +/** + *

+ * The realization method of the parameters. + *

+ * Created in Oct 20, 2015 4:24:27 PM. + * + * @param a generics, regulated the analytic results of the Request.It should be with the {@link Response}, {@link OnResponseListener}. + * @author Yan Zhenjie. + */ +public abstract class RestRequest extends ParseRequest implements Request { + + /** + * The callback mark. + */ + private int what; + /** + * The request of the listener. + */ + private OnResponseListener responseListener; + + /** + * Create a request, RequestMethod is {@link RequestMethod#GET}. + * + * @param url request address, like: http://www.google.com. + */ + public RestRequest(String url) { + this(url, RequestMethod.GET); + } + + /** + * Create a request + * + * @param url request address, like: http://www.google.com. + * @param requestMethod request method, like {@link RequestMethod#GET}, {@link RequestMethod#POST}. + */ + public RestRequest(String url, RequestMethod requestMethod) { + super(url, requestMethod); + } + + @Override + public void onPreResponse(int what, OnResponseListener responseListener) { + this.what = what; + this.responseListener = responseListener; + } + + @Override + public int what() { + return what; + } + + @Override + public OnResponseListener responseListener() { + return responseListener; + } +} \ No newline at end of file diff --git a/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/rest/RestResponse.java b/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/rest/RestResponse.java new file mode 100644 index 0000000..4bc34e4 --- /dev/null +++ b/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/rest/RestResponse.java @@ -0,0 +1,150 @@ +/* + * Copyright 2015 Yan Zhenjie + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yolanda.nohttp.rest; + +import com.yolanda.nohttp.Headers; + +import java.util.List; +import java.util.Set; + +/** + *

In response to the class, use generic compatibility with all I to type, and put the parsing operation in {@link Request}.

+ * Created in Oct 12, 2015 1:00:46 PM. + * + * @author Yan Zhenjie. + */ +public class RestResponse implements Response { + + /** + * Corresponding request URL. + */ + private IParserRequest request; + + /** + * Whether from the cache. + */ + private final boolean isFromCache; + + /** + * Http response Headers + */ + private final Headers headers; + + /** + * Corresponding response results. + */ + private final T result; + /** + * Millisecond of request. + */ + private final long mNetworkMillis; + /** + * The error message. + */ + private Exception mException; + + /** + * Create succeed response. + * + * @param request {@link Request}. + * @param isFromCache data is come from cache. + * @param headers response header. + * @param result result. + * @param millis request time. + * @param e exception. + * @return {@link com.yolanda.nohttp.rest.Response}. + */ + RestResponse(IParserRequest request, boolean isFromCache, Headers headers, T result, long millis, Exception e) { + this.request = request; + this.isFromCache = isFromCache; + this.headers = headers; + this.result = result; + this.mNetworkMillis = millis; + this.mException = e; + } + + @Override + public IParserRequest request() { + return request; + } + + @Override + public int responseCode() { + return headers.getResponseCode(); + } + + @Override + public String responseMessage() { + return headers.getResponseMessage(); + } + + @Override + public boolean isSucceed() { + return this.mException == null; + } + + @Override + public boolean isFromCache() { + return isFromCache; + } + + @Override + public Headers getHeaders() { + return headers; + } + + @Override + public Object getTag() { + return this.request.getTag(); + } + + @Override + public T get() { + return result; + } + + @Override + public Exception getException() { + return mException; + } + + @Override + public long getNetworkMillis() { + return mNetworkMillis; + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + Headers headers = getHeaders(); + if (headers != null) { + Set keys = headers.keySet(); + for (String key : keys) { + List values = headers.getValues(key); + for (String value : values) { + if (key != null) { + builder.append(key).append(": "); + } + builder.append(value).append("\n"); + } + } + } + T result = get(); + if (result != null) + builder.append(result.toString()); + return builder.toString(); + } +} diff --git a/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/rest/SimpleResponseListener.java b/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/rest/SimpleResponseListener.java new file mode 100644 index 0000000..46fdc2b --- /dev/null +++ b/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/rest/SimpleResponseListener.java @@ -0,0 +1,40 @@ +/* + * Copyright © Yan Zhenjie. All Rights Reserved + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yolanda.nohttp.rest; + +/** + *

Simple {@link SimpleResponseListener}.

+ * Created on 2016/6/7. + * + * @author Yan Zhenjie. + */ +public abstract class SimpleResponseListener implements OnResponseListener { + @Override + public void onStart(int what) { + } + + @Override + public void onSucceed(int what, Response response) { + } + + @Override + public void onFailed(int what, Response response) { + } + + @Override + public void onFinish(int what) { + } +} diff --git a/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/rest/StringRequest.java b/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/rest/StringRequest.java new file mode 100644 index 0000000..8019352 --- /dev/null +++ b/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/rest/StringRequest.java @@ -0,0 +1,55 @@ +/* + * Copyright 2015 Yan Zhenjie + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yolanda.nohttp.rest; + +import com.yolanda.nohttp.Headers; +import com.yolanda.nohttp.RequestMethod; +import com.yolanda.nohttp.tools.HeaderUtil; +import com.yolanda.nohttp.tools.IOUtils; + +/** + * Created in Jul 28, 2015 7:33:52 PM. + * + * @author Yan Zhenjie. + */ +public class StringRequest extends com.yolanda.nohttp.rest.RestRequest { + + public StringRequest(String url) { + this(url, RequestMethod.GET); + } + + public StringRequest(String url, RequestMethod requestMethod) { + super(url, requestMethod); + } + + @Override + public String parseResponse(Headers responseHeaders, byte[] responseBody) throws Throwable { + return parseResponseString(responseHeaders, responseBody); + } + + /** + * Parse http response to string. + * + * @param responseHeaders header from http response. + * @param responseBody byteArray from http response. + * @return result fro response. + */ + public static String parseResponseString(Headers responseHeaders, byte[] responseBody) { + if (responseBody == null || responseBody.length == 0) + return ""; + return IOUtils.toString(responseBody, HeaderUtil.parseHeadValue(responseHeaders.getContentType(), Headers.HEAD_KEY_CONTENT_TYPE, "")); + } +} \ No newline at end of file diff --git a/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/tools/AndroidVersion.java b/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/tools/AndroidVersion.java new file mode 100644 index 0000000..79b4f28 --- /dev/null +++ b/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/tools/AndroidVersion.java @@ -0,0 +1,139 @@ +/* + * Copyright 2015 Yan Zhenjie + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yolanda.nohttp.tools; + +/** + * Created by Yan Zhenjie on 2016/3/17. + * + * @author Yan Zhenjie. + */ +public class AndroidVersion { + + /** + * The value is {@value #BASE} + */ + public static final int BASE = 1; + + /** + * The value is {@value #BASE_1_1} + */ + public static final int BASE_1_1 = 2; + + /** + * The value is {@value #CUPCAKE} + */ + public static final int CUPCAKE = 3; + + /** + * The value is {@value #DONUT} + */ + public static final int DONUT = 4; + + /** + * The value is {@value #ECLAIR} + */ + public static final int ECLAIR = 5; + + /** + * The value is {@value #ECLAIR_0_1} + */ + public static final int ECLAIR_0_1 = 6; + + /** + * The value is {@value #ECLAIR_MR1} + */ + public static final int ECLAIR_MR1 = 7; + + /** + * The value is {@value #FROYO} + */ + public static final int FROYO = 8; + + /** + * The value is {@value #GINGERBREAD} + */ + public static final int GINGERBREAD = 9; + + /** + * The value is {@value #GINGERBREAD_MR1} + */ + public static final int GINGERBREAD_MR1 = 10; + + /** + * The value is {@value #HONEYCOMB} + */ + public static final int HONEYCOMB = 11; + + /** + * The value is {@value #HONEYCOMB_MR1} + */ + public static final int HONEYCOMB_MR1 = 12; + + /** + * The value is {@value #HONEYCOMB_MR2} + */ + public static final int HONEYCOMB_MR2 = 13; + + /** + * The value is {@value #ICE_CREAM_SANDWICH} + */ + public static final int ICE_CREAM_SANDWICH = 14; + + /** + * The value is {@value #ICE_CREAM_SANDWICH_MR1} + */ + public static final int ICE_CREAM_SANDWICH_MR1 = 15; + + /** + * The value is {@value #JELLY_BEAN} + */ + public static final int JELLY_BEAN = 16; + + /** + * The value is {@value #JELLY_BEAN_MR1} + */ + public static final int JELLY_BEAN_MR1 = 17; + + /** + * The value is {@value #JELLY_BEAN_MR2} + */ + public static final int JELLY_BEAN_MR2 = 18; + + /** + * The value is {@value #KITKAT} + */ + public static final int KITKAT = 19; + + /** + * The value is {@value #KITKAT_WATCH} + */ + public static final int KITKAT_WATCH = 20; + + /** + * The value is {@value #LOLLIPOP} + */ + public static final int LOLLIPOP = 21; + + /** + * The value is {@value #LOLLIPOP_MR1} + */ + public static final int LOLLIPOP_MR1 = 22; + + /** + * The value is {@value #M} + */ + public static final int M = 23; +} diff --git a/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/tools/BasicMultiValueMap.java b/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/tools/BasicMultiValueMap.java new file mode 100644 index 0000000..ad5bb3f --- /dev/null +++ b/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/tools/BasicMultiValueMap.java @@ -0,0 +1,133 @@ +/* + * Copyright 2015 Yan Zhenjie + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yolanda.nohttp.tools; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * Created on 2016/6/23. + * + * @author Yan Zhenjie. + */ +public class BasicMultiValueMap implements MultiValueMap { + + private Map> mSource; + + public BasicMultiValueMap(Map> source) { + mSource = source; + } + + @Override + public void add(K key, V value) { + if (key != null) { + if (!mSource.containsKey(key)) + mSource.put(key, new ArrayList(2)); + mSource.get(key).add(value); + } + } + + @Override + public void add(K key, List values) { + for (V value : values) { + add(key, value); + } + } + + @Override + public void set(K key, V value) { + mSource.remove(key); + add(key, value); + } + + @Override + public void set(K key, List values) { + mSource.remove(key); + add(key, values); + } + + @Override + public void set(Map> map) { + mSource.clear(); + for (Map.Entry> entry : map.entrySet()) { + add(entry.getKey(), entry.getValue()); + } + } + + @Override + public List remove(K key) { + return mSource.remove(key); + } + + @Override + public void clear() { + mSource.clear(); + } + + @Override + public Set keySet() { + return mSource.keySet(); + } + + @Override + public List values() { + List allValues = new ArrayList(); + Set keySet = mSource.keySet(); + for (K key : keySet) { + allValues.addAll(mSource.get(key)); + } + return allValues; + } + + @Override + public List getValues(K key) { + return mSource.get(key); + } + + @Override + public Set>> entrySet() { + return mSource.entrySet(); + } + + @Override + public V getValue(K key, int index) { + List values = mSource.get(key); + if (values != null && index < values.size()) + return values.get(index); + return null; + } + + @Override + public int size() { + return mSource.size(); + } + + @Override + public boolean isEmpty() { + return mSource.isEmpty(); + } + + @Override + public boolean containsKey(K key) { + return mSource.containsKey(key); + } + + public Map> getSource() { + return mSource; + } +} \ No newline at end of file diff --git a/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/tools/CounterOutputStream.java b/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/tools/CounterOutputStream.java new file mode 100644 index 0000000..a666d32 --- /dev/null +++ b/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/tools/CounterOutputStream.java @@ -0,0 +1,76 @@ +/* + * Copyright 2015 Yan Zhenjie + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yolanda.nohttp.tools; + +import java.io.IOException; +import java.io.OutputStream; +import java.util.concurrent.atomic.AtomicLong; + +/** + *

Measure the length of the flow.

+ * Created in Dec 17, 2015 2:57:46 PM. + * + * @author Yan Zhenjie. + */ +public class CounterOutputStream extends OutputStream { + + private final AtomicLong length = new AtomicLong(0L); + + public CounterOutputStream() { + } + + public void write(long count) { + if (count > 0) + length.addAndGet(count); + } + + public long get() { + return length.get(); + } + + @Override + public void write(int oneByte) throws IOException { + length.addAndGet(oneByte); + } + + @Override + public void write(byte[] buffer) throws IOException { + length.addAndGet(buffer.length); + } + + @Override + public void write(byte[] buffer, int offset, int count) throws IOException { + length.addAndGet(count); + } + + /** + * Didn't do anything here. + * + * @throws IOException nothing. + */ + @Override + public void close() throws IOException { + } + + /** + * Didn't do anything here. + * + * @throws IOException nothing. + */ + @Override + public void flush() throws IOException { + } +} diff --git a/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/tools/Encryption.java b/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/tools/Encryption.java new file mode 100644 index 0000000..ffdef99 --- /dev/null +++ b/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/tools/Encryption.java @@ -0,0 +1,58 @@ +/* + * Copyright 2015 Yan Zhenjie + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yolanda.nohttp.tools; + +import com.yolanda.nohttp.Logger; + +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; + +/** + * Created in 2016/4/10 11:27. + * + * @author Yan Zhenjie. + */ +public class Encryption { + + /** + * Get the MD5 value of string. + * + * @param content the target string. + * @return the MD5 value. + */ + public static String getMa5ForString(String content) { + StringBuffer md5Buffer = new StringBuffer(); + try { + MessageDigest digest = MessageDigest.getInstance("MD5"); + byte[] tempBytes = digest.digest(content.getBytes()); + int digital; + for (int i = 0; i < tempBytes.length; i++) { + digital = tempBytes[i]; + if (digital < 0) { + digital += 256; + } + if (digital < 16) { + md5Buffer.append("0"); + } + md5Buffer.append(Integer.toHexString(digital)); + } + } catch (NoSuchAlgorithmException e) { + Logger.e(e); + } + return md5Buffer.toString(); + } + +} diff --git a/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/tools/HeaderUtil.java b/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/tools/HeaderUtil.java new file mode 100644 index 0000000..f8e4154 --- /dev/null +++ b/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/tools/HeaderUtil.java @@ -0,0 +1,220 @@ +/* + * Copyright 2015 Yan Zhenjie + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yolanda.nohttp.tools; + +import android.text.TextUtils; + +import com.yolanda.nohttp.Headers; +import com.yolanda.nohttp.cache.CacheEntity; +import com.yolanda.nohttp.rest.ProtocolResult; + +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.Locale; +import java.util.StringTokenizer; +import java.util.TimeZone; + +/** + * Created on 2016/6/21. + * + * @author Yan Zhenjie. + */ +public class HeaderUtil { + + /** + * Accept-Language. + */ + private static String acceptLanguageInstance; + + /** + * Format of http head. + */ + public static final String FORMAT_HTTP_DATA = "EEE, dd MMM y HH:mm:ss 'GMT'"; + + /** + * Commmon TimeZone for GMT. + */ + public static final TimeZone GMT_TIME_ZONE = TimeZone.getTimeZone("GMT"); + + /** + * Parsing the TimeZone of time in milliseconds. + * + * @param gmtTime GRM Time, Format such as: {@value #FORMAT_HTTP_DATA}. + * @return The number of milliseconds from 1970.1.1. + * @throws ParseException if an error occurs during parsing. + */ + public static long parseGMTToMillis(String gmtTime) throws ParseException { + SimpleDateFormat formatter = new SimpleDateFormat(FORMAT_HTTP_DATA, Locale.US); + formatter.setTimeZone(GMT_TIME_ZONE); + Date date = formatter.parse(gmtTime); + return date.getTime(); + } + + /** + * Parsing the TimeZone of time from milliseconds. + * + * @param milliseconds the number of milliseconds from 1970.1.1. + * @return GRM Time, Format such as: {@value #FORMAT_HTTP_DATA}. + */ + public static String formatMillisToGMT(long milliseconds) { + Date date = new Date(milliseconds); + SimpleDateFormat simpleDateFormat = new SimpleDateFormat(FORMAT_HTTP_DATA, Locale.US); + simpleDateFormat.setTimeZone(GMT_TIME_ZONE); + return simpleDateFormat.format(date); + } + + /** + * Returned the local number of milliseconds after 100. + * + * @return Long format time. + */ + public static long getMaxExpiryMillis() { + return System.currentTimeMillis() + 1000L * 60L * 60L * 24L * 365L * 100L; + } + + /** + * Create acceptLanguage. + * + * @return Returns the client can accept the language types. Such as:zh-CN,zh. + */ + public static String systemAcceptLanguage() { + if (TextUtils.isEmpty(acceptLanguageInstance)) { + Locale locale = Locale.getDefault(); + String language = locale.getLanguage(); + String country = locale.getCountry(); + StringBuilder acceptLanguageBuilder = new StringBuilder(language); + if (!TextUtils.isEmpty(country)) + acceptLanguageBuilder.append('-').append(country).append(',').append(language); + acceptLanguageInstance = acceptLanguageBuilder.toString(); + } + return acceptLanguageInstance; + } + + /** + * A value of the header information. + * + * @param content like {@code text/html;charset=utf-8}. + * @param key like {@code charset}. + * @param defaultValue list {@code utf-8}. + * @return If you have a value key, you will return the parsed value if you don't return the default value. + */ + public static String parseHeadValue(String content, String key, String defaultValue) { + if (!TextUtils.isEmpty(content) && !TextUtils.isEmpty(key)) { + StringTokenizer stringTokenizer = new StringTokenizer(content, ";"); + while (stringTokenizer.hasMoreElements()) { + String valuePair = stringTokenizer.nextToken(); + int index = valuePair.indexOf('='); + if (index > 0) { + String name = valuePair.substring(0, index).trim(); + if (key.equalsIgnoreCase(name)) { + defaultValue = valuePair.substring(index + 1).trim(); + break; + } + } + } + } + return defaultValue; + } + + /** + * Whether the content has been compressed. + * + * @param contentEncoding read the data from the server's head. + * @return True: yes, false: no inclusion. + */ + public static boolean isGzipContent(String contentEncoding) { + return contentEncoding != null && contentEncoding.contains("gzip"); + } + + /** + * Extracts a {@link CacheEntity} from a {@link ProtocolResult}. + * + * @param responseHeaders response headers. + * @param responseBody response data. + * @param forceCache whether mandatory cache. + * @return Cache entity. + */ + public static CacheEntity parseCacheHeaders(Headers responseHeaders, byte[] responseBody, boolean forceCache) { + String cacheControl = responseHeaders.getCacheControl(); + if (!forceCache && cacheControl != null && (cacheControl.contains("no-cache") || cacheControl.contains("no-store"))) + return null; + long localExpire = getLocalExpires(responseHeaders); + CacheEntity cacheEntity = new CacheEntity(); + cacheEntity.setData(responseBody); + cacheEntity.setLocalExpire(localExpire); + cacheEntity.setResponseHeaders(responseHeaders); + return cacheEntity; + } + + /** + * Parse the response of the cache is valid time. + * + * @param responseHeaders http response header. + * @return Time corresponding milliseconds. + */ + public static long getLocalExpires(Headers responseHeaders) { + long now = System.currentTimeMillis(); + + long date = responseHeaders.getDate(); + long expires = responseHeaders.getExpiration(); + + long maxAge = 0; + long staleWhileRevalidate = 0; + boolean mustRevalidate = false; + + String cacheControl = responseHeaders.getCacheControl(); + if (!TextUtils.isEmpty(cacheControl)) { + StringTokenizer tokens = new StringTokenizer(cacheControl, ","); + while (tokens.hasMoreTokens()) { + String token = tokens.nextToken().trim().toLowerCase(Locale.getDefault()); + if ((token.equals("no-cache") || token.equals("no-store"))) { + return 0; + } else if (token.startsWith("max-age=")) { + try { + maxAge = Long.parseLong(token.substring(8)); + } catch (Exception e) { + } + } else if (token.startsWith("stale-while-revalidate=")) { + try { + staleWhileRevalidate = Long.parseLong(token.substring(23)); + } catch (Exception e) { + } + } else if (token.equals("must-revalidate") || token.equals("proxy-revalidate")) { + mustRevalidate = true; + } + } + } + + long localExpire = 0;// Local expires time of cache. + + // If must-revalidate, It must be from the server to validate expired. + // Have CacheControl. + if (!TextUtils.isEmpty(cacheControl)) { + localExpire = now + maxAge * 1000; + if (mustRevalidate) + localExpire += staleWhileRevalidate * 1000; + } + + // If the server through control the cache Expires. + if ((localExpire == 0 || localExpire == now) && date > 0 && expires >= date) { + localExpire = now + (expires - date); + } + + return localExpire; + } + +} diff --git a/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/tools/IOUtils.java b/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/tools/IOUtils.java new file mode 100644 index 0000000..ab64e41 --- /dev/null +++ b/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/tools/IOUtils.java @@ -0,0 +1,584 @@ +/* + * Copyright 2015 Yan Zhenjie + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yolanda.nohttp.tools; + +import android.os.Build; +import android.os.StatFs; +import android.text.TextUtils; + +import com.yolanda.nohttp.Logger; + +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.CharArrayWriter; +import java.io.Closeable; +import java.io.File; +import java.io.Flushable; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.io.Reader; +import java.io.UnsupportedEncodingException; +import java.io.Writer; +import java.lang.reflect.Method; +import java.net.HttpURLConnection; +import java.util.ArrayList; +import java.util.List; + +/** + * Created in 2016/4/12 21:21. + * + * @author Yan Zhenjie. + */ +public class IOUtils { + + public static void closeQuietly(Closeable closeable) { + if (closeable != null) + try { + closeable.close(); + } catch (Exception e) { + Logger.w(e); + } + } + + public static void flushQuietly(Flushable flushable) { + if (flushable != null) + try { + flushable.flush(); + } catch (Exception e) { + Logger.w(e); + } + } + + public static void closeQuietly(HttpURLConnection urlConnection) { + if (urlConnection != null) + urlConnection.disconnect(); + } + + public static BufferedInputStream toBufferedInputStream(InputStream inputStream) { + return inputStream instanceof BufferedInputStream ? (BufferedInputStream) inputStream : new BufferedInputStream(inputStream); + } + + public static BufferedOutputStream toBufferedOutputStream(OutputStream outputStream) { + return outputStream instanceof BufferedOutputStream ? (BufferedOutputStream) outputStream : new BufferedOutputStream(outputStream); + } + + public static BufferedReader toBufferedReader(Reader reader) { + return reader instanceof BufferedReader ? (BufferedReader) reader : new BufferedReader(reader); + } + + public static BufferedWriter toBufferedWriter(Writer writer) { + return writer instanceof BufferedWriter ? (BufferedWriter) writer : new BufferedWriter(writer); + } + + public static InputStream toInputStream(CharSequence input) { + return new ByteArrayInputStream(input.toString().getBytes()); + } + + public static InputStream toInputStream(CharSequence input, String encoding) throws UnsupportedEncodingException { + byte[] bytes = input.toString().getBytes(encoding); + return new ByteArrayInputStream(bytes); + } + + public static String toString(InputStream input) throws IOException { + return new String(toByteArray(input)); + } + + public static String toString(InputStream input, String encoding) throws IOException { + return new String(toByteArray(input), encoding); + } + + public static String toString(Reader input) throws IOException { + return new String(toByteArray(input)); + } + + public static String toString(Reader input, String encoding) throws IOException { + return new String(toByteArray(input), encoding); + } + + public static String toString(byte[] byteArray) { + return new String(byteArray); + } + + public static String toString(byte[] byteArray, String encoding) { + try { + return new String(byteArray, encoding); + } catch (UnsupportedEncodingException e) { + return new String(byteArray); + } + } + + public static byte[] toByteArray(CharSequence input) { + if (input == null) + return new byte[0]; + return input.toString().getBytes(); + } + + public static byte[] toByteArray(CharSequence input, String encoding) throws UnsupportedEncodingException { + if (input == null) + return new byte[0]; + else + return input.toString().getBytes(encoding); + } + + public static byte[] toByteArray(InputStream input) throws IOException { + ByteArrayOutputStream output = new ByteArrayOutputStream(); + write(input, output); + output.close(); + return output.toByteArray(); + } + + public static byte[] toByteArray(InputStream input, int size) throws IOException { + if (size < 0) + throw new IllegalArgumentException("Size must be equal or greater than zero: " + size); + + if (size == 0) + return new byte[0]; + + byte[] data = new byte[size]; + int offset = 0; + int byteCount; + while ((offset < size) && (byteCount = input.read(data, offset, size - offset)) != -1) + offset += byteCount; + + if (offset != size) + throw new IOException("Unexpected byte count size. current: " + offset + ", excepted: " + size); + return data; + } + + public static byte[] toByteArray(Reader input) throws IOException { + ByteArrayOutputStream output = new ByteArrayOutputStream(); + write(input, output); + output.close(); + return output.toByteArray(); + } + + public static byte[] toByteArray(Reader input, String encoding) throws IOException { + ByteArrayOutputStream output = new ByteArrayOutputStream(); + write(input, output, encoding); + output.close(); + return output.toByteArray(); + } + + public static char[] toCharArray(CharSequence input) throws IOException { + CharArrayWriter output = new CharArrayWriter(); + write(input, output); + return output.toCharArray(); + } + + public static char[] toCharArray(InputStream input) throws IOException { + CharArrayWriter output = new CharArrayWriter(); + write(input, output); + return output.toCharArray(); + } + + public static char[] toCharArray(InputStream input, String encoding) throws IOException { + CharArrayWriter output = new CharArrayWriter(); + write(input, output, encoding); + return output.toCharArray(); + } + + public static char[] toCharArray(Reader input) throws IOException { + CharArrayWriter output = new CharArrayWriter(); + write(input, output); + return output.toCharArray(); + } + + public static List readLines(InputStream input, String encoding) throws IOException { + Reader reader = new InputStreamReader(input, encoding); + return readLines(reader); + } + + public static List readLines(InputStream input) throws IOException { + Reader reader = new InputStreamReader(input); + return readLines(reader); + } + + public static List readLines(Reader input) throws IOException { + BufferedReader reader = toBufferedReader(input); + List list = new ArrayList(); + String line = reader.readLine(); + while (line != null) { + list.add(line); + line = reader.readLine(); + } + return list; + } + + public static void write(byte[] data, OutputStream output) throws IOException { + if (data != null) + output.write(data); + } + + public static void write(byte[] data, Writer output) throws IOException { + if (data != null) + output.write(new String(data)); + } + + public static void write(byte[] data, Writer output, String encoding) throws IOException { + if (data != null) + output.write(new String(data, encoding)); + } + + public static void write(char[] data, Writer output) throws IOException { + if (data != null) + output.write(data); + } + + public static void write(char[] data, OutputStream output) throws IOException { + if (data != null) + output.write(new String(data).getBytes()); + } + + public static void write(char[] data, OutputStream output, String encoding) throws IOException { + if (data != null) + output.write(new String(data).getBytes(encoding)); + } + + public static void write(CharSequence data, Writer output) throws IOException { + if (data != null) + output.write(data.toString()); + } + + public static void write(CharSequence data, OutputStream output) throws IOException { + if (data != null) + output.write(data.toString().getBytes()); + } + + public static void write(CharSequence data, OutputStream output, String encoding) throws IOException { + if (data != null) + output.write(data.toString().getBytes(encoding)); + } + + public static void write(InputStream inputStream, OutputStream outputStream) throws IOException { + int len; + byte[] buffer = new byte[4096]; + while ((len = inputStream.read(buffer)) != -1) + outputStream.write(buffer, 0, len); + } + + public static void write(Reader input, OutputStream output) throws IOException { + Writer out = new OutputStreamWriter(output); + write(input, out); + out.flush(); + } + + public static void write(InputStream input, Writer output) throws IOException { + Reader in = new InputStreamReader(input); + write(in, output); + } + + public static void write(Reader input, OutputStream output, String encoding) throws IOException { + Writer out = new OutputStreamWriter(output, encoding); + write(input, out); + out.flush(); + } + + public static void write(InputStream input, OutputStream output, String encoding) throws IOException { + Reader in = new InputStreamReader(input, encoding); + write(in, output); + } + + public static void write(InputStream input, Writer output, String encoding) throws IOException { + Reader in = new InputStreamReader(input, encoding); + write(in, output); + } + + public static void write(Reader input, Writer output) throws IOException { + int len; + char[] buffer = new char[4096]; + while (-1 != (len = input.read(buffer))) + output.write(buffer, 0, len); + } + + public static boolean contentEquals(InputStream input1, InputStream input2) throws IOException { + input1 = toBufferedInputStream(input1); + input2 = toBufferedInputStream(input2); + + int ch = input1.read(); + while (-1 != ch) { + int ch2 = input2.read(); + if (ch != ch2) { + return false; + } + ch = input1.read(); + } + + int ch2 = input2.read(); + return ch2 == -1; + } + + public static boolean contentEquals(Reader input1, Reader input2) throws IOException { + input1 = toBufferedReader(input1); + input2 = toBufferedReader(input2); + + int ch = input1.read(); + while (-1 != ch) { + int ch2 = input2.read(); + if (ch != ch2) { + return false; + } + ch = input1.read(); + } + + int ch2 = input2.read(); + return ch2 == -1; + } + + public static boolean contentEqualsIgnoreEOL(Reader input1, Reader input2) throws IOException { + BufferedReader br1 = toBufferedReader(input1); + BufferedReader br2 = toBufferedReader(input2); + + String line1 = br1.readLine(); + String line2 = br2.readLine(); + while ((line1 != null) && (line2 != null) && (line1.equals(line2))) { + line1 = br1.readLine(); + line2 = br2.readLine(); + } + return line1 == null ? false : line2 == null ? true : line1.equals(line2); + } + + /** + * Access to a directory available size. + * + * @param path path. + * @return space size. + */ + public static long getDirSize(String path) { + StatFs stat = new StatFs(path); + if (Build.VERSION.SDK_INT >= AndroidVersion.JELLY_BEAN_MR2) + return getStatFsSize(stat, "getBlockSizeLong", "getAvailableBlocksLong"); + else + return getStatFsSize(stat, "getBlockSize", "getAvailableBlocks"); + } + + private static long getStatFsSize(StatFs statFs, String blockSizeMethod, String availableBlocksMethod) { + try { + Method getBlockSizeMethod = statFs.getClass().getMethod(blockSizeMethod); + getBlockSizeMethod.setAccessible(true); + + Method getAvailableBlocksMethod = statFs.getClass().getMethod(availableBlocksMethod); + getAvailableBlocksMethod.setAccessible(true); + + long blockSize, availableBlocks; + if (Build.VERSION.SDK_INT >= AndroidVersion.JELLY_BEAN_MR2) { + blockSize = (Long) getBlockSizeMethod.invoke(statFs); + availableBlocks = (Long) getAvailableBlocksMethod.invoke(statFs); + } else { + blockSize = (Integer) getBlockSizeMethod.invoke(statFs); + availableBlocks = (Integer) getAvailableBlocksMethod.invoke(statFs); + } + return blockSize * availableBlocks; + } catch (Throwable e) { + } + return 0; + } + + /** + * If the folder can be written. + * + * @param path path. + * @return True: success, or false: failure. + */ + public static boolean canWrite(String path) { + return new File(path).canWrite(); + } + + /** + * If the folder can be read. + * + * @param path path. + * @return True: success, or false: failure. + */ + public static boolean canRead(String path) { + return new File(path).canRead(); + } + + /** + * Create a folder, If the folder exists is not created. + * + * @param folderPath folder path. + * @return True: success, or false: failure. + */ + public static boolean createFolder(String folderPath) { + if (!TextUtils.isEmpty(folderPath)) { + File folder = new File(folderPath); + return createFolder(folder); + } + return false; + } + + /** + * Create a folder, If the folder exists is not created. + * + * @param targetFolder folder path. + * @return True: success, or false: failure. + */ + public static boolean createFolder(File targetFolder) { + if (targetFolder.exists()) { + if (targetFolder.isDirectory()) + return true; + targetFolder.delete(); + } + return targetFolder.mkdirs(); + } + + /** + * Create a folder, If the folder exists is not created. + * + * @param folderPath folder path. + * @return True: success, or false: failure. + */ + public static boolean createNewFolder(String folderPath) { + return deleteFolder(folderPath) && createFolder(folderPath); + } + + /** + * Create a folder, If the folder exists is not created. + * + * @param targetFolder folder path. + * @return True: success, or false: failure. + */ + public static boolean createNewFolder(File targetFolder) { + return deleteFolder(targetFolder) && createFolder(targetFolder); + } + + /** + * Create a file, If the file exists is not created. + * + * @param filePath file path. + * @return True: success, or false: failure. + */ + public static boolean createFile(String filePath) { + if (!TextUtils.isEmpty(filePath)) { + File file = new File(filePath); + return createFile(file); + } + return false; + } + + /** + * Create a file, If the file exists is not created. + * + * @param targetFile file. + * @return True: success, or false: failure. + */ + public static boolean createFile(File targetFile) { + if (targetFile.exists()) { + if (targetFile.isFile()) + return true; + targetFile.delete(); + } + try { + return targetFile.createNewFile(); + } catch (IOException e) { + return false; + } + } + + /** + * Create a new file, if the file exists, delete and create again. + * + * @param filePath file path. + * @return True: success, or false: failure. + */ + public static boolean createNewFile(String filePath) { + if (!TextUtils.isEmpty(filePath)) { + File file = new File(filePath); + return createNewFile(file); + } + return false; + } + + /** + * Create a new file, if the file exists, delete and create again. + * + * @param targetFile file. + * @return True: success, or false: failure. + */ + public static boolean createNewFile(File targetFile) { + if (targetFile.exists()) + targetFile.delete(); + try { + return targetFile.createNewFile(); + } catch (IOException e) { + return false; + } + } + + /** + * Delete file or folder. + * + * @param path path. + * @return is succeed. + * @deprecated use {@link #delFileOrFolder(String)} instead. + */ + @Deprecated + public static boolean deleteFolder(String path) { + return delFileOrFolder(new File(path)); + } + + /** + * Delete file or folder. + * + * @param file file. + * @return is succeed. + * @deprecated use {@link #delFileOrFolder(File)} instead. + */ + @Deprecated + public static boolean deleteFolder(File file) { + return delFileOrFolder(file); + } + + /** + * Delete file or folder. + * + * @param path path. + * @return is succeed. + * @see #deleteFolder(File) + */ + public static boolean delFileOrFolder(String path) { + return delFileOrFolder(new File(path)); + } + + /** + * Delete file or folder. + * + * @param file file. + * @return is succeed. + * @see #delFileOrFolder(String) + */ + public static boolean delFileOrFolder(File file) { + if (file == null || !file.exists()) { + // do nothing + } else if (file.isFile()) + file.delete(); + else if (file.isDirectory()) { + File[] files = file.listFiles(); + if (files != null) + for (File sonFile : files) + delFileOrFolder(sonFile); + file.delete(); + } + return true; + } +} diff --git a/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/tools/ImageDownloader.java b/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/tools/ImageDownloader.java new file mode 100644 index 0000000..d807fbe --- /dev/null +++ b/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/tools/ImageDownloader.java @@ -0,0 +1,239 @@ +/* + * Copyright 2015 Yan Zhenjie + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yolanda.nohttp.tools; + +import android.text.TextUtils; + +import com.yolanda.nohttp.Logger; +import com.yolanda.nohttp.NoHttp; +import com.yolanda.nohttp.PosterHandler; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.HttpURLConnection; +import java.net.URL; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +/** + * Created in Nov 4, 2015 3:10:58 PM. + * + * @author Yan Zhenjie. + */ +public class ImageDownloader { + + private static ImageDownloader instance; + private ExecutorService mExecutorService; + + /** + * Cache path. + */ + private String mCachePath; + + private ImageDownloader() { + setCachePath(NoHttp.getContext().getCacheDir().getAbsolutePath()); + mExecutorService = Executors.newFixedThreadPool(3); + } + + /** + * Singleton mode to create download object. + * + * @return {@link ImageDownloader}. + */ + public static ImageDownloader getInstance() { + if (instance == null) { + instance = new ImageDownloader(); + } + return instance; + } + + /** + * Set cache path. + * + * @param cachePath path. + */ + public void setCachePath(String cachePath) { + if (TextUtils.isEmpty(cachePath)) + throw new NullPointerException("cachePath can't null"); + this.mCachePath = cachePath; + File file = new File(cachePath); + if (file.exists() && file.isFile()) + file.delete(); + if (!file.exists()) + file.mkdirs(); + } + + /** + * download image. + * + * @param imageUrl url. + * @param downListener listener. + * @param deleteOld whether to delete the old files. + * @param tag tag. + */ + public void downloadImage(String imageUrl, OnImageDownListener downListener, boolean deleteOld, Object tag) { + downloadImage(imageUrl, downListener, deleteOld, tag, 3 * 1000); + } + + /** + * download image. + * + * @param imageUrl url. + * @param downListener listener. + * @param deleteOld whether to delete the old files. + * @param tag tag. + * @param timeOut times. + */ + public void downloadImage(String imageUrl, OnImageDownListener downListener, boolean deleteOld, Object tag, int timeOut) { + StringBuffer buffer = new StringBuffer(mCachePath); + buffer.append(File.separator); + buffer.append(Encryption.getMa5ForString(imageUrl)); + buffer.append(".png"); + downloadImage(imageUrl, downListener, buffer.toString(), deleteOld, tag, timeOut); + } + + /** + * Download the image to the specified path. + * + * @param imageUrl url. + * @param downListener listener. + * @param path path. + * @param deleteOld whether to delete the old files. + * @param tag tag. + */ + public void downloadImage(String imageUrl, OnImageDownListener downListener, String path, boolean deleteOld, Object tag) { + downloadImage(imageUrl, downListener, path, deleteOld, tag, 3 * 1000); + } + + /** + * Download the image to the specified path. + * + * @param imageUrl url. + * @param downListener listener. + * @param path path. + * @param deleteOld whether to delete the old files. + * @param tag tag. + * @param timeOut times. + */ + public void downloadImage(String imageUrl, OnImageDownListener downListener, String path, Boolean deleteOld, Object tag, int timeOut) { + Logger.d("ImageDownload url: " + imageUrl); + Logger.d("ImageDownload path: " + path); + File file = new File(path); + if (file.exists() && deleteOld) + file.delete(); + if (file.exists()) { + ImageHolder holder = new ImageHolder(); + holder.imageUrl = imageUrl; + holder.isSucceed = true; + holder.imagePath = path; + holder.downListener = downListener; + holder.tag = tag; + PosterHandler.getInstance().post(holder); + } else + mExecutorService.execute(new DownImageThread(imageUrl, path, downListener, tag, timeOut)); + } + + private class DownImageThread implements Runnable { + + private String mImageUrl; + + private String mImagePath; + + private Object tag; + + private OnImageDownListener mDownListener; + + private int timeOut; + + public DownImageThread(String imageUrl, String imagePath, OnImageDownListener downListener, Object tag, int timeOut) { + super(); + this.mImageUrl = imageUrl; + this.mImagePath = imagePath; + this.mDownListener = downListener; + this.tag = tag; + this.timeOut = timeOut; + } + + @Override + public void run() { + ImageHolder holder = new ImageHolder(); + holder.imageUrl = mImageUrl; + holder.downListener = mDownListener; + holder.imagePath = mImagePath; + holder.tag = tag; + + HttpURLConnection urlConnection = null; + InputStream inputStream = null; + OutputStream outputStream = null; + try { + URL url = new URL(mImageUrl); + urlConnection = (HttpURLConnection) url.openConnection(); + urlConnection.setConnectTimeout(timeOut); + urlConnection.setReadTimeout(timeOut); + urlConnection.connect(); + int responseCode = urlConnection.getResponseCode(); + if (HttpURLConnection.HTTP_OK == responseCode) { + inputStream = IOUtils.toBufferedInputStream(urlConnection.getInputStream()); + outputStream = IOUtils.toBufferedOutputStream(new FileOutputStream(new File(mImagePath), false)); + int len; + byte[] buffer = new byte[1024]; + while ((len = inputStream.read(buffer)) != -1) { + outputStream.write(buffer, 0, len); + } + holder.isSucceed = true; + IOUtils.flushQuietly(outputStream); + + Logger.d(mImageUrl + " download finished; path: " + mImagePath + "."); + } else { + Logger.d(mImageUrl + " responseCode: " + responseCode + "."); + } + } catch (Exception e) { + Logger.w(e); + } finally { + IOUtils.closeQuietly(outputStream); + IOUtils.closeQuietly(inputStream); + IOUtils.closeQuietly(urlConnection); + } + PosterHandler.getInstance().post(holder); + } + } + + private class ImageHolder implements Runnable { + String imageUrl; + String imagePath; + OnImageDownListener downListener; + boolean isSucceed; + Object tag; + + @Override + public void run() { + if (downListener != null) + downListener.onDownFinish(imageUrl, imagePath, isSucceed, tag); + } + } + + public interface OnImageDownListener { + /** + * @param imageUrl picture address. + * @param path picture save address. + * @param isSucceed success ask for download. + * @param tag tag that are passed to download. + */ + void onDownFinish(String imageUrl, String path, boolean isSucceed, Object tag); + } +} \ No newline at end of file diff --git a/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/tools/ImageLocalLoader.java b/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/tools/ImageLocalLoader.java new file mode 100644 index 0000000..7430c19 --- /dev/null +++ b/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/tools/ImageLocalLoader.java @@ -0,0 +1,306 @@ +/* + * Copyright 2015 Yan Zhenjie + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yolanda.nohttp.tools; + +import android.annotation.SuppressLint; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.Color; +import android.graphics.drawable.ColorDrawable; +import android.graphics.drawable.Drawable; +import android.text.TextUtils; +import android.util.DisplayMetrics; +import android.view.ViewGroup.LayoutParams; +import android.widget.ImageView; + +import com.yolanda.nohttp.Logger; +import com.yolanda.nohttp.NoHttp; +import com.yolanda.nohttp.PosterHandler; + +import java.io.BufferedInputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +/** + * Created in Nov 4, 2015 3:07:29 PM. + * + * @author Yan Zhenjie. + */ +public class ImageLocalLoader { + /** + * Single module. + */ + private static ImageLocalLoader mInstance; + /** + * Default gray image. + */ + private Drawable mDefaultDrawable; + /** + * Image cache. + */ + private LruCache mLruCache; + /** + * Thread pool. + */ + private ExecutorService mExecutorService; + + /** + * Get single object. + * + * @return {@link ImageLocalLoader}. + */ + public static ImageLocalLoader getInstance() { + synchronized (ImageLocalLoader.class) { + if (mInstance == null) { + mInstance = new ImageLocalLoader(); + } + } + return mInstance; + } + + private ImageLocalLoader() { + mDefaultDrawable = new ColorDrawable(Color.GRAY); + mExecutorService = Executors.newSingleThreadExecutor(); + + int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 8); + mLruCache = new LruCache(maxMemory) { + @Override + protected int sizeOf(String key, Bitmap value) { + return value.getRowBytes() * value.getHeight(); + } + }; + } + + /** + * Deposit in the province read images, width is high, the greater the picture clearer, but also the memory. + * + * @param imagePath pictures in the path of the memory card. + * @param maxWidth the highest limit value target width. + * @param maxHeight the highest limit value target height. + * @return Bitmap + */ + public Bitmap readImage(String imagePath, int maxWidth, int maxHeight) { + File imageFile = new File(imagePath); + if (imageFile.exists()) { + BufferedInputStream inputStream = null; + try { + inputStream = new BufferedInputStream(new FileInputStream(imageFile)); + BitmapFactory.Options options = new BitmapFactory.Options(); + options.inJustDecodeBounds = true; + BitmapFactory.decodeStream(inputStream, null, options); + inputStream.close(); + int i = 0; + while (true) { + if ((options.outWidth >> i <= maxWidth) && (options.outHeight >> i <= maxHeight)) { + inputStream = new BufferedInputStream(new FileInputStream(new File(imagePath))); + options.inSampleSize = (int) Math.pow(2.0, i); + options.inJustDecodeBounds = false; + Bitmap bitmap = BitmapFactory.decodeStream(inputStream, null, options); + inputStream.close(); + return bitmap; + } + i += 1; + } + } catch (IOException e) { + Logger.e(e, "This path does not exist" + imagePath + "."); + } finally { + IOUtils.closeQuietly(inputStream); + } + } + return null; + } + + /** + * According to the ImageView obtains appropriate width and height of compression. + * + * @param imageView {@link ImageView}. + * @param viewSizes ViewSize. + */ + public void measureSize(ImageView imageView, int[] viewSizes) { + final DisplayMetrics displayMetrics = NoHttp.getContext().getResources().getDisplayMetrics(); + final LayoutParams params = imageView.getLayoutParams(); + if (params == null) { + viewSizes[0] = displayMetrics.widthPixels; + viewSizes[1] = displayMetrics.heightPixels; + } else { + viewSizes[0] = params.width == LayoutParams.WRAP_CONTENT ? 0 : imageView.getWidth(); // Get actual image width + viewSizes[1] = params.height == LayoutParams.WRAP_CONTENT ? 0 : imageView.getHeight(); // Get actual image height + + if (viewSizes[0] <= 0) + viewSizes[0] = displayMetrics.widthPixels; // Get layout width parameter + if (viewSizes[1] <= 0) + viewSizes[1] = displayMetrics.heightPixels; // Get layout height parameter + } + } + + /** + * Set the default image, resId from drawable. Is displayed when loading or loading failure. + * + * @param resId res id. + */ + @SuppressLint("NewApi") + public void setDefaultImage(int resId) { + mDefaultDrawable = ResCompat.getDrawable(resId); + } + + /** + * Set the default image, resId from drawable. Is displayed when loading or loading failure. + * + * @param color color. + */ + public void setDefaultImageColor(int color) { + mDefaultDrawable = new ColorDrawable(color); + } + + /** + * Load image from local SDCard. + * + * @param imageView {@link ImageView}. + * @param imagePath path. + */ + public void loadImage(ImageView imageView, String imagePath) { + loadImage(imageView, imagePath, 0, 0, null); + } + + /** + * Load image from local SDCard. + * + * @param imageView {@link ImageView}. + * @param imagePath path. + * @param imageLoadListener {@link ImageLoadListener}. + */ + public void loadImage(ImageView imageView, String imagePath, ImageLoadListener imageLoadListener) { + loadImage(imageView, imagePath, 0, 0, imageLoadListener); + } + + /** + * Load image from local SDCard. + * + * @param imageView {@link ImageView}. + * @param imagePath path. + * @param width width. + * @param height height. + */ + public void loadImage(ImageView imageView, String imagePath, int width, int height) { + loadImage(imageView, imagePath, width, height, null); + } + + /** + * According to the specified width high loading pictures, wide high, the greater the picture clearer, more memory. + * + * @param imageView {@link ImageView}. + * @param imagePath path from local SDCard. + * @param width target width. + * @param height target height. + * @param imageLoadListener {@link ImageLoadListener}. + */ + public void loadImage(ImageView imageView, String imagePath, int width, int height, ImageLoadListener imageLoadListener) { + if (imageLoadListener == null) + imageView.setTag(imagePath); + Bitmap bitmap = getImageFromCache(imagePath + width + height); + if (bitmap == null) { + imageView.setImageDrawable(mDefaultDrawable); + mExecutorService.execute(new TaskThread(imageView, imagePath, width, height, imageLoadListener)); + } else { + ImgBeanHolder holder = new ImgBeanHolder(); + holder.imageView = imageView; + holder.imagePath = imagePath; + holder.bitmap = bitmap; + holder.imageLoadListener = imageLoadListener; + PosterHandler.getInstance().post(holder); + } + } + + private Bitmap getImageFromCache(String key) { + return mLruCache.get(key); + } + + private void addImageToCache(String key, Bitmap bitmap) { + if (getImageFromCache(key) == null && bitmap != null) + mLruCache.put(key, bitmap); + } + + private class TaskThread implements Runnable { + private ImageView mImageView; + private String mImagePath; + private int width; + private int height; + private ImageLoadListener imageLoadListener; + + TaskThread(ImageView imageView, String imagePath, int width, int height, ImageLoadListener imageLoadListener) { + this.mImagePath = imagePath; + this.mImageView = imageView; + this.width = width; + this.height = height; + this.imageLoadListener = imageLoadListener; + } + + @Override + public void run() { + if (TextUtils.isEmpty(mImagePath)) + Logger.e("The image path is null"); + else { + Bitmap bitmap; + if (width != 0 && height != 0) + bitmap = readImage(mImagePath, width, height); + else { + int[] viewSizes = new int[2]; + measureSize(mImageView, viewSizes); + bitmap = readImage(mImagePath, viewSizes[0], viewSizes[1]); + } + addImageToCache(mImagePath + width + height, bitmap); + ImgBeanHolder holder = new ImgBeanHolder(); + holder.bitmap = getImageFromCache(mImagePath + width + height); + holder.imageView = mImageView; + holder.imagePath = mImagePath; + holder.imageLoadListener = imageLoadListener; + PosterHandler.getInstance().post(holder); + } + } + } + + private class ImgBeanHolder implements Runnable { + Bitmap bitmap; + ImageView imageView; + String imagePath; + ImageLoadListener imageLoadListener; + + @Override + public void run() { + if (imagePath.equals(imageView.getTag())) { + if (bitmap == null) + imageView.setImageDrawable(mDefaultDrawable); + else + imageView.setImageBitmap(bitmap); + } + if (imageLoadListener != null) { + if (bitmap != null) + imageLoadListener.onLoadSucceed(imageView, bitmap, imagePath); + else + imageLoadListener.onLoadFailed(imageView, imagePath); + } + } + } + + public interface ImageLoadListener { + void onLoadSucceed(ImageView imageView, Bitmap bitmap, String imagePath); + + void onLoadFailed(ImageView imageView, String imagePath); + } +} diff --git a/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/tools/LinkedMultiValueMap.java b/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/tools/LinkedMultiValueMap.java new file mode 100644 index 0000000..530971d --- /dev/null +++ b/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/tools/LinkedMultiValueMap.java @@ -0,0 +1,31 @@ +/* + * Copyright 2015 Yan Zhenjie + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yolanda.nohttp.tools; + +import java.util.LinkedHashMap; +import java.util.List; + +/** + * Created in Jan 10, 2016 5:03:17 PM. + * + * @author Yan Zhenjie. + */ +public class LinkedMultiValueMap extends BasicMultiValueMap { + + public LinkedMultiValueMap() { + super(new LinkedHashMap>()); + } +} diff --git a/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/tools/LruCache.java b/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/tools/LruCache.java new file mode 100644 index 0000000..1ad86e4 --- /dev/null +++ b/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/tools/LruCache.java @@ -0,0 +1,370 @@ +/* + * Copyright 2015 Yan Zhenjie + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yolanda.nohttp.tools; + +import java.util.LinkedHashMap; +import java.util.Locale; +import java.util.Map; + +/** + * Created in Jan 27, 2016 2:54:13 PM. + * + * @author Yan Zhenjie. + */ +public class LruCache { + private final LinkedHashMap map; + + /** + * Size of this cache in units. Not necessarily the number of elements. + */ + private int size; + private int maxSize; + + private int putCount; + private int createCount; + private int evictionCount; + private int hitCount; + private int missCount; + + /** + * @param maxSize for caches that do not override {@link #sizeOf}, this is + * the maximum number of entries in the cache. For all other caches, + * this is the maximum sum of the sizes of the entries in this cache. + */ + public LruCache(int maxSize) { + if (maxSize <= 0) { + throw new IllegalArgumentException("maxSize <= 0"); + } + this.maxSize = maxSize; + this.map = new LinkedHashMap(0, 0.75f, true); + } + + /** + * Sets the size of the cache. + * + * @param maxSize The new maximum size. + */ + public void reSize(int maxSize) { + if (maxSize <= 0) { + throw new IllegalArgumentException("maxSize <= 0"); + } + + synchronized (this) { + this.maxSize = maxSize; + } + trimToSize(maxSize); + } + + /** + * Returns the value for {@code key} if it exists in the cache or can be + * created by {@code #create}. If a value was returned, it is moved to the + * head of the queue. This returns null if a value is not cached and cannot + * be created. + * + * @param key Key + * @return Value + */ + public final V get(K key) { + if (key == null) { + throw new NullPointerException("key == null"); + } + + V mapValue; + synchronized (this) { + mapValue = map.get(key); + if (mapValue != null) { + hitCount++; + return mapValue; + } + missCount++; + } + + /* + * Attempt to create a value. This may take a long time, and the map + * may be different when create() returns. If a conflicting value was + * added to the map while create() was working, we leave that value in + * the map and release the created value. + */ + + V createdValue = create(key); + if (createdValue == null) { + return null; + } + + synchronized (this) { + createCount++; + mapValue = map.put(key, createdValue); + + if (mapValue != null) { + // There was a conflict so undo that last put + map.put(key, mapValue); + } else { + size += safeSizeOf(key, createdValue); + } + } + + if (mapValue != null) { + entryRemoved(false, key, createdValue, mapValue); + return mapValue; + } else { + trimToSize(maxSize); + return createdValue; + } + } + + /** + * Caches {@code value} for {@code key}. The value is moved to the head of + * the queue. + * + * @param key Key + * @param value Value + * @return the previous value mapped by {@code key}. + */ + public final V put(K key, V value) { + if (key == null || value == null) { + throw new NullPointerException("key == null || value == null"); + } + + V previous; + synchronized (this) { + putCount++; + size += safeSizeOf(key, value); + previous = map.put(key, value); + if (previous != null) { + size -= safeSizeOf(key, previous); + } + } + + if (previous != null) { + entryRemoved(false, key, previous, value); + } + + trimToSize(maxSize); + return previous; + } + + /** + * Remove the eldest entries until the total of remaining entries is at or + * below the requested size. + * + * @param maxSize the maximum size of the cache before returning. May be -1 + * to evict even 0-sized elements. + */ + public void trimToSize(int maxSize) { + while (true) { + K key; + V value; + synchronized (this) { + if (size < 0 || (map.isEmpty() && size != 0)) { + throw new IllegalStateException(getClass().getName() + ".sizeOf() is reporting inconsistent results!"); + } + + if (size <= maxSize || map.isEmpty()) { + break; + } + + Map.Entry toEvict = map.entrySet().iterator().next(); + key = toEvict.getKey(); + value = toEvict.getValue(); + map.remove(key); + size -= safeSizeOf(key, value); + evictionCount++; + } + + entryRemoved(true, key, value, null); + } + } + + /** + * Removes the entry for {@code key} if it exists. + * + * @param key Key + * @return the previous value mapped by {@code key}. + */ + public final V remove(K key) { + if (key == null) { + throw new NullPointerException("key == null"); + } + + V previous; + synchronized (this) { + previous = map.remove(key); + if (previous != null) { + size -= safeSizeOf(key, previous); + } + } + + if (previous != null) { + entryRemoved(false, key, previous, null); + } + + return previous; + } + + /** + *

Called for entries that have been evicted or removed. This method is + * invoked when a value is evicted to make space, removed by a call to + * {@link #remove}, or replaced by a call to {@link #put}. The default + * implementation does nothing.

+ *

The method is called without synchronization: other threads may + * access the cache while this method is executing.

+ * + * @param evicted true if the entry is being removed to make space, false + * if the removal was caused by a {@link #put} or {@link #remove}. + * @param key Key + * @param oldValue Old Value + * @param newValue the new value for {@code key}, if it exists. If non-null, + * this removal was caused by a {@link #put}. Otherwise it was caused by + * an eviction or a {@link #remove}. + */ + protected void entryRemoved(boolean evicted, K key, V oldValue, V newValue) { + } + + /** + *

Called after a cache miss to compute a value for the corresponding key. + * Returns the computed value or null if no value can be computed. The + * default implementation returns null. + * The method is called without synchronization: other threads may + * access the cache while this method is executing.

+ *

If a value for {@code key} exists in the cache when this method + * returns, the created value will be released with {@link #entryRemoved} + * and discarded. This can occur when multiple threads request the same key + * at the same time (causing multiple values to be created), or when one + * thread calls {@link #put} while another is creating a value for the same + * key.

+ * + * @param key Key + * @return Value + */ + protected V create(K key) { + return null; + } + + private int safeSizeOf(K key, V value) { + int result = sizeOf(key, value); + if (result < 0) { + throw new IllegalStateException("Negative size: " + key + "=" + value); + } + return result; + } + + /** + *

Returns the size of the entry for {@code key} and {@code value} in + * user-defined units. The default implementation returns 1 so that size + * is the number of entries and max size is the maximum number of entries.

+ *

An entry's size must not change while it is in the cache.

+ * + * @param key Key + * @param value Value + * @return size + */ + protected int sizeOf(K key, V value) { + return 1; + } + + /** + * Clear the cache, calling {@link #entryRemoved} on each removed entry. + */ + public final void evictAll() { + trimToSize(-1); // -1 will evict 0-sized elements + } + + /** + * For caches that do not override {@link #sizeOf}, this returns the number + * of entries in the cache. For all other caches, this returns the sum of + * the sizes of the entries in this cache. + * + * @return size + */ + public synchronized final int size() { + return size; + } + + /** + * For caches that do not override {@link #sizeOf}, this returns the maximum + * number of entries in the cache. For all other caches, this returns the + * maximum sum of the sizes of the entries in this cache. + * + * @return Max size + */ + public synchronized final int maxSize() { + return maxSize; + } + + /** + * Returns the number of times {@link #get} returned a value that was + * already present in the cache. + * + * @return count + */ + public synchronized final int hitCount() { + return hitCount; + } + + /** + * Returns the number of times {@link #get} returned null or required a new + * value to be created. + * + * @return count + */ + public synchronized final int missCount() { + return missCount; + } + + /** + * Returns the number of times {@link #create(Object)} returned a value. + * + * @return count + */ + public synchronized final int createCount() { + return createCount; + } + + /** + * Returns the number of times {@link #put} was called. + * + * @return count + */ + public synchronized final int putCount() { + return putCount; + } + + /** + * Returns the number of values that have been evicted. + * + * @return count + */ + public synchronized final int evictionCount() { + return evictionCount; + } + + /** + * Returns a copy of the current contents of the cache, ordered from least + * recently accessed to most recently accessed. + * + * @return Map + */ + public synchronized final Map snapshot() { + return new LinkedHashMap(map); + } + + @Override + public synchronized final String toString() { + int accesses = hitCount + missCount; + int hitPercent = accesses != 0 ? (100 * hitCount / accesses) : 0; + return String.format(Locale.getDefault(), "LruCache[maxSize=%d,hits=%d,misses=%d,hitRate=%d%%]", maxSize, hitCount, missCount, hitPercent); + } +} diff --git a/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/tools/MultiValueMap.java b/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/tools/MultiValueMap.java new file mode 100644 index 0000000..c6d338d --- /dev/null +++ b/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/tools/MultiValueMap.java @@ -0,0 +1,141 @@ +/* + * Copyright 2015 Yan Zhenjie + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yolanda.nohttp.tools; + +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + *

Can save multiple the value of the map.

+ * Created in Jan 10, 2016 5:00:07 PM. + * + * @author Yan Zhenjie. + */ +public interface MultiValueMap { + + /** + * Add a value for a key. + * + * @param key key. + * @param value value. + */ + void add(K key, V value); + + /** + * Add more value to a key. + * + * @param key key. + * @param values values. + */ + void add(K key, List values); + + /** + * Set the value for a key, if the key has the value, delete all of the old value, then the new value added. + * + * @param key key. + * @param value values. + */ + void set(K key, V value); + + /** + * @param key key. + * @param values values. + * @see #set(Object, Object) + */ + void set(K key, List values); + + /** + * The removal of all key/value pair, add new keys to enter. + * + * @param values values. + */ + void set(Map> values); + + /** + * Delete a key-value. + * + * @param key key. + * @return value. + */ + List remove(K key); + + /** + * Remove all key-value. + */ + void clear(); + + /** + * Get the key set. + * + * @return Set. + */ + Set keySet(); + + /** + * To get all key of all values. + * + * @return List. + */ + List values(); + + /** + * EntrySet. + * + * @return {@link Set}. + */ + Set>> entrySet(); + + /** + * To get the key of the at index value. + * + * @param key key. + * @param index index value. + * @return The value. + */ + V getValue(K key, int index); + + /** + * To get key of all values. + * + * @param key key. + * @return values. + */ + List getValues(K key); + + /** + * The size of the map. + * + * @return size. + */ + int size(); + + /** + * If the map has no value. + * + * @return True: empty, false: not empty. + */ + boolean isEmpty(); + + /** + * Whether the map with a key. + * + * @param key key. + * @return True: contain, false: none. + */ + boolean containsKey(K key); + +} diff --git a/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/tools/NetUtil.java b/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/tools/NetUtil.java new file mode 100644 index 0000000..ba35626 --- /dev/null +++ b/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/tools/NetUtil.java @@ -0,0 +1,298 @@ +/* + * Copyright 2015 Yan Zhenjie + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yolanda.nohttp.tools; + +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.net.InetAddress; +import java.net.NetworkInterface; +import java.net.SocketException; +import java.util.Enumeration; +import java.util.regex.Pattern; + +import com.yolanda.nohttp.Logger; +import com.yolanda.nohttp.NoHttp; + +import android.content.Context; +import android.content.Intent; +import android.net.ConnectivityManager; +import android.net.NetworkInfo; +import android.os.Build; + +/** + *

+ * Check the network utility class. + *

+ * Created in Jul 31, 2015 1:19:47 PM. + * + * @author Yan Zhenjie. + */ +public class NetUtil { + + public enum NetType { + Any, + + Wifi, + + Mobile + } + + /** + * Class name of the {@link android.provider.Settings}. + */ + private static final String ANDROID_PROVIDER_SETTINGS = "android.provider.Settings"; + + /** + * Open network settings page. + */ + public static void openSetting() { + if (Build.VERSION.SDK_INT > AndroidVersion.GINGERBREAD_MR1) + openSetting("ACTION_WIFI_SETTINGS"); + else + openSetting("ACTION_WIRELESS_SETTINGS"); + } + + private static void openSetting(String ActionName) { + try { + Class settingsClass = Class.forName(ANDROID_PROVIDER_SETTINGS); + Field actionWifiSettingsField = settingsClass.getDeclaredField(ActionName); + Intent settingIntent = new Intent(actionWifiSettingsField.get(null).toString()); + settingIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + NoHttp.getContext().startActivity(settingIntent); + } catch (Throwable e) { + Logger.w(e); + } + } + + /** + * Check the network is enable. + * + * @return Available returns true, unavailable returns false. + */ + public static boolean isNetworkAvailable() { + return isNetworkAvailable(NetType.Any); + } + + /** + * To determine whether a WiFi network is available. + * + * @return Open return true, close returns false. + */ + public static boolean isWifiConnected() { + return isNetworkAvailable(NetType.Wifi); + } + + /** + * To determine whether a mobile phone network is available. + * + * @return Open return true, close returns false. + */ + public static boolean isMobileConnected() { + return isNetworkAvailable(NetType.Mobile); + } + + /** + * According to the different type of network to determine whether the network connection. + * + * @param netType from {@link NetType}. + * @return Connection state return true, otherwise it returns false. + */ + public static boolean isNetworkAvailable(NetType netType) { + ConnectivityManager connectivity = (ConnectivityManager) NoHttp.getContext().getSystemService(Context.CONNECTIVITY_SERVICE); + Class connectivityManagerClass = connectivity.getClass(); + if (Build.VERSION.SDK_INT >= AndroidVersion.LOLLIPOP) { + try { + Method getAllNetworksMethod = connectivityManagerClass.getMethod("getAllNetworks"); + getAllNetworksMethod.setAccessible(true); + Object[] networkArray = (Object[]) getAllNetworksMethod.invoke(connectivity); + for (Object network : networkArray) { + Method getNetworkInfoMethod = connectivityManagerClass.getMethod("getNetworkInfo", Class.forName("android.net.Network")); + getNetworkInfoMethod.setAccessible(true); + NetworkInfo networkInfo = (NetworkInfo) getNetworkInfoMethod.invoke(connectivity, network); + if (isConnected(netType, networkInfo)) + return true; + } + } catch (Throwable e) { + } + } else { + try { + Method getAllNetworkInfoMethod = connectivityManagerClass.getMethod("getAllNetworkInfo"); + getAllNetworkInfoMethod.setAccessible(true); + Object[] networkInfoArray = (Object[]) getAllNetworkInfoMethod.invoke(connectivity); + for (Object object : networkInfoArray) { + if (isConnected(netType, (NetworkInfo) object)) + return true; + } + } catch (Throwable e) { + } + } + return false; + } + + /** + * According to the different type of network to determine whether the network connection. + * + * @param netType from {@link NetType}. + * @param networkInfo from {@link NetworkInfo}. + * @return Connection state return true, otherwise it returns false. + */ + public static boolean isConnected(NetType netType, NetworkInfo networkInfo) { + if (netType == NetType.Any && networkInfo != null && isConnected(networkInfo)) + return true; + else if (netType == NetType.Wifi && networkInfo != null && networkInfo.getType() == ConnectivityManager.TYPE_WIFI && isConnected(networkInfo)) + return true; + else if (netType == NetType.Mobile && networkInfo != null && networkInfo.getType() == ConnectivityManager.TYPE_MOBILE && isConnected(networkInfo)) + return true; + return false; + } + + /** + * Whether network connection. + * + * @param networkInfo from {@link NetworkInfo}. + * @return Connection state return true, otherwise it returns false. + */ + public static boolean isConnected(NetworkInfo networkInfo) { + return networkInfo != null && networkInfo.isAvailable() && networkInfo.isConnected(); + } + + /** + * Check the GPRS whether available. + * + * @return Open return true, close returns false. + */ + public static boolean isGPRSOpen() { + ConnectivityManager connectivityManager = (ConnectivityManager) NoHttp.getContext().getSystemService(Context.CONNECTIVITY_SERVICE); + Class cmClass = connectivityManager.getClass(); + try { + Method getMobileDataEnabledMethod = cmClass.getMethod("getMobileDataEnabled"); + getMobileDataEnabledMethod.setAccessible(true); + return (Boolean) getMobileDataEnabledMethod.invoke(connectivityManager); + } catch (Throwable e) { + } + return false; + } + + /** + * Open or close the GPRS. + * + * @param isEnable Open to true, close to false. + */ + public static void setGPRSEnable(boolean isEnable) { + ConnectivityManager connectivityManager = (ConnectivityManager) NoHttp.getContext().getSystemService(Context.CONNECTIVITY_SERVICE); + Class cmClass = connectivityManager.getClass(); + try { + Method setMobileDataEnabledMethod = cmClass.getMethod("setMobileDataEnabled", boolean.class); + setMobileDataEnabledMethod.setAccessible(true); + setMobileDataEnabledMethod.invoke(connectivityManager, isEnable); + } catch (Throwable e) { + } + } + + /** + * Tet local ip address. + * + * @return Such as: {@code 192.168.1.1}. + */ + public static String getLocalIPAddress() { + Enumeration enumeration = null; + try { + enumeration = NetworkInterface.getNetworkInterfaces(); + } catch (SocketException e) { + Logger.w(e); + } + if (enumeration != null) { + // 遍历所用的网络接口 + while (enumeration.hasMoreElements()) { + NetworkInterface nif = enumeration.nextElement();// 得到每一个网络接口绑定的地址 + Enumeration inetAddresses = nif.getInetAddresses(); + // 遍历每一个接口绑定的所有ip + if (inetAddresses != null) + while (inetAddresses.hasMoreElements()) { + InetAddress ip = inetAddresses.nextElement(); + if (!ip.isLoopbackAddress() && isIPv4Address(ip.getHostAddress())) { + return ip.getHostAddress(); + } + } + } + } + return ""; + } + + /** + * Ipv4 address check. + */ + private static final Pattern IPV4_PATTERN = Pattern.compile("^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$"); + + /** + * Check if valid IPV4 address. + * + * @param input the address string to check for validity. + * @return True if the input parameter is a valid IPv4 address. + */ + public static boolean isIPv4Address(String input) { + return IPV4_PATTERN.matcher(input).matches(); + } + + /* ===========以下是IPv6的检查,暂时用不到========== */ + + // 未压缩过的IPv6地址检查 + private static final Pattern IPV6_STD_PATTERN = Pattern.compile("^[0-9a-fA-F]{1,4}(:[0-9a-fA-F]{1,4}){7}$"); + // 压缩过的IPv6地址检查 + private static final Pattern IPV6_HEX_COMPRESSED_PATTERN = Pattern.compile("^(([0-9A-Fa-f]{1,4}(:[0-9A-Fa-f]{1,4}){0,5})?)" + // 0-6 + "::" + "(([0-9A-Fa-f]{1,4}(:[0-9A-Fa-f]{1,4}){0,5})?)$");// 0-6 hex fields + + /** + * Check whether the parameter is effective standard (uncompressed) IPv6 address. + * + * @param input IPV6 address. + * @return True or false. + * @see #isIPv6HexCompressedAddress(String) + */ + public static boolean isIPv6StdAddress(final String input) { + return IPV6_STD_PATTERN.matcher(input).matches(); + } + + /** + * Check whether the parameter is effective compression IPv6 address. + * + * @param input IPV6 address. + * @return True or false. + * @see #isIPv6StdAddress(String) + */ + public static boolean isIPv6HexCompressedAddress(final String input) { + int colonCount = 0; + for (int i = 0; i < input.length(); i++) { + if (input.charAt(i) == ':') { + colonCount++; + } + } + return colonCount <= 7 && IPV6_HEX_COMPRESSED_PATTERN.matcher(input).matches(); + } + + /** + * Check whether the IPV6 address of compressed or uncompressed. + * + * @param input IPV6 address. + * @return True or false. + * @see #isIPv6HexCompressedAddress(String) + * @see #isIPv6StdAddress(String) + */ + public static boolean isIPv6Address(final String input) { + return isIPv6StdAddress(input) || isIPv6HexCompressedAddress(input); + } + +} diff --git a/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/tools/ResCompat.java b/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/tools/ResCompat.java new file mode 100644 index 0000000..7386ad9 --- /dev/null +++ b/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/tools/ResCompat.java @@ -0,0 +1,230 @@ +/* + * Copyright 2015 Yan Zhenjie + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yolanda.nohttp.tools; + +import android.content.res.ColorStateList; +import android.content.res.Resources; +import android.content.res.Resources.Theme; +import android.graphics.Color; +import android.graphics.drawable.Drawable; +import android.os.Build; +import android.text.Spannable; +import android.text.SpannableString; +import android.text.style.AbsoluteSizeSpan; +import android.text.style.ForegroundColorSpan; +import android.text.style.ImageSpan; +import android.text.style.StrikethroughSpan; +import android.view.View; +import android.widget.TextView; + +import com.yolanda.nohttp.NoHttp; + +import java.lang.reflect.Method; + +/** + * Created in Nov 27, 2015 6:20:48 PM. + * + * @author Yan Zhenjie. + */ +public class ResCompat { + + public static Drawable getDrawable(int drawableId) { + return getDrawable(drawableId, null); + } + + public static Drawable getDrawable(int drawableId, Theme theme) { + Resources resources = NoHttp.getContext().getResources(); + Class resourcesClass = resources.getClass(); + if (Build.VERSION.SDK_INT >= AndroidVersion.LOLLIPOP) + try { + Method getDrawableMethod = resourcesClass.getMethod("getDrawable", int.class, Theme.class); + getDrawableMethod.setAccessible(true); + return (Drawable) getDrawableMethod.invoke(resources, drawableId, theme); + } catch (Throwable e) { + } + else + try { + Method getDrawableMethod = resourcesClass.getMethod("getDrawable", int.class); + getDrawableMethod.setAccessible(true); + return (Drawable) getDrawableMethod.invoke(resources, drawableId); + } catch (Throwable e) { + } + return null; + } + + public static void setLeftDrawable(TextView textView, Drawable leftDrawable) { + setDrawableBounds(leftDrawable); + Drawable top = textView.getCompoundDrawables()[1]; + Drawable right = textView.getCompoundDrawables()[2]; + Drawable bottom = textView.getCompoundDrawables()[3]; + textView.setCompoundDrawables(leftDrawable, top, right, bottom); + } + + public static void setLeftDrawable(TextView textView, int drawableId) { + setLeftDrawable(textView, getDrawable(drawableId)); + } + + public static void setTopDrawable(TextView textView, Drawable topDrawable) { + setDrawableBounds(topDrawable); + Drawable left = textView.getCompoundDrawables()[0]; + Drawable right = textView.getCompoundDrawables()[2]; + Drawable bottom = textView.getCompoundDrawables()[3]; + textView.setCompoundDrawables(left, topDrawable, right, bottom); + } + + public static void setTopDrawable(TextView textView, int drawableId) { + setTopDrawable(textView, getDrawable(drawableId)); + } + + public static void setRightDrawable(TextView textView, Drawable rightDrawable) { + setDrawableBounds(rightDrawable); + Drawable left = textView.getCompoundDrawables()[0]; + Drawable top = textView.getCompoundDrawables()[1]; + Drawable bottom = textView.getCompoundDrawables()[3]; + textView.setCompoundDrawables(left, top, rightDrawable, bottom); + } + + public static void setRightDrawable(TextView textView, int drawableId) { + setRightDrawable(textView, getDrawable(drawableId)); + } + + public static void setBottomDrawable(TextView textView, Drawable bottomDrawable) { + setDrawableBounds(bottomDrawable); + Drawable left = textView.getCompoundDrawables()[0]; + Drawable top = textView.getCompoundDrawables()[1]; + Drawable bottom = textView.getCompoundDrawables()[2]; + textView.setCompoundDrawables(left, top, bottom, bottomDrawable); + } + + public static void setBottomDrawable(TextView textView, int drawableId) { + setBottomDrawable(textView, getDrawable(drawableId)); + } + + public static void setCompoundDrawables(TextView textView, Drawable leftDrawable, Drawable topDrawable, Drawable rightDrawable, Drawable bottoDrawable) { + setDrawableBounds(leftDrawable); + setDrawableBounds(topDrawable); + setDrawableBounds(rightDrawable); + setDrawableBounds(bottoDrawable); + textView.setCompoundDrawables(leftDrawable, topDrawable, rightDrawable, bottoDrawable); + } + + public static void setCompoundDrawables(TextView textView, int drawableLeftId, int drawableRightId, int drawableTopId, int drawableBottomId) { + setCompoundDrawables(textView, getDrawable(drawableLeftId), getDrawable(drawableRightId), getDrawable(drawableTopId), getDrawable(drawableBottomId)); + } + + public static void setDrawableBounds(Drawable drawable) { + if (drawable != null) + drawable.setBounds(0, 0, drawable.getMinimumWidth(), drawable.getMinimumHeight()); + } + + public static int getColor(int colorId) { + return getColor(colorId, null); + } + + public static int getColor(int colorId, Theme theme) { + Resources resources = NoHttp.getContext().getResources(); + Class resourcesClass = resources.getClass(); + if (Build.VERSION.SDK_INT >= AndroidVersion.M) + try { + Method getColorMethod = resourcesClass.getMethod("getColor", int.class, Theme.class); + getColorMethod.setAccessible(true); + return (Integer) getColorMethod.invoke(resources, colorId, theme); + } catch (Throwable e) { + } + else + try { + Method getColorMethod = resourcesClass.getMethod("getColor", int.class); + getColorMethod.setAccessible(true); + return (Integer) getColorMethod.invoke(resources, colorId); + } catch (Throwable e) { + } + return Color.BLACK; + } + + public static ColorStateList getColorStateList(int colorStateId) { + return getColorStateList(colorStateId, null); + } + + public static ColorStateList getColorStateList(int colorStateId, Theme theme) { + Resources resources = NoHttp.getContext().getResources(); + Class resourcesClass = resources.getClass(); + if (Build.VERSION.SDK_INT >= AndroidVersion.M) + try { + Method getColorStateListMethod = resourcesClass.getMethod("getColorStateList", int.class, Theme.class); + getColorStateListMethod.setAccessible(true); + return (ColorStateList) getColorStateListMethod.invoke(resources, colorStateId, theme); + } catch (Throwable e) { + } + else + try { + Method getColorStateListMethod = resourcesClass.getMethod("getColorStateList", int.class); + getColorStateListMethod.setAccessible(true); + return (ColorStateList) getColorStateListMethod.invoke(resources, colorStateId); + } catch (Throwable e) { + } + return null; + } + + public static void setBackground(View view, int drawableId) { + setBackground(view, getDrawable(drawableId)); + } + + public static void setBackground(View view, Drawable background) { + if (Build.VERSION.SDK_INT >= AndroidVersion.JELLY_BEAN) + setBackground("setBackground", view, background); + else + setBackground("setBackgroundDrawable", view, background); + } + + public static void setBackground(String method, View view, Drawable background) { + try { + Method viewMethod = view.getClass().getMethod(method, Drawable.class); + viewMethod.setAccessible(true); + viewMethod.invoke(view, background); + } catch (Throwable e) { + } + } + + public static SpannableString getScaleText(String content, int start, int end, int px) { + SpannableString stringSpan = new SpannableString(content); + stringSpan.setSpan(new AbsoluteSizeSpan(px), start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + return stringSpan; + } + + public static SpannableString getColorText(String content, int start, int end, int color) { + SpannableString stringSpan = new SpannableString(content); + stringSpan.setSpan(new ForegroundColorSpan(color), start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + return stringSpan; + } + + public static SpannableString getDeleteText(String content) { + return getDeleteText(content, 0, content.length()); + } + + public static SpannableString getDeleteText(String content, int start, int end) { + SpannableString stringSpan = new SpannableString(content); + stringSpan.setSpan(new StrikethroughSpan(), start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + return stringSpan; + } + + public static SpannableString getImageSpanText(String content, Drawable drawable, int start, int end) { + SpannableString stringSpan = new SpannableString(content); + setDrawableBounds(drawable); + ImageSpan imageSpan = new ImageSpan(drawable, ImageSpan.ALIGN_BASELINE); + stringSpan.setSpan(imageSpan, start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + return stringSpan; + } +} diff --git a/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/tools/TreeMultiValueMap.java b/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/tools/TreeMultiValueMap.java new file mode 100644 index 0000000..a86c5b5 --- /dev/null +++ b/YFDXJ/nohttp/src/main/java/com/yolanda/nohttp/tools/TreeMultiValueMap.java @@ -0,0 +1,36 @@ +/* + * Copyright 2015 Yan Zhenjie + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.yolanda.nohttp.tools; + +import java.util.Comparator; +import java.util.List; +import java.util.TreeMap; + +/** + * Created on 2016/6/23. + * + * @author Yan Zhenjie. + */ +public class TreeMultiValueMap extends BasicMultiValueMap { + + public TreeMultiValueMap() { + super(new TreeMap>()); + } + + public TreeMultiValueMap(Comparator keyComparator) { + super(new TreeMap>(keyComparator)); + } +} diff --git a/YFDXJ/pictureselector/.gitignore b/YFDXJ/pictureselector/.gitignore old mode 100755 new mode 100644 diff --git a/YFDXJ/pictureselector/build.gradle b/YFDXJ/pictureselector/build.gradle old mode 100755 new mode 100644 diff --git a/YFDXJ/pictureselector/proguard-rules.pro b/YFDXJ/pictureselector/proguard-rules.pro old mode 100755 new mode 100644 diff --git a/YFDXJ/pictureselector/src/androidTest/java/com/lidong/photopicker/ApplicationTest.java b/YFDXJ/pictureselector/src/androidTest/java/com/lidong/photopicker/ApplicationTest.java old mode 100755 new mode 100644 diff --git a/YFDXJ/pictureselector/src/main/AndroidManifest.xml b/YFDXJ/pictureselector/src/main/AndroidManifest.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/pictureselector/src/main/java/com/lidong/photopicker/Folder.java b/YFDXJ/pictureselector/src/main/java/com/lidong/photopicker/Folder.java old mode 100755 new mode 100644 diff --git a/YFDXJ/pictureselector/src/main/java/com/lidong/photopicker/FolderAdapter.java b/YFDXJ/pictureselector/src/main/java/com/lidong/photopicker/FolderAdapter.java old mode 100755 new mode 100644 diff --git a/YFDXJ/pictureselector/src/main/java/com/lidong/photopicker/Image.java b/YFDXJ/pictureselector/src/main/java/com/lidong/photopicker/Image.java old mode 100755 new mode 100644 diff --git a/YFDXJ/pictureselector/src/main/java/com/lidong/photopicker/ImageCaptureManager.java b/YFDXJ/pictureselector/src/main/java/com/lidong/photopicker/ImageCaptureManager.java old mode 100755 new mode 100644 diff --git a/YFDXJ/pictureselector/src/main/java/com/lidong/photopicker/ImageConfig.java b/YFDXJ/pictureselector/src/main/java/com/lidong/photopicker/ImageConfig.java old mode 100755 new mode 100644 diff --git a/YFDXJ/pictureselector/src/main/java/com/lidong/photopicker/ImageGridAdapter.java b/YFDXJ/pictureselector/src/main/java/com/lidong/photopicker/ImageGridAdapter.java old mode 100755 new mode 100644 diff --git a/YFDXJ/pictureselector/src/main/java/com/lidong/photopicker/PhotoPagerAdapter.java b/YFDXJ/pictureselector/src/main/java/com/lidong/photopicker/PhotoPagerAdapter.java old mode 100755 new mode 100644 diff --git a/YFDXJ/pictureselector/src/main/java/com/lidong/photopicker/PhotoPickerActivity.java b/YFDXJ/pictureselector/src/main/java/com/lidong/photopicker/PhotoPickerActivity.java old mode 100755 new mode 100644 diff --git a/YFDXJ/pictureselector/src/main/java/com/lidong/photopicker/PhotoPreviewActivity.java b/YFDXJ/pictureselector/src/main/java/com/lidong/photopicker/PhotoPreviewActivity.java old mode 100755 new mode 100644 diff --git a/YFDXJ/pictureselector/src/main/java/com/lidong/photopicker/SelectModel.java b/YFDXJ/pictureselector/src/main/java/com/lidong/photopicker/SelectModel.java old mode 100755 new mode 100644 diff --git a/YFDXJ/pictureselector/src/main/java/com/lidong/photopicker/intent/PhotoPickerIntent.java b/YFDXJ/pictureselector/src/main/java/com/lidong/photopicker/intent/PhotoPickerIntent.java old mode 100755 new mode 100644 diff --git a/YFDXJ/pictureselector/src/main/java/com/lidong/photopicker/intent/PhotoPreviewIntent.java b/YFDXJ/pictureselector/src/main/java/com/lidong/photopicker/intent/PhotoPreviewIntent.java old mode 100755 new mode 100644 diff --git a/YFDXJ/pictureselector/src/main/java/com/lidong/photopicker/widget/ViewPagerFixed.java b/YFDXJ/pictureselector/src/main/java/com/lidong/photopicker/widget/ViewPagerFixed.java old mode 100755 new mode 100644 diff --git a/YFDXJ/pictureselector/src/main/res/color/action_text_color.xml b/YFDXJ/pictureselector/src/main/res/color/action_text_color.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/pictureselector/src/main/res/drawable/text_indicator.xml b/YFDXJ/pictureselector/src/main/res/drawable/text_indicator.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/pictureselector/src/main/res/layout/activity_image_preview.xml b/YFDXJ/pictureselector/src/main/res/layout/activity_image_preview.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/pictureselector/src/main/res/layout/activity_photopicker.xml b/YFDXJ/pictureselector/src/main/res/layout/activity_photopicker.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/pictureselector/src/main/res/layout/item_camera.xml b/YFDXJ/pictureselector/src/main/res/layout/item_camera.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/pictureselector/src/main/res/layout/item_folder.xml b/YFDXJ/pictureselector/src/main/res/layout/item_folder.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/pictureselector/src/main/res/layout/item_preview.xml b/YFDXJ/pictureselector/src/main/res/layout/item_preview.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/pictureselector/src/main/res/layout/item_select_image.xml b/YFDXJ/pictureselector/src/main/res/layout/item_select_image.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/pictureselector/src/main/res/layout/photopicker_toolbar.xml b/YFDXJ/pictureselector/src/main/res/layout/photopicker_toolbar.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/pictureselector/src/main/res/menu/menu_picker.xml b/YFDXJ/pictureselector/src/main/res/menu/menu_picker.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/pictureselector/src/main/res/menu/menu_preview.xml b/YFDXJ/pictureselector/src/main/res/menu/menu_preview.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/pictureselector/src/main/res/mipmap-hdpi/ic_action_discard.png b/YFDXJ/pictureselector/src/main/res/mipmap-hdpi/ic_action_discard.png old mode 100755 new mode 100644 diff --git a/YFDXJ/pictureselector/src/main/res/mipmap-mdpi/ic_action_discard.png b/YFDXJ/pictureselector/src/main/res/mipmap-mdpi/ic_action_discard.png old mode 100755 new mode 100644 diff --git a/YFDXJ/pictureselector/src/main/res/mipmap-xhdpi/ic_action_discard.png b/YFDXJ/pictureselector/src/main/res/mipmap-xhdpi/ic_action_discard.png old mode 100755 new mode 100644 diff --git a/YFDXJ/pictureselector/src/main/res/mipmap-xhdpi/text_indicator_normal.png b/YFDXJ/pictureselector/src/main/res/mipmap-xhdpi/text_indicator_normal.png old mode 100755 new mode 100644 diff --git a/YFDXJ/pictureselector/src/main/res/mipmap-xhdpi/text_indicator_pressed.png b/YFDXJ/pictureselector/src/main/res/mipmap-xhdpi/text_indicator_pressed.png old mode 100755 new mode 100644 diff --git a/YFDXJ/pictureselector/src/main/res/mipmap-xxhdpi/asv.png b/YFDXJ/pictureselector/src/main/res/mipmap-xxhdpi/asv.png old mode 100755 new mode 100644 diff --git a/YFDXJ/pictureselector/src/main/res/mipmap-xxhdpi/asy.png b/YFDXJ/pictureselector/src/main/res/mipmap-xxhdpi/asy.png old mode 100755 new mode 100644 diff --git a/YFDXJ/pictureselector/src/main/res/mipmap-xxhdpi/btn_selected.png b/YFDXJ/pictureselector/src/main/res/mipmap-xxhdpi/btn_selected.png old mode 100755 new mode 100644 diff --git a/YFDXJ/pictureselector/src/main/res/mipmap-xxhdpi/btn_unselected.png b/YFDXJ/pictureselector/src/main/res/mipmap-xxhdpi/btn_unselected.png old mode 100755 new mode 100644 diff --git a/YFDXJ/pictureselector/src/main/res/mipmap-xxhdpi/default_check.png b/YFDXJ/pictureselector/src/main/res/mipmap-xxhdpi/default_check.png old mode 100755 new mode 100644 diff --git a/YFDXJ/pictureselector/src/main/res/mipmap-xxhdpi/default_error.png b/YFDXJ/pictureselector/src/main/res/mipmap-xxhdpi/default_error.png old mode 100755 new mode 100644 diff --git a/YFDXJ/pictureselector/src/main/res/mipmap-xxhdpi/ic_action_discard.png b/YFDXJ/pictureselector/src/main/res/mipmap-xxhdpi/ic_action_discard.png old mode 100755 new mode 100644 diff --git a/YFDXJ/pictureselector/src/main/res/values/colors.xml b/YFDXJ/pictureselector/src/main/res/values/colors.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/pictureselector/src/main/res/values/dimens.xml b/YFDXJ/pictureselector/src/main/res/values/dimens.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/pictureselector/src/main/res/values/strings.xml b/YFDXJ/pictureselector/src/main/res/values/strings.xml old mode 100755 new mode 100644 diff --git a/YFDXJ/settings.gradle b/YFDXJ/settings.gradle old mode 100755 new mode 100644 index 09d7568..e65fa22 --- a/YFDXJ/settings.gradle +++ b/YFDXJ/settings.gradle @@ -1,6 +1,13 @@ pluginManagement { repositories { - google() + google { + content { + includeGroupByRegex("com\\.android.*") + includeGroupByRegex("com\\.google.*") + includeGroupByRegex("androidx.*") + } + } + //google() mavenCentral() gradlePluginPortal() jcenter() @@ -13,11 +20,11 @@ dependencyResolutionManagement { mavenCentral() jcenter() maven {url "https://jitpack.io"} - maven {url 'https://maven.google.com'} + //maven {url 'https://maven.google.com'} } } -include ':app' +include ':app',':nohttp',':autolayout',':litepal',':matisse',':luban' rootProject.name = "YFDXJ" \ No newline at end of file diff --git a/destop/app/src/main/java/com/rehome/destop/utils/StatusBarUtil.java b/destop/app/src/main/java/com/rehome/destop/utils/StatusBarUtil.java old mode 100755 new mode 100644 diff --git a/destop/app/src/main/res/values/ids.xml b/destop/app/src/main/res/values/ids.xml old mode 100755 new mode 100644 diff --git a/destop/gradlew b/destop/gradlew old mode 100755 new mode 100644