Commit 240ca20c authored by 张圣龙's avatar 张圣龙

Merge branch 'master' of https://git.zmcms.cn/wangwei/widget

parents 5310ed4d a74d216d
...@@ -8,4 +8,5 @@ ...@@ -8,4 +8,5 @@
.externalNativeBuild .externalNativeBuild
/.idea /.idea
/.gradle /.gradle
\ No newline at end of file /fetch/build
\ No newline at end of file
apply plugin: 'com.android.application' apply plugin: 'com.android.application'
apply plugin: 'kotlin-android' apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions' apply plugin: 'kotlin-android-extensions'
apply plugin: 'kotlin-kapt'
android { android {
compileSdkVersion 29 compileSdkVersion 29
...@@ -26,8 +25,18 @@ android { ...@@ -26,8 +25,18 @@ android {
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
} }
} }
configurations.all {
resolutionStrategy {
force 'androidx.core:core-ktx:1.6.0'
//force 'com.github.bumptech.glide:compiler:4.2.0'
}
}
buildFeatures {
viewBinding true
}
packagingOptions { packagingOptions {
pickFirst 'lib/armeabi-v7a/libc++_shared.so' pickFirst 'lib/armeabi-v7a/libc++_shared.so'
pickFirst 'lib/armeabi-v7a/libmarsxlog.so' pickFirst 'lib/armeabi-v7a/libmarsxlog.so'
pickFirst 'lib/arm64-v8a/libc++_shared.so' pickFirst 'lib/arm64-v8a/libc++_shared.so'
pickFirst 'lib/arm64-v8a/libmarsxlog.so' pickFirst 'lib/arm64-v8a/libmarsxlog.so'
...@@ -38,24 +47,31 @@ dependencies { ...@@ -38,24 +47,31 @@ dependencies {
implementation 'com.hyman:flowlayout-lib:1.1.2' implementation 'com.hyman:flowlayout-lib:1.1.2'
implementation fileTree(dir: 'libs', include: ['*.jar']) implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
implementation 'androidx.appcompat:appcompat:1.0.2' implementation 'androidx.appcompat:appcompat:1.3.1'
implementation 'androidx.core:core-ktx:1.0.2' implementation 'androidx.core:core-ktx:1.7.0'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3' implementation 'androidx.constraintlayout:constraintlayout:2.1.1'
implementation project(path: ':KeyBoardView') implementation project(path: ':KeyBoardView')
implementation project(path: ':log') implementation project(path: ':log')
testImplementation 'junit:junit:4.12' testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test:runner:1.2.0' androidTestImplementation 'androidx.test:runner:1.3.0'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
implementation project(path: ':widgetlib') implementation project(path: ':widgetlib')
// implementation 'com.qmai.android.sdk:widgetlib:1.1.2-SNAPSHOT' // implementation 'com.qmai.android.sdk:widgetlib:1.1.2-SNAPSHOT'
implementation project(path: ':tools') implementation project(path: ':tools')
implementation 'androidx.recyclerview:recyclerview:1.0.0'
implementation 'com.github.sebdomdev:android-drawer-sheet:1.0' implementation 'com.github.sebdomdev:android-drawer-sheet:1.0'
implementation 'com.lxj:xpopup:2.0.6-rc1' implementation 'com.lxj:xpopup:2.0.6-rc1'
implementation 'com.google.android.material:material:1.3.0-alpha01' implementation 'com.google.android.material:material:1.4.0'
implementation 'androidx.recyclerview:recyclerview:1.1.0' implementation 'androidx.recyclerview:recyclerview:1.2.1'
implementation project(path: ':printer') implementation project(path: ':printer')
// implementation 'com.android.tools.build:gradle:3.1.1' // implementation 'com.android.tools.build:gradle:3.1.1'
// implementation 'com.android.tools.lint:lint-gradle:26.1.1' // implementation 'com.android.tools.lint:lint-gradle:26.1.1'
implementation 'com.guolindev.permissionx:permissionx:1.5.1'
implementation 'com.eclipsesource.j2v8:j2v8:6.2.0@aar'
implementation 'com.github.bumptech.glide:glide:4.12.0'
annotationProcessor 'com.github.bumptech.glide:compiler:4.12.0'
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.1"
implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.3.1"
implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.3.1"
} }
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.qimai.android.widget"> xmlns:tools="http://schemas.android.com/tools"
package="com.qimai.android.widget">
<uses-permission android:name="android.permission.BLUETOOTH" /> <uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
<uses-permission android:name="android.permission.READ_PRIVILEGED_PHONE_STATE" />
<uses-permission android:name="android.permission.READ_PHONE_STATE"/>
<application <application
android:name=".App" android:name=".App"
android:allowBackup="true" android:allowBackup="true"
android:icon="@mipmap/ic_launcher" android:icon="@mipmap/ic_launcher"
android:label="@string/app_name" android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round" android:requestLegacyExternalStorage="true"
android:supportsRtl="true" android:roundIcon="@mipmap/ic_launcher_round"
android:theme="@style/AppTheme"> android:supportsRtl="true"
<activity android:name=".DrawerActivity"/> android:theme="@style/AppTheme"
tools:ignore="AllowBackup">
<activity
android:name=".LogActivity"
android:exported="false" />
<activity android:name=".DrawerActivity" />
<activity android:name=".GridDividerActivity" /> <activity android:name=".GridDividerActivity" />
<activity android:name=".TipActivity" /> <activity android:name=".TipActivity" />
<activity android:name=".CommomPopActivity" /> <activity android:name=".CommomPopActivity" />
<activity android:name=".TopBarActivity" /> <activity android:name=".TopBarActivity" />
<activity android:name="com.qimai.android.widget.MainActivity"> <activity android:name=".MainActivity">
</activity>
<activity android:name="BtListActivity"/>
<activity android:name=".MainActivityPtinter">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN" /> <action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" /> <category android:name="android.intent.category.LAUNCHER" />
</intent-filter> </intent-filter>
</activity> </activity>
<activity android:name=".UsbActivity"/> <activity android:name=".BtListActivity" />
<activity android:name=".MainActivityPtinter"></activity>
<activity android:name=".UsbActivity" />
</application> </application>
</manifest> </manifest>
\ No newline at end of file
/*! art-template@4.13.2 for browser | https://github.com/aui/art-template */
!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?exports.template=t():e.template=t()}("undefined"!=typeof self?self:this,function(){return function(e){function t(r){if(n[r])return n[r].exports;var i=n[r]={i:r,l:!1,exports:{}};return e[r].call(i.exports,i,i.exports,t),i.l=!0,i.exports}var n={};return t.m=e,t.c=n,t.d=function(e,n,r){t.o(e,n)||Object.defineProperty(e,n,{configurable:!1,enumerable:!0,get:r})},t.n=function(e){var n=e&&e.__esModule?function(){return e["default"]}:function(){return e};return t.d(n,"a",n),n},t.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},t.p="",t(t.s=4)}([function(e,t,n){"use strict";var r=n(6),i=n(2),o=n(22),s=function(e,t){t.onerror(e,t);var n=function(){return"{Template Error}"};return n.mappings=[],n.sourcesContent=[],n},a=function u(e){var t=arguments.length>1&&arguments[1]!==undefined?arguments[1]:{};"string"!=typeof e?t=e:t.source=e,t=i.$extend(t),e=t.source,!0===t.debug&&(t.cache=!1,t.minimize=!1,t.compileDebug=!0),t.compileDebug&&(t.minimize=!1),t.filename&&(t.filename=t.resolveFilename(t.filename,t));var n=t.filename,a=t.cache,c=t.caches;if(a&&n){var l=c.get(n);if(l)return l}if(!e)try{e=t.loader(n,t),t.source=e}catch(m){var f=new o({name:"CompileError",path:n,message:"template not found: "+m.message,stack:m.stack});if(t.bail)throw f;return s(f,t)}var p=void 0,h=new r(t);try{p=h.build()}catch(f){if(f=new o(f),t.bail)throw f;return s(f,t)}var d=function(e,n){try{return p(e,n)}catch(f){if(!t.compileDebug)return t.cache=!1,t.compileDebug=!0,u(t)(e,n);if(f=new o(f),t.bail)throw f;return s(f,t)()}};return d.mappings=p.mappings,d.sourcesContent=p.sourcesContent,d.toString=function(){return p.toString()},a&&n&&c.set(n,d),d};a.Compiler=r,e.exports=a},function(e,t){Object.defineProperty(t,"__esModule",{value:!0}),t["default"]=/((['"])(?:(?!\2|\\).|\\(?:\r\n|[\s\S]))*(\2)?|`(?:[^`\\$]|\\[\s\S]|\$(?!\{)|\$\{(?:[^{}]|\{[^}]*\}?)*\}?)*(`)?)|(\/\/.*)|(\/\*(?:[^*]|\*(?!\/))*(\*\/)?)|(\/(?!\*)(?:\[(?:(?![\]\\]).|\\.)*\]|(?![\/\]\\]).|\\.)+\/(?:(?!\s*(?:\b|[\u0080-\uFFFF$\\'"~({]|[+\-!](?!=)|\.?\d))|[gmiyu]{1,5}\b(?![\u0080-\uFFFF$\\]|\s*(?:[+\-*%&|^<>!=?({]|\/(?![\/*])))))|(0[xX][\da-fA-F]+|0[oO][0-7]+|0[bB][01]+|(?:\d*\.\d+|\d+\.?)(?:[eE][+-]?\d+)?)|((?!\d)(?:(?!\s)[$\w\u0080-\uFFFF]|\\u[\da-fA-F]{4}|\\u\{[\da-fA-F]+\})+)|(--|\+\+|&&|\|\||=>|\.{3}|(?:[+\-\/%&|^]|\*{1,2}|<{1,2}|>{1,3}|!=?|={1,2})=?|[?~.,:;[\](){}])|(\s+)|(^$|[\s\S])/g,t.matchToToken=function(e){var t={type:"invalid",value:e[0]};return e[1]?(t.type="string",t.closed=!(!e[3]&&!e[4])):e[5]?t.type="comment":e[6]?(t.type="comment",t.closed=!!e[7]):e[8]?t.type="regex":e[9]?t.type="number":e[10]?t.type="name":e[11]?t.type="punctuator":e[12]&&(t.type="whitespace"),t}},function(e,t,n){"use strict";function r(){this.$extend=function(e){return e=e||{},o(e,e instanceof r?e:this)}}var i=n(10),o=n(12),s=n(13),a=n(14),u=n(15),c=n(16),l=n(17),f=n(18),p=n(19),h=n(21),d="undefined"==typeof window,m={source:null,filename:null,rules:[f,l],escape:!0,debug:!!d&&true,bail:!0,cache:!0,minimize:!0,compileDebug:!1,resolveFilename:h,include:s,htmlMinifier:p,htmlMinifierOptions:{collapseWhitespace:!0,minifyCSS:!0,minifyJS:!0,ignoreCustomFragments:[]},onerror:a,loader:c,caches:u,root:"/",extname:".art",ignore:[],imports:i};r.prototype=m,e.exports=new r},function(e,t){},function(e,t,n){"use strict";var r=n(5),i=n(0),o=n(23),s=function(e,t){return t instanceof Object?r({filename:e},t):i({filename:e,source:t})};s.render=r,s.compile=i,s.defaults=o,e.exports=s},function(e,t,n){"use strict";var r=n(0),i=function(e,t,n){return r(e,n)(t)};e.exports=i},function(e,t,n){"use strict";function r(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function i(e){if(Array.isArray(e)){for(var t=0,n=Array(e.length);t<e.length;t++)n[t]=e[t];return n}return Array.from(e)}function o(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}var s=function(){function e(e,t){for(var n=0;n<t.length;n++){var r=t[n];r.enumerable=r.enumerable||!1,r.configurable=!0,"value"in r&&(r.writable=!0),Object.defineProperty(e,r.key,r)}}return function(t,n,r){return n&&e(t.prototype,n),r&&e(t,r),t}}(),a=n(7),u=n(9),c="$data",l="$imports",f="print",p="include",h="extend",d="block",m="$$out",v="$$line",g="$$blocks",y="$$slice",b="$$from",w="$$options",x=function(e,t){return Object.hasOwnProperty.call(e,t)},k=JSON.stringify,E=function(){function e(t){var n,s,a=this;o(this,e);var x=t.source,k=t.minimize,E=t.htmlMinifier;if(this.options=t,this.stacks=[],this.context=[],this.scripts=[],this.CONTEXT_MAP={},this.ignore=[c,l,w].concat(i(t.ignore)),this.internal=(n={},r(n,m,"''"),r(n,v,"[0,0]"),r(n,g,"arguments[1]||{}"),r(n,b,"null"),r(n,f,"function(){var s=''.concat.apply('',arguments);"+m+"+=s;return s}"),r(n,p,"function(src,data){var s="+w+".include(src,data||"+c+",arguments[2]||"+g+","+w+");"+m+"+=s;return s}"),r(n,h,"function(from){"+b+"=from}"),r(n,y,"function(c,p,s){p="+m+";"+m+"='';c();s="+m+";"+m+"=p+s;return s}"),r(n,d,"function(){var a=arguments,s;if(typeof a[0]==='function'){return "+y+"(a[0])}else if("+b+"){if(!"+g+"[a[0]]){"+g+"[a[0]]="+y+"(a[1])}else{"+m+"+="+g+"[a[0]]}}else{s="+g+"[a[0]];if(typeof s==='string'){"+m+"+=s}else{s="+y+"(a[1])}return s}}"),n),this.dependencies=(s={},r(s,f,[m]),r(s,p,[m,w,c,g]),r(s,h,[b,p]),r(s,d,[y,b,m,g]),s),this.importContext(m),t.compileDebug&&this.importContext(v),k)try{x=E(x,t)}catch(T){}this.source=x,this.getTplTokens(x,t.rules,this).forEach(function(e){e.type===u.TYPE_STRING?a.parseString(e):a.parseExpression(e)})}return s(e,[{key:"getTplTokens",value:function(){return u.apply(undefined,arguments)}},{key:"getEsTokens",value:function(e){return a(e)}},{key:"getVariables",value:function(e){var t=!1;return e.filter(function(e){return"whitespace"!==e.type&&"comment"!==e.type}).filter(function(e){return"name"===e.type&&!t||(t="punctuator"===e.type&&"."===e.value,!1)}).map(function(e){return e.value})}},{key:"importContext",value:function(e){var t=this,n="",r=this.internal,i=this.dependencies,o=this.ignore,s=this.context,a=this.options,u=a.imports,f=this.CONTEXT_MAP;x(f,e)||-1!==o.indexOf(e)||(x(r,e)?(n=r[e],x(i,e)&&i[e].forEach(function(e){return t.importContext(e)})):n="$escape"===e||"$each"===e||x(u,e)?l+"."+e:c+"."+e,f[e]=n,s.push({name:e,value:n}))}},{key:"parseString",value:function(e){var t=e.value;if(t){var n=m+"+="+k(t);this.scripts.push({source:t,tplToken:e,code:n})}}},{key:"parseExpression",value:function(e){var t=this,n=e.value,r=e.script,i=r.output,o=this.options.escape,s=r.code;i&&(s=!1===o||i===u.TYPE_RAW?m+"+="+r.code:m+"+=$escape("+r.code+")");var a=this.getEsTokens(s);this.getVariables(a).forEach(function(e){return t.importContext(e)}),this.scripts.push({source:n,tplToken:e,code:s})}},{key:"checkExpression",value:function(e){for(var t=[[/^\s*}[\w\W]*?{?[\s;]*$/,""],[/(^[\w\W]*?\([\w\W]*?(?:=>|\([\w\W]*?\))\s*{[\s;]*$)/,"$1})"],[/(^[\w\W]*?\([\w\W]*?\)\s*{[\s;]*$)/,"$1}"]],n=0;n<t.length;){if(t[n][0].test(e)){var r;e=(r=e).replace.apply(r,i(t[n]));break}n++}try{return new Function(e),!0}catch(o){return!1}}},{key:"build",value:function(){var e=this.options,t=this.context,n=this.scripts,r=this.stacks,i=this.source,o=e.filename,s=e.imports,a=[],f=x(this.CONTEXT_MAP,h),d=0,y=function(e,t){var n=t.line,i=t.start,o={generated:{line:r.length+d+1,column:1},original:{line:n+1,column:i+1}};return d+=e.split(/\n/).length-1,o},E=function(e){return e.replace(/^[\t ]+|[\t ]$/g,"")};r.push("function("+c+"){"),r.push("'use strict'"),r.push(c+"="+c+"||{}"),r.push("var "+t.map(function(e){return e.name+"="+e.value}).join(",")),e.compileDebug?(r.push("try{"),n.forEach(function(e){e.tplToken.type===u.TYPE_EXPRESSION&&r.push(v+"=["+[e.tplToken.line,e.tplToken.start].join(",")+"]"),a.push(y(e.code,e.tplToken)),r.push(E(e.code))}),r.push("}catch(error){"),r.push("throw {"+["name:'RuntimeError'","path:"+k(o),"message:error.message","line:"+v+"[0]+1","column:"+v+"[1]+1","source:"+k(i),"stack:error.stack"].join(",")+"}"),r.push("}")):n.forEach(function(e){a.push(y(e.code,e.tplToken)),r.push(E(e.code))}),f&&(r.push(m+"=''"),r.push(p+"("+b+","+c+","+g+")")),r.push("return "+m),r.push("}");var T=r.join("\n");try{var O=new Function(l,w,"return "+T)(s,e);return O.mappings=a,O.sourcesContent=[i],O}catch(P){for(var $=0,j=0,_=0,S=void 0;$<n.length;){var C=n[$];if(!this.checkExpression(C.code)){j=C.tplToken.line,_=C.tplToken.start,S=C.code;break}$++}throw{name:"CompileError",path:o,message:P.message,line:j+1,column:_+1,source:i,generated:S,stack:P.stack}}}}]),e}();E.CONSTS={DATA:c,IMPORTS:l,PRINT:f,INCLUDE:p,EXTEND:h,BLOCK:d,OPTIONS:w,OUT:m,LINE:v,BLOCKS:g,SLICE:y,FROM:b,ESCAPE:"$escape",EACH:"$each"},e.exports=E},function(e,t,n){"use strict";var r=n(8),i=n(1)["default"],o=n(1).matchToToken,s=function(e){return e.match(i).map(function(e){return i.lastIndex=0,o(i.exec(e))}).map(function(e){return"name"===e.type&&r(e.value)&&(e.type="keyword"),e})};e.exports=s},function(e,t,n){"use strict";var r={"abstract":!0,await:!0,"boolean":!0,"break":!0,"byte":!0,"case":!0,"catch":!0,"char":!0,"class":!0,"const":!0,"continue":!0,"debugger":!0,"default":!0,"delete":!0,"do":!0,"double":!0,"else":!0,"enum":!0,"export":!0,"extends":!0,"false":!0,"final":!0,"finally":!0,"float":!0,"for":!0,"function":!0,"goto":!0,"if":!0,"implements":!0,"import":!0,"in":!0,"instanceof":!0,"int":!0,"interface":!0,"let":!0,"long":!0,"native":!0,"new":!0,"null":!0,"package":!0,"private":!0,"protected":!0,"public":!0,"return":!0,"short":!0,"static":!0,"super":!0,"switch":!0,"synchronized":!0,"this":!0,"throw":!0,"transient":!0,"true":!0,"try":!0,"typeof":!0,"var":!0,"void":!0,"volatile":!0,"while":!0,"with":!0,"yield":!0};e.exports=function(e){return r.hasOwnProperty(e)}},function(e,t,n){"use strict";function r(e){var t=new String(e.value);return t.line=e.line,t.start=e.start,t.end=e.end,t}function i(e,t,n){this.type=e,this.value=t,this.script=null,n?(this.line=n.line+n.value.split(/\n/).length-1,this.line===n.line?this.start=n.end:this.start=n.value.length-n.value.lastIndexOf("\n")-1):(this.line=0,this.start=0),this.end=this.start+this.value.length}var o=function(e,t){for(var n=arguments.length>2&&arguments[2]!==undefined?arguments[2]:{},o=[new i("string",e)],s=0;s<t.length;s++)for(var a=t[s],u=a.test.ignoreCase?"ig":"g",c=new RegExp(a.test.source,u),l=0;l<o.length;l++){var f=o[l],p=o[l-1];if("string"===f.type){for(var h=void 0,d=0,m=[],v=f.value;null!==(h=c.exec(v));)h.index>d&&(p=new i("string",v.slice(d,h.index),p),m.push(p)),p=new i("expression",h[0],p),h[0]=r(p),p.script=a.use.apply(n,h),m.push(p),d=h.index+h[0].length;d<v.length&&(p=new i("string",v.slice(d),p),m.push(p)),o.splice.apply(o,[l,1].concat(m)),l+=m.length-1}}return o};o.TYPE_STRING="string",o.TYPE_EXPRESSION="expression",o.TYPE_RAW="raw",o.TYPE_ESCAPE="escape",e.exports=o},function(e,t,n){"use strict";(function(t){function n(e){return"string"!=typeof e&&(e=e===undefined||null===e?"":"function"==typeof e?n(e.call(e)):JSON.stringify(e)),e}function r(e){var t=""+e,n=s.exec(t);if(!n)return e;var r="",i=void 0,o=void 0,a=void 0;for(i=n.index,o=0;i<t.length;i++){switch(t.charCodeAt(i)){case 34:a="&#34;";break;case 38:a="&#38;";break;case 39:a="&#39;";break;case 60:a="&#60;";break;case 62:a="&#62;";break;default:continue}o!==i&&(r+=t.substring(o,i)),o=i+1,r+=a}return o!==i?r+t.substring(o,i):r}/*! art-template@runtime | https://github.com/aui/art-template */
var i="undefined"!=typeof self?self:"undefined"!=typeof window?window:void 0!==t?t:{},o=Object.create(i),s=/["&'<>]/;o.$escape=function(e){return r(n(e))},o.$each=function(e,t){if(Array.isArray(e))for(var n=0,r=e.length;n<r;n++)t(e[n],n);else for(var i in e)t(e[i],i)},e.exports=o}).call(t,n(11))},function(e,t){var n;n=function(){return this}();try{n=n||Function("return this")()||(0,eval)("this")}catch(r){"object"==typeof window&&(n=window)}e.exports=n},function(e,t,n){"use strict";var r=Object.prototype.toString,i=function(e){return null===e?"Null":r.call(e).slice(8,-1)},o=function s(e,t){var n=void 0,r=i(e);if("Object"===r?n=Object.create(t||{}):"Array"===r&&(n=[].concat(t||[])),n){for(var o in e)Object.hasOwnProperty.call(e,o)&&(n[o]=s(e[o],n[o]));return n}return e};e.exports=o},function(e,t,n){"use strict";var r=function(e,t,r,i){var o=n(0);return i=i.$extend({filename:i.resolveFilename(e,i),bail:!0,source:null}),o(i)(t,r)};e.exports=r},function(e,t,n){"use strict";var r=function(e){console.error(e.name,e.message)};e.exports=r},function(e,t,n){"use strict";var r={__data:Object.create(null),set:function(e,t){this.__data[e]=t},get:function(e){return this.__data[e]},reset:function(){this.__data={}}};e.exports=r},function(e,t,n){"use strict";var r="undefined"==typeof window,i=function(e){if(r){return n(3).readFileSync(e,"utf8")}var t=document.getElementById(e);return t.value||t.innerHTML};e.exports=i},function(e,t,n){"use strict";var r={test:/{{([@#]?)[ \t]*(\/?)([\w\W]*?)[ \t]*}}/,use:function(e,t,n,i){var o=this,s=o.options,a=o.getEsTokens(i),u=a.map(function(e){return e.value}),c={},l=void 0,f=!!t&&"raw",p=n+u.shift(),h=function(t,n){console.warn((s.filename||"anonymous")+":"+(e.line+1)+":"+(e.start+1)+"\nTemplate upgrade: {{"+t+"}} -> {{"+n+"}}")};switch("#"===t&&h("#value","@value"),p){case"set":i="var "+u.join("").trim();break;case"if":i="if("+u.join("").trim()+"){";break;case"else":var d=u.indexOf("if");~d?(u.splice(0,d+1),i="}else if("+u.join("").trim()+"){"):i="}else{";break;case"/if":i="}";break;case"each":l=r._split(a),l.shift(),"as"===l[1]&&(h("each object as value index","each object value index"),l.splice(1,1));i="$each("+(l[0]||"$data")+",function("+(l[1]||"$value")+","+(l[2]||"$index")+"){";break;case"/each":i="})";break;case"block":l=r._split(a),l.shift(),i="block("+l.join(",").trim()+",function(){";break;case"/block":i="})";break;case"echo":p="print",h("echo value","value");case"print":case"include":case"extend":if(0!==u.join("").trim().indexOf("(")){l=r._split(a),l.shift(),i=p+"("+l.join(",")+")";break}default:if(~u.indexOf("|")){var m=a.reduce(function(e,t){var n=t.value,r=t.type;return"|"===n?e.push([]):"whitespace"!==r&&"comment"!==r&&(e.length||e.push([]),":"===n&&1===e[e.length-1].length?h("value | filter: argv","value | filter argv"):e[e.length-1].push(t)),e},[]).map(function(e){return r._split(e)});i=m.reduce(function(e,t){var n=t.shift();return t.unshift(e),"$imports."+n+"("+t.join(",")+")"},m.shift().join(" ").trim())}f=f||"escape"}return c.code=i,c.output=f,c},_split:function(e){e=e.filter(function(e){var t=e.type;return"whitespace"!==t&&"comment"!==t});for(var t=0,n=e.shift(),r=/\]|\)/,i=[[n]];t<e.length;){var o=e[t];"punctuator"===o.type||"punctuator"===n.type&&!r.test(n.value)?i[i.length-1].push(o):i.push([o]),n=o,t++}return i.map(function(e){return e.map(function(e){return e.value}).join("")})}};e.exports=r},function(e,t,n){"use strict";var r={test:/<%(#?)((?:==|=#|[=-])?)[ \t]*([\w\W]*?)[ \t]*(-?)%>/,use:function(e,t,n,r){return n={"-":"raw","=":"escape","":!1,"==":"raw","=#":"raw"}[n],t&&(r="/*"+r+"*/",n=!1),{code:r,output:n}}};e.exports=r},function(e,t,n){"use strict";function r(e){if(Array.isArray(e)){for(var t=0,n=Array(e.length);t<e.length;t++)n[t]=e[t];return n}return Array.from(e)}var i="undefined"==typeof window,o=function(e,t){if(i){var o,s=n(20).minify,a=t.htmlMinifierOptions,u=t.rules.map(function(e){return e.test});(o=a.ignoreCustomFragments).push.apply(o,r(u)),e=s(e,a)}return e};e.exports=o},function(e,t){!function(e){e.noop=function(){}}("object"==typeof e&&"object"==typeof e.exports?e.exports:window)},function(e,t,n){"use strict";var r="undefined"==typeof window,i=/^\.+\//,o=function(e,t){if(r){var o=n(3),s=t.root,a=t.extname;if(i.test(e)){var u=t.filename,c=!u||e===u,l=c?s:o.dirname(u);e=o.resolve(l,e)}else e=o.resolve(s,e);o.extname(e)||(e+=a)}return e};e.exports=o},function(e,t,n){"use strict";function r(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function i(e,t){if(!e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!t||"object"!=typeof t&&"function"!=typeof t?e:t}function o(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function, not "+typeof t);e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),t&&(Object.setPrototypeOf?Object.setPrototypeOf(e,t):e.__proto__=t)}function s(e){var t=e.name,n=e.source,r=e.path,i=e.line,o=e.column,s=e.generated,a=e.message;if(!n)return a;var u=n.split(/\n/),c=Math.max(i-3,0),l=Math.min(u.length,i+3),f=u.slice(c,l).map(function(e,t){var n=t+c+1;return(n===i?" >> ":" ")+n+"| "+e}).join("\n");return(r||"anonymous")+":"+i+":"+o+"\n"+f+"\n\n"+t+": "+a+(s?"\n generated: "+s:"")}var a=function(e){function t(e){r(this,t);var n=i(this,(t.__proto__||Object.getPrototypeOf(t)).call(this,e.message));return n.name="TemplateError",n.message=s(e),Error.captureStackTrace&&Error.captureStackTrace(n,n.constructor),n}return o(t,e),t}(Error);e.exports=a},function(e,t,n){"use strict";e.exports=n(2)}])});
\ No newline at end of file
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
function print1(template1,data){
var html = template1
// let encoding = require('./encoding')
// let TextEncoder = encoding.TextEncoder
// let template = require('./art-template')
// android.printString(html)
var render = template.render(html, JSON.parse(data));
// var htmlparser2 = require('./htmlparser2')
var byteArray = [27, 64, 10]
var uint8array = new TextEncoder('gbk', { NONSTANDARD_allowLegacyEncoding: true })
var handler = new htmlparser2.DomHandler(function(error, dom) {
dom.forEach(item => {
if (item.name) {
var children = item.children
if (children && children.length == 1 && children[0].type == 'text') {
// console.log(uint8array.encode('值编号:SDJL1293780123') + '')
byteArray = byteArray.concat(Array.from(uint8array.encode(children[0].data)))
byteArray = byteArray.concat([10])
}
if (children.length > 1) {
children.forEach(child => {
var childChildren = child.children
if (childChildren && childChildren.length == 1 && childChildren[0].type == 'text') {
var childStyle = child.attribs.style
if (childStyle) {
var styleObject = tools.normalizeStyle(childStyle)
if (styleObject['text-align']) {
console.log(tools.fontAlign(styleObject['text-align']))
byteArray = byteArray.concat(tools.fontAlign(styleObject['text-align']))
}
}
byteArray = byteArray.concat(Array.from(uint8array.encode(childChildren[0].data)))
byteArray = byteArray.concat([10])
}
})
}
}
})
byteArray = byteArray.concat([10, 10, 10, 10])
android.print12(byteArray)
// console.log(byteArray + '')
})
var parser = new htmlparser2.Parser(handler)
// var parser = new htmlparser2.Parser({
// onopentag(name, attributes) {
// console.log(name)
// console.log(attributes)
// // html = html + '<' + name + '>'
// // if (name === 'span') {
// // name = 'p'
// // }
// },
// // ontext(text) {
// // console.log("-->", text);
// // // html = html + '{{value}}'
// // },
// // onclosetag(tagname) {
// // // if (tagname === "script") {
// // // console.log("That's it?!");
// // // }
// // // html = html + '</' + tagname + '>'
// // console.log(tagname, '结尾')
// // },
// });
parser.write(render);
parser.end();
// function transform() {
// return render
// }
// console.log(transform())
// let encoding = require('./encoding')
// let TextEncoder = encoding.TextEncoder
// console.log(TextEncoder)
var uint8array = new TextEncoder(
'gbk', { NONSTANDARD_allowLegacyEncoding: true }).encode('值编号:SDJL1293780123');
// function encodeArrayOfStrings(strings) {
// var encoder, encoded, len, i, bytes, view, offset;
// encoder = new TextEncoder();
// encoded = [];
// len = Uint16Array.BYTES_PER_ELEMENT;
// for (i = 0; i < strings.length; i += 1) {
// len += Uint16Array.BYTES_PER_ELEMENT;
// encoded[i] = new TextEncoder().encode(strings[i]);
// len += encoded[i].byteLength;
// }
// bytes = new Uint16Array(len);
// view = new DataView(bytes.buffer);
// offset = 0;
// view.setUint32(offset, strings.length);
// offset += Uint16Array.BYTES_PER_ELEMENT;
// for (i = 0; i < encoded.length; i += 1) {
// len = encoded[i].byteLength;
// view.setUint32(offset, len);
// offset += Uint16Array.BYTES_PER_ELEMENT;
// bytes.set(encoded[i], offset);
// offset += len;
// }
// return bytes;
// }
// var aa = encodeArrayOfStrings('hello world')
return
}
\ No newline at end of file
var INITBYTE = [0x1b, 0x40];
var LEFT_ALIGIN = [0x1b, 97, 0];
var CENTER_ALIGIN = [0x1b, 97, 1];
var RIGHT_ALIGIN = [0x1b, 97, 2];
var FONT_BOLD = [0x1b, 0x45, 0x01];
var FONT_NORMAL = [0x1b, 0x45, 0x00];
var FONT_TYPE_0 = [0x1d, 0x21, 0x00];
var FONT_TYPE_1 = [0x1d, 0x21, 0x10];
var FONT_TYPE_2 = [0x1d, 0x21, 0x01];
var FONT_TYPE_3 = [0x1d, 0x21, 17];
var PRINT_AND_FEED = [10];
var dataArray = [];
var hockeyTeam = {
dataArray1 : [],
templateJson: "",
dataJson: "",
getPrintData:function(){
writeValue("企迈测试");
return stringToByte("企迈测试");
},
};
function print1(){
var arrays2 =
[27, 64, 10, 27, 51, 20, 27, 97, 1, 29, 33, 17, 27, 69, 0, 83, 97, 109, 112, 108,
101, 10, 10, 27, 51, 20, 27, 97, 0, 29, 33, 0, 27, 69, 0, 50, 51, 51, 51, 51, 10,
10, 27, 51, 20, 27, 97, 0, 29, 33, 0, 27, 69, 0, 50, 51, 51, 51, 51, 10, 10, 27,
51, 20, 27, 97, 0, 29, 33, 0, 27, 69, 0, 50, 51, 50, 51, 51, 51, 10, 10, 27, 51,
20, 27, 97, 0, 29, 33, 0, 27, 69, 0, 50, 50, 51, 51, 51, 51, 10, 10, 27, 51, 20,
27, 97, 0, 29, 33, 0, 27, 69, 0, 49, 50, 49, 51, 51, 51, 51, 10, 10, 27, 51, 20,
27, 97, 1, 29, 33, 17, 27, 69, 0, 45, 45, 45, -46, -47, -42, -89, -72, -74, 45, 45, 45, 10,
10, 27, 51, 20, 27, 97, 0, 29, 33, 0, 27, 69, 0, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46,
46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46,
46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 10, 10, 27, 51, 20, 27, 97, 0, 29, 33, 1, 27,
69, 0, -78, -30, -54, -44, -55, -52, -58, -73, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 42, 49, 32, 32, 32, 32, 32, 32, 49, 50, 46, 48, 48, 10, 10, 27, 51, 20, 27, 97, 0, 29, 33, 1, 27,
69, 0, -78, -30, -54, -44, -55, -52, -58, -73, 50, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
32, 32, 32, -95, -66, 42, 50, -95, -65, 32, 32, 32, 32, 32, 49, 53, 46, 48, 48, 10, 10, 27, 51, 20, 27, 97, 0, 29, 33, 0, 27, 69, 0, 46,
46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46,
46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 10, 10, 27, 51, 20, 27, 97, 0, 29, 33, 0, 27, 69, 0, -74, -87, -75, -91, -70, -59, 58, 70,
68, 49, 50, 52, 81, 52, 52, 51, 52, 50, 50, 10, 10, 27, 51, 20, 27, 97, 0, 29, 33, 0, 27, 69, 0, -48, -69, -48, -69, -69, -35, -71, -53,
-93, -84, -69, -74, -45, -83, -49, -62, -76, -50, -71, -30, -63, -39, 10, 10, 10, 10, 10];
dataArray = write([0x1b, 0x40]);
return dataArray;
}
function stringToByte(str) {
var bytes = new Array();
var len, c;
len = str.length;
for(var i = 0; i < len; i++) {
c = str.charCodeAt(i);
if(c >= 0x010000 && c <= 0x10FF FF) {
bytes.push(((c >> 18) & 0x07) | 0xF0);
bytes.push(((c >> 12) & 0x3F) | 0x80);
bytes.push(((c >> 6) & 0x3F) | 0x80);
bytes.push((c & 0x3F) | 0x80);
} else if(c >= 0x000800 && c <= 0x00FFFF) {
bytes.push(((c >> 12) & 0x0F) | 0xE0);
bytes.push(((c >> 6) & 0x3F) | 0x80);
bytes.push((c & 0x3F) | 0x80);
} else if(c >= 0x000080 && c <= 0x0007FF) {
bytes.push(((c >> 6) & 0x1F) | 0xC0);
bytes.push((c & 0x3F) | 0x80);
} else {
bytes.push(c & 0xFF);
}
}
return bytes;
}
function write(value) {
dataArray = dataArray.concat(value);
return dataArray;
}
function writeValue(value){
return write(stringToByte(value));
}
function writeAlign(align) {
switch (align) {
case 0:
write(LEFT_ALIGIN);
break;
case 1:
write(CENTER_ALIGIN);
break;
case 2 :
write(RIGHT_ALIGIN);
break;
}
return dataArray;
}
function writePrintAndLineFeed() {
return write(PRINT_AND_FEED);
}
function writeNewLine(num) {
for(var i=0;i<num;i++) {
write([10]);
}
return dataArray;
}
function writelnLineText(value,aligin) {
writePrintAndLineFeed();
writeAlign(aligin);
writeValue(value);
writeNewLine(1);
return dataArray;
}
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
typeof define === 'function' && define.amd ? define(factory) :
(global = global || self, global.tools = factory());
}(this, (function () { 'use strict';
var tools = {
// 文字大小
fontSize(size) {
var array = []
switch(size) {
case 'normal':
array = [29, 33, 0]
break
case 'doubleHeight':
array = [29, 33, 1]
break
case 'doubleWidth':
array = [29, 33, 16]
break
case 'double':
array = [29, 33, 17]
break
}
return array
},
// 文字加粗
fontBold() {
return [27, 69, 1]
},
// 文字对齐方式
fontAlign(align) {
var array = []
switch(align) {
case 'left':
array = [27, 97, 48]
break
case 'center':
array = [27, 97, 49]
break
case 'right':
array = [27, 97, 50]
break
}
return array
},
normalizeStyle(styleString) {
let styleArray = styleString.split(';')
let styleObject = {}
styleArray.forEach(item => {
if (item) {
var styleItem = item.split(':')
styleObject[styleItem[0]] = styleItem[1]
}
})
return styleObject
}
}
return tools
})))
\ No newline at end of file
...@@ -3,7 +3,7 @@ package com.qimai.android.widget ...@@ -3,7 +3,7 @@ package com.qimai.android.widget
import android.app.Application import android.app.Application
import zs.qimai.com.printer2.utils.PrintManagerUtils import zs.qimai.com.printer2.utils.PrintManagerUtils
class App: Application() { class App : Application() {
override fun onCreate() { override fun onCreate() {
super.onCreate() super.onCreate()
PrintManagerUtils.getInstance().init(this) PrintManagerUtils.getInstance().init(this)
......
...@@ -71,7 +71,7 @@ class BtListActivity : AppCompatActivity(), View.OnClickListener { ...@@ -71,7 +71,7 @@ class BtListActivity : AppCompatActivity(), View.OnClickListener {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setContentView(R.layout.activity_bt_list) setContentView(R.layout.activity_bt_list)
Log.d(TAG, "onCreate: model= ${Build.MODEL} brand = ${Build.BRAND} ") Log.d(TAG, "onCreate: model= ${Build.MODEL} brand = ${Build.BRAND} ")
PrintManagerUtils.getInstance().getSearchBlueToothList(object : BlueToothSearchCallBack { PrintManagerUtils.getInstance().getSearchBlueToothList(this,object : BlueToothSearchCallBack {
override fun onRefuseGrantPermission() { override fun onRefuseGrantPermission() {
} }
......
package com.qimai.android.widget
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.lifecycleScope
import com.qimai.android.widget.databinding.ActivityLogBinding
import com.qimai.log.QLog
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
class LogActivity : AppCompatActivity() {
private lateinit var bind: ActivityLogBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
bind = ActivityLogBinding.inflate(layoutInflater)
setContentView(bind.root)
init()
}
private fun init() {
bind.btnLog.setOnClickListener { initLog() }
bind.btnWrite.setOnClickListener {
lifecycleScope.launch(Dispatchers.IO) {
QLog.write("123456", QLog.D)
withContext(Dispatchers.Main) {
QLog.write("123456789", QLog.E)
}
}
}
}
/**
* 初始化LogUp
*/
private fun initLog() {
QLog.init(true)
}
}
\ No newline at end of file
...@@ -9,7 +9,6 @@ import android.widget.TextView ...@@ -9,7 +9,6 @@ import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import com.qimai.android.widgetlib.popup.QmTipOverlayDrawable import com.qimai.android.widgetlib.popup.QmTipOverlayDrawable
import com.qimai.android.widgetlib.toast.HolderContext import com.qimai.android.widgetlib.toast.HolderContext
import com.qimai.log.QmLog
import kotlinx.android.synthetic.main.activity_main.* import kotlinx.android.synthetic.main.activity_main.*
class MainActivity : AppCompatActivity() { class MainActivity : AppCompatActivity() {
...@@ -17,11 +16,7 @@ class MainActivity : AppCompatActivity() { ...@@ -17,11 +16,7 @@ class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main) setContentView(R.layout.activity_main)
HolderContext.getInstance().register { HolderContext.getInstance().register { this }
this
}
QmLog.d("test1111","111111")
findViewById<Button>(R.id.commonPop).setOnClickListener { findViewById<Button>(R.id.commonPop).setOnClickListener {
// ToastUtils.getInstance().showCenter("测试测试") // ToastUtils.getInstance().showCenter("测试测试")
...@@ -47,22 +42,22 @@ class MainActivity : AppCompatActivity() { ...@@ -47,22 +42,22 @@ class MainActivity : AppCompatActivity() {
findViewById<TextView>(R.id.drawer).setOnClickListener { findViewById<TextView>(R.id.drawer).setOnClickListener {
startActivity(Intent(MainActivity@ this, CommomPopActivity::class.java)) startActivity(Intent(MainActivity@ this, CommomPopActivity::class.java))
} }
findViewById<Button>(R.id.tip).setOnClickListener { findViewById<Button>(R.id.tip).setOnClickListener {
startActivity(Intent(MainActivity@ this, TipActivity::class.java)) startActivity(Intent(MainActivity@ this, TipActivity::class.java))
} }
findViewById<Button>(R.id.topbar).setOnClickListener { findViewById<Button>(R.id.topbar).setOnClickListener {
startActivity(Intent(MainActivity@ this, TopBarActivity::class.java)) startActivity(Intent(MainActivity@ this, TopBarActivity::class.java))
} }
findViewById<Button>(R.id.divide).setOnClickListener { findViewById<Button>(R.id.divide).setOnClickListener {
startActivity(Intent(MainActivity@ this, GridDividerActivity::class.java)) startActivity(Intent(MainActivity@ this, GridDividerActivity::class.java))
} }
keyboard.btnText = "结账" findViewById<Button>(R.id.btn_printer).setOnClickListener {
startActivity(Intent(this, MainActivityPtinter::class.java))
}
findViewById<Button>(R.id.btn_log).setOnClickListener {
startActivity(Intent(this, LogActivity::class.java))
}
keyboard.btnText = "结账"
keyboard.invalidate() keyboard.invalidate()
} }
} }
package com.qimai.android.widget; package com.qimai.android.widget;
import android.content.Intent import android.content.Intent
import android.graphics.Bitmap
import android.graphics.drawable.Drawable
import android.hardware.usb.UsbDevice import android.hardware.usb.UsbDevice
import android.os.Build import android.os.Build
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle import android.os.Bundle
import android.os.Environment
import android.util.Log import android.util.Log
import androidx.annotation.RequiresApi import androidx.annotation.RequiresApi
import com.qimai.android.widget.R import androidx.appcompat.app.AppCompatActivity
import kotlinx.android.synthetic.main.activity_main.* import com.eclipsesource.v8.JavaVoidCallback
import com.eclipsesource.v8.V8
import com.eclipsesource.v8.V8Array
import com.eclipsesource.v8.V8Object
import kotlinx.android.synthetic.main.activity_main_printer.* import kotlinx.android.synthetic.main.activity_main_printer.*
import kotlinx.coroutines.* import kotlinx.coroutines.*
import zs.qimai.com.printer2.callback.* import zs.qimai.com.printer2.callback.*
...@@ -21,8 +26,11 @@ import zs.qimai.com.printer2.manager.DeviceManager.Companion.USB ...@@ -21,8 +26,11 @@ import zs.qimai.com.printer2.manager.DeviceManager.Companion.USB
import zs.qimai.com.printer2.manager.DeviceManagerUtils import zs.qimai.com.printer2.manager.DeviceManagerUtils
import zs.qimai.com.printer2.manager.UsbDeviceManager import zs.qimai.com.printer2.manager.UsbDeviceManager
import zs.qimai.com.printer2.utils.PrintManagerUtils import zs.qimai.com.printer2.utils.PrintManagerUtils
import java.io.*
import java.util.*
import kotlin.collections.ArrayList
class MainActivityPtinter : AppCompatActivity(), PrintConnOrDisCallBack { class MainActivityPtinter : AppCompatActivity(), PrintConnOrDisCallBack, JavaVoidCallback {
private var list = ArrayList<DeviceManager>() private var list = ArrayList<DeviceManager>()
...@@ -39,37 +47,106 @@ class MainActivityPtinter : AppCompatActivity(), PrintConnOrDisCallBack { ...@@ -39,37 +47,106 @@ class MainActivityPtinter : AppCompatActivity(), PrintConnOrDisCallBack {
private val TAG = "MainActivity" private val TAG = "MainActivity"
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
Log.d(TAG, "onCreate: brand= ${Build.BRAND} model= ${Build.MODEL}")
setContentView(R.layout.activity_main_printer) setContentView(R.layout.activity_main_printer)
DeviceManagerUtils.getInstance().addConnectStatusCallBack(this) DeviceManagerUtils.getInstance().addConnectStatusCallBack(this)
initUsbPrinter() initUsbPrinter()
val value = Utils.getSN() val value = Utils.getSN()
Log.d(TAG, "onCreate: value= $value") Log.d(TAG, "onCreate: value= $value")
tv_Log_up.setOnClickListener {
startActivity(Intent(this, LogActivity::class.java))
}
tv_connect_bt.setOnClickListener { tv_connect_bt.setOnClickListener {
startActivity(Intent(this, BtListActivity::class.java)) startActivity(Intent(this, BtListActivity::class.java))
} }
tv_print.setOnClickListener { tv_print.setOnClickListener {
//PrintExecutor().doPrint(TestPrintTemplate()) Log.d(TAG, "download start: ")
// PrintExecutor().doPrint(LabelTemplete())
val task = object : PrintExecutor() {
override fun covertEscData(): ByteArray? {
return TestPrintTemplate().getPrintData("122")
}
override fun convertTscData(): ByteArray? {
return LabelTemplete().getPrintData("Test") GlobalScope.launch(Dispatchers.IO){
}
val bitmap : Drawable? = Glide.with(this@MainActivityPtinter)
.load("https://ss0.bdstatic.com/5aV1bjqh_Q23odCf/static/superman/img/logo/logo_white_fe6da1ec.png")
.into(500,500)
.get()
Log.d(TAG, "onCreate: drawale bitmap = ${bitmap==null}")
val futureTask = Glide.with(this@MainActivityPtinter)
.load("https://ss0.bdstatic.com/5aV1bjqh_Q23odCf/static/superman/img/logo/logo_white_fe6da1ec.png")
.downloadOnly(500,500)
val file = futureTask.get()
Log.d(TAG, "download finish: file= ${file.absoluteFile}")
} }
task.mPrintCallBack = object : PrintCallBack { return@setOnClickListener
override fun printSuccess(deviceManager: DeviceManager) {
Log.d(TAG, "printSuccess: deviceManager = $deviceManager") /* val file =
File("${Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)}/test.js")
val bos = ByteArrayOutputStream()
val b = ByteArray(1024)
val `is`: InputStream = FileInputStream(file)
var len: Int
while (`is`!!.read(b).also { len = it } != -1) {
bos.write(b, 0, len)
}
val json = bos.toString()
Log.d(TAG, "onCreate: json= ${json}")
val value = "企迈测试".toByteArray(charset("gbk"))
Log.d(TAG, "onCreate value: value= ${Arrays.toString(value)}")*/
val v8 = V8.createV8Runtime()
// v8.executeJSFunction("print")
// val v8Array = v8.executeArrayScript("print1") as V8Array
// val v8Array = v8.executeScript(readFile()) as V8Array
// val byteArray = v8Array.getBytes(0,v8Array.length())
//Log.d(TAG, "onCreate: byteArray = ${Arrays.toString(byteArray)}")
v8.executeScript(readFile("htmlparser2.js"))
v8.executeScript(readFile("art-template.js"))
v8.executeScript(readFile("encoding-indexes.js"))
v8.executeScript(readFile("encoding.js"))
v8.executeScript(readFile("tools.js"))
v8.executeScript(readFile("operation.js"))
val android = Console()
val v8Object = V8Object(v8)
v8.add("android", v8Object)
v8Object.registerJavaMethod(
android,
"printString",
"printString",
arrayOf(String::class.java)
)
v8Object.registerJavaMethod(android, "print12", "print12", arrayOf(V8Array::class.java))
v8.executeJSFunction("print1")
//val v8Array =
// v8.executeJSFunction("print1")
/*as V8Array
val byteArray = v8Array.getBytes(0,v8Array.length())
val task = object : PrintExecutor() {
override fun covertEscData(): ByteArray? {
return TestPrintTemplate().getPrintData("122")
}
override fun convertTscData(): ByteArray? {
return LabelTemplete().getPrintData("Test")
}
} }
task.mPrintCallBack = object : PrintCallBack {
override fun printSuccess(deviceManager: DeviceManager) {
Log.d(TAG, "printSuccess: deviceManager = $deviceManager")
}
override fun printFailed(msg: String?) { override fun printFailed(msg: String?) {
Log.d(TAG, "printFailed: msg= $msg") Log.d(TAG, "printFailed: msg= $msg")
}
} }
} //task.execute()
task.execute() task.goToPrint(DeviceManagerUtils.getInstance().lists[0], byteArray)*/
} }
tv_connect_usb.setOnClickListener { tv_connect_usb.setOnClickListener {
startActivity(Intent(this, UsbActivity::class.java)) startActivity(Intent(this, UsbActivity::class.java))
...@@ -91,7 +168,7 @@ class MainActivityPtinter : AppCompatActivity(), PrintConnOrDisCallBack { ...@@ -91,7 +168,7 @@ class MainActivityPtinter : AppCompatActivity(), PrintConnOrDisCallBack {
} }
} }
tv_connect_wifi.setOnClickListener { tv_connect_wifi.setOnClickListener {
PrintManagerUtils.getInstance().wifiConnect("192.168.10.200", PrintManagerUtils.getInstance().wifiConnect("192.168.31.200",
object : OnConnectCallBack { object : OnConnectCallBack {
override fun onConnectStart() { override fun onConnectStart() {
Log.d(TAG, "onConnectStart: ") Log.d(TAG, "onConnectStart: ")
...@@ -128,7 +205,6 @@ class MainActivityPtinter : AppCompatActivity(), PrintConnOrDisCallBack { ...@@ -128,7 +205,6 @@ class MainActivityPtinter : AppCompatActivity(), PrintConnOrDisCallBack {
} }
} }
private fun initUsbPrinter() { private fun initUsbPrinter() {
DeviceManagerUtils.getInstance().addConnectStatusCallBack(object : PrintConnOrDisCallBack { DeviceManagerUtils.getInstance().addConnectStatusCallBack(object : PrintConnOrDisCallBack {
override fun onConectPrint(device: DeviceManager) { override fun onConectPrint(device: DeviceManager) {
...@@ -200,4 +276,106 @@ class MainActivityPtinter : AppCompatActivity(), PrintConnOrDisCallBack { ...@@ -200,4 +276,106 @@ class MainActivityPtinter : AppCompatActivity(), PrintConnOrDisCallBack {
DeviceManagerUtils.getInstance().removeConnectStatusCallBack(this) DeviceManagerUtils.getInstance().removeConnectStatusCallBack(this)
} }
fun readFile(fileNme: String): String {
var result = "没有"
var in1: InputStream? = null
try {
in1 = getAssets().open(fileNme)
} catch (e: IOException) {
e.printStackTrace()
}
val reader = BufferedReader(InputStreamReader(in1))
val sb = StringBuilder()
var line: String? = null
while ((reader.readLine().also { line = it }) != null) {
sb.append(line).append("\n")
}
return sb.toString()
}
override fun invoke(p0: V8Object?, p1: V8Array?) {
TODO("Not yet implemented")
}
fun print12(value: V8Array) {
val value1 = value.getBytes(0, value.length())
Log.d(TAG, "print: value1= ${Arrays.toString(value1)}")
val task = object : PrintExecutor() {
override fun covertEscData(): ByteArray? {
return TestPrintTemplate().getPrintData("122")
}
override fun convertTscData(): ByteArray? {
return LabelTemplete().getPrintData("Test")
}
}
task.mPrintCallBack = object : PrintCallBack {
override fun printSuccess(deviceManager: DeviceManager) {
Log.d(TAG, "printSuccess: deviceManager = $deviceManager")
}
override fun printFailed(msg: String?) {
Log.d(TAG, "printFailed: msg= $msg")
}
}
//task.execute()
task.goToPrint(DeviceManagerUtils.getInstance().lists[0], value1)
value1.forEach {
Log.d(TAG, "test123: 111 value= ${it}")
}
"hello world".toByteArray().forEach {
Log.d(TAG, "print: it= $it")
}
}
class Console {
fun printString(text: String) {
Log.d("TAG", "printString: text= ${text}")
}
fun print12(value: V8Array) {
val value1 = value.getBytes(0, value.length())
Log.d("TAG", "print: value1= ${Arrays.toString(value1)}")
val task = object : PrintExecutor() {
override fun covertEscData(): ByteArray? {
return TestPrintTemplate().getPrintData("122")
}
override fun convertTscData(): ByteArray? {
return LabelTemplete().getPrintData("Test")
}
}
task.mPrintCallBack = object : PrintCallBack {
override fun printSuccess(deviceManager: DeviceManager) {
Log.d("TAG", "printSuccess: deviceManager = $deviceManager")
}
override fun printFailed(msg: String?) {
Log.d("TAG", "printFailed: msg= $msg")
}
}
//task.execute()
task.goToPrint(DeviceManagerUtils.getInstance().lists[0], value1)
value1.forEach {
Log.d("TAG", "test123: 111 value= ${it}")
}
"hello world".toByteArray().forEach {
Log.d("TAG", "print: it= $it")
}
}
}
} }
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<Button
android:id="@+id/btn_log"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="初始化Log"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="@+id/btn_write"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="写日志"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toBottomOf="@id/btn_log" />
</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file
...@@ -41,6 +41,16 @@ ...@@ -41,6 +41,16 @@
android:text="drawer" android:text="drawer"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="50dp" /> android:layout_height="50dp" />
<Button
android:id="@+id/btn_printer"
android:text="打印"
android:layout_width="match_parent"
android:layout_height="50dp" />
<Button
android:id="@+id/btn_log"
android:text="日志"
android:layout_width="match_parent"
android:layout_height="50dp" />
<TextView <TextView
android:layout_width="match_parent" android:layout_width="match_parent"
......
...@@ -4,8 +4,7 @@ ...@@ -4,8 +4,7 @@
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:orientation="vertical" android:orientation="vertical">
tools:context="zs.qimai.com.test.MainActivity">
<TextView <TextView
android:id="@+id/tv_connect_bt" android:id="@+id/tv_connect_bt"
...@@ -80,4 +79,20 @@ ...@@ -80,4 +79,20 @@
android:textColor="#000" android:textColor="#000"
/> />
<ImageView
android:layout_gravity="center"
android:id="@+id/iv_image"
android:layout_width="100dp"
android:layout_height="100dp"/>
<TextView
android:id="@+id/tv_Log_up"
android:layout_width="match_parent"
android:layout_height="100dp"
android:layout_marginTop="10dp"
android:gravity="center"
android:text="日志操作"
android:textColor="#000"
android:textSize="22sp" />
</LinearLayout> </LinearLayout>
\ No newline at end of file
...@@ -21,7 +21,7 @@ buildscript { ...@@ -21,7 +21,7 @@ buildscript {
} }
dependencies { dependencies {
classpath 'com.android.tools.build:gradle:4.0.2' classpath 'com.android.tools.build:gradle:4.0.2'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.5.31"
// NOTE: Do not place your application dependencies here; they belong // NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files // in the individual module build.gradle files
classpath "com.whl:gradle-publish-plugin:0.1.16-SNAPSHOT" classpath "com.whl:gradle-publish-plugin:0.1.16-SNAPSHOT"
......
...@@ -63,7 +63,7 @@ repositories { ...@@ -63,7 +63,7 @@ repositories {
mavenCentral() mavenCentral()
} }
group 'com.qmai.android.fetch' group 'com.qmai.android.fetch'
version '1.2.57-SNAPSHOT' version '1.2.58-SNAPSHOT'
gradlePublish { gradlePublish {
......
...@@ -137,7 +137,8 @@ class OkHttpConfig private constructor(builder: Builder) { ...@@ -137,7 +137,8 @@ class OkHttpConfig private constructor(builder: Builder) {
setHostnameVerifier() setHostnameVerifier()
addInterceptors() addInterceptors()
setTimeout() setTimeout()
setDebugConfig() //http log显示信息由外部调用,内部不提供
// setDebugConfig()
return OkHttpConfig(this) return OkHttpConfig(this)
} }
......
File mode changed from 100755 to 100644
@if "%DEBUG%" == "" @echo off @if "%DEBUG%" == "" @echo off
@rem ########################################################################## @rem ##########################################################################
@rem @rem
@rem Gradle startup script for Windows @rem Gradle startup script for Windows
@rem @rem
@rem ########################################################################## @rem ##########################################################################
@rem Set local scope for the variables with windows NT shell @rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0 set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=. if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0 set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME% set APP_HOME=%DIRNAME%
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS= set DEFAULT_JVM_OPTS=
@rem Find java.exe @rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1 %JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto init if "%ERRORLEVEL%" == "0" goto init
echo. echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo. echo.
echo Please set the JAVA_HOME variable in your environment to match the echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation. echo location of your Java installation.
goto fail goto fail
:findJavaFromJavaHome :findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=% set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto init if exist "%JAVA_EXE%" goto init
echo. echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo. echo.
echo Please set the JAVA_HOME variable in your environment to match the echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation. echo location of your Java installation.
goto fail goto fail
:init :init
@rem Get command-line arguments, handling Windows variants @rem Get command-line arguments, handling Windows variants
if not "%OS%" == "Windows_NT" goto win9xME_args if not "%OS%" == "Windows_NT" goto win9xME_args
:win9xME_args :win9xME_args
@rem Slurp the command line arguments. @rem Slurp the command line arguments.
set CMD_LINE_ARGS= set CMD_LINE_ARGS=
set _SKIP=2 set _SKIP=2
:win9xME_args_slurp :win9xME_args_slurp
if "x%~1" == "x" goto execute if "x%~1" == "x" goto execute
set CMD_LINE_ARGS=%* set CMD_LINE_ARGS=%*
:execute :execute
@rem Setup the command line @rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle @rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
:end :end
@rem End local scope for the variables with windows NT shell @rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd if "%ERRORLEVEL%"=="0" goto mainEnd
:fail :fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code! rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1 exit /b 1
:mainEnd :mainEnd
if "%OS%"=="Windows_NT" endlocal if "%OS%"=="Windows_NT" endlocal
:omega :omega
apply plugin: 'com.android.library' apply plugin: 'com.android.library'
apply plugin: 'kotlin-android' apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
apply plugin: "com.whl.gradle-publish-plugin" apply plugin: "com.whl.gradle-publish-plugin"
apply plugin: 'kotlin-kapt' apply plugin: 'kotlin-kapt'
android { android {
compileSdkVersion 29 compileSdkVersion 30
buildToolsVersion "30.0.2" buildToolsVersion "30.0.2"
defaultConfig { defaultConfig {
minSdkVersion 16 minSdkVersion 21
targetSdkVersion 29 targetSdkVersion 30
versionCode 1 versionCode 1
versionName "1.0" versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
} }
...@@ -36,41 +34,33 @@ android { ...@@ -36,41 +34,33 @@ android {
jniLibs.srcDirs = ['libs'] jniLibs.srcDirs = ['libs']
} }
} }
buildFeatures {
viewBinding true
}
} }
dependencies { dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
implementation 'androidx.core:core-ktx:1.3.1' implementation 'androidx.core:core-ktx:1.7.0'
implementation 'androidx.appcompat:appcompat:1.2.0' implementation 'androidx.appcompat:appcompat:1.3.1'
implementation 'com.google.android.material:material:1.2.1' implementation 'com.google.android.material:material:1.4.0'
implementation 'androidx.constraintlayout:constraintlayout:2.0.1' implementation 'androidx.constraintlayout:constraintlayout:2.1.1'
implementation files('libs/commons-net-3.7.jar') implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.1"
implementation files('libs/commons-net-3.7-sources.jar') implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.3.1"
implementation files('libs/commons-net-examples-3.7.jar') implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.3.1"
testImplementation 'junit:junit:4.+' //日志读写
implementation 'com.dianping.android.sdk:logan:1.2.4'
//系统级别浮框
implementation "com.github.princekin-f:EasyFloat:2.0.3"
implementation project(path: ':widgetlib')
//test
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.2' androidTestImplementation 'androidx.test.ext:junit:1.1.2'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0' androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
api 'com.tencent.mars:mars-xlog:1.2.4'
// For Kotlin use kapt instead of annotationProcessor (注意这个注释)
kapt "androidx.room:room-compiler:$lifecycle_version"
implementation "androidx.room:room-runtime:$lifecycle_version"
kapt "androidx.room:room-compiler:$lifecycle_version"
implementation "androidx.room:room-ktx:$lifecycle_version"
api "androidx.lifecycle:lifecycle-extensions:2.2.0"
api "androidx.lifecycle:lifecycle-runtime:2.2.0"
annotationProcessor "androidx.lifecycle:lifecycle-compiler:2.2.0"
kapt "androidx.lifecycle:lifecycle-compiler:2.2.0"
api "androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0"
api "androidx.lifecycle:lifecycle-livedata-ktx:2.2.0"
} }
group 'com.qmai.android.log' group 'com.qmai.android.log'
version '1.0.1.21-SNAPSHOT' version '1.2-SNAPSHOT'
gradlePublish { gradlePublish {
......
File mode changed from 100755 to 100644
File mode changed from 100755 to 100644
File mode changed from 100755 to 100644
File mode changed from 100755 to 100644
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="com.qimai.log"> package="com.qimai.log">
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.INTERNET"/>
<application <uses-permission
> android:name="android.permission.WRITE_EXTERNAL_STORAGE"
tools:ignore="ScopedStorage" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
<uses-permission android:name="android.permission.INTERNET" />
<application>
<provider <activity
android:name="com.qimai.log.InitContentProvider" android:name=".ui.LogUpActivity"
android:authorities="${applicationId}.log.InitContentProvider1" android:exported="false" />
android:enabled="true"
android:exported="false"/>
<service android:name="com.qimai.log.UploadService"/>
<service android:name=".service.ScheduledExecutionService"/>
</application> </application>
</manifest> </manifest>
\ No newline at end of file
package com.qimai.log;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.os.Environment;
import android.text.TextUtils;
import android.util.Log;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.lang.reflect.Method;
import java.nio.channels.FileChannel;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class DBCopyUtils {
private static final String TAG = "DBCopyUtils";
/* public static void copyLogToFTP(String tag) {
File file = null;
final String SDCARD = Environment.getExternalStorageDirectory().getAbsolutePath();
final String logPath = LogInit.Companion.getInstance().mApplication.getFilesDir().toString() + "/release/xlog";
file = new File(logPath);
try {
// File logFile =
if (!file.exists()) {
return;
}
File[] fileList = file.listFiles();
if (fileList != null && fileList.length > 0) {
for (int i = 0; i < fileList.length; i++) {
file = fileList[i];
Log.d(TAG, "copylogToFTP: file = " + file.getAbsolutePath().toString());
final FileInputStream in = new FileInputStream(file);
final File finalFile = file;
new Thread(new Runnable() {
@Override
public void run() {
boolean isUpload = FtpUtil.uploadFile("119.29.25.121", 221, "dataftp"
, "Data2019@#%", "/", tag, finalFile.getName(), in);
Log.d(TAG, "copyDataBaseToSD: isUpload= " + isUpload + "DeviceUtils.getDeviceSN()= " + tag);
if (isUpload) {
finalFile.delete();
}
}
}).start();
}
} else {
return;
}
} catch (Exception e) {
Log.d(TAG, "copyDataBaseToSD: failed= " + e.getMessage());
//LogUtils.e(TAG, "copy dataBase to SD error.");
e.printStackTrace();
} finally {
}
}*/
/**
* @param file 代表文件不是目录
**/
public static void copyLogToFTP(File file,String tag) {
FileInputStream in = null;
try {
// File logFile =
if (!file.exists()) {
return;
}
in = new FileInputStream(file);
Log.d(TAG, "copyLogToFTP: thread= " + Thread.currentThread().getName());
boolean isUpload = FtpUtil.uploadFile("119.29.25.121", 221, "dataftp"
, "Data2019@#%",
"/", tag, file.getName(), in);
Log.d(TAG, "copyLogToFTP: isUpload= " + isUpload);
/*File []fileList = file.listFiles();
if (fileList!=null&&fileList.length>0){
for (int i = 0; i <fileList.length;i++) {
file = fileList[i];
Log.d(TAG, "copylogToFTP: file = "+file.getAbsolutePath().toString());
FileInputStream in = new FileInputStream(file);
File finalFile = file;
new Thread(new Runnable() {
@Override
public void run() {
boolean isUpload = FtpUtil.uploadFile("119.29.25.121",221,"dataftp"
,"Data2018@#%",
"/",DeviceUtils.getDeviceSN(), finalFile.getName(),in);
Log.d(TAG, "copyDataBaseToSD: isUpload= "+isUpload+"DeviceUtils.getDeviceSN()= "+(DeviceUtils.getDeviceSN()));
if (isUpload){
finalFile.delete();
}
}
}).start();
}
}
else{
return;
}
}
catch (Exception e) {
Log.d(TAG, "copyDataBaseToSD: failed= "+e.getMessage());
//LogUtils.e(TAG, "copy dataBase to SD error.");
e.printStackTrace();
}finally{
}
*/
} catch (FileNotFoundException e) {
e.printStackTrace();
} finally {
if (in != null) {
try {
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
package com.qimai.log
import android.util.Log
import com.qimai.log.QmLog.getConfig
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.io.File
import java.io.FileInputStream
import java.io.FileNotFoundException
import java.io.IOException
import java.util.*
object FtpUpload {
fun upload(config: UploadConfig, uploadCallBack: UploadCallBack? = null) {
GlobalScope.launch(Dispatchers.Main){
uploadCallBack?.onLoading()
val logFiles = getLogFiles(config)
if (logFiles.isNullOrEmpty()) {
uploadCallBack?.onError("暂无可上传日志文件")
} else {
try {
for (i in logFiles.indices) {
val tempFile = logFiles[i]
if (tempFile != null) {
uploadFile(config.tag,tempFile)
}
}
uploadCallBack?.onSuccess()
} catch (e: FileNotFoundException) {
uploadCallBack?.onError(e.toString())
}catch (e: IOException) {
uploadCallBack?.onError(e.toString())
}
}
}
}
suspend fun uploadFile(tag: String, file: File) = withContext(Dispatchers.IO){
var `in`: FileInputStream? = null
try {
// File logFile =
if (!file.exists()) {
return@withContext
}
`in` = FileInputStream(file)
val isUpload = FtpUtil.uploadFile(
"119.29.25.121", 221, "dataftp", "Data2019@#%",
"/", tag, file.getName(), `in`
)
} catch (e: FileNotFoundException) {
throw e
} finally {
if (`in` != null) {
try {
`in`.close()
} catch (e: IOException) {
throw e
}
}
}
}
}
private fun getLogFiles(config: UploadConfig): Array<File?>? {
//DBCopyUtils.copyLogToFTP();
//int requestNum = receiverBean.getNum();
var file: File? = null
// final String logPath = SDCARD + "/ptcy/log";
val logPath: String
logPath = if (BuildConfig.DEBUG) {
getConfig().logDebugPath
} else {
getConfig().logReleasePath
}
file = File(logPath)
if (!file.exists()) {
return null
}
//file.lastModified();
val files: Array<File?>? = convertTimeDesc(file.listFiles())
if (files == null || files.size == 0) {
return null
}
return files
// int maxFile = files.length;
// int endNum = Math.min(maxFile,num);
/*for (i in files.indices) {
val tempFile = files[i]
DBCopyUtils.copyLogToFTP(tempFile, config.tag)
}*/
}
private fun convertTimeDesc(files: Array<File>): Array<File?>? {
if (files == null || files.size == 0) {
return null
}
val endNum = files.size
val filesAll = arrayOfNulls<File>(20)
val longFileTreeMap = TreeMap<Long, File>()
for (i in files.indices) {
val temp = files[i].lastModified()
if (files[i].exists()) {
longFileTreeMap[temp] = files[i]
}
}
val navigableSet = longFileTreeMap.descendingKeySet()
val iterator: Iterator<Long> = navigableSet.iterator()
var index = 0
while (iterator.hasNext()) {
val file = longFileTreeMap[iterator.next()]
// Log.d(UploadService.TAG, "convertTimeDesc: file name= " + file!!.name)
filesAll[index] = file
index++
if (index >= endNum) {
break
}
}
return filesAll
}
fun copyLogToFTP(file: File, tag: String?) {
var `in`: FileInputStream? = null
try {
// File logFile =
if (!file.exists()) {
return
}
`in` = FileInputStream(file)
val isUpload = FtpUtil.uploadFile(
"119.29.25.121", 221, "dataftp", "Data2019@#%",
"/", tag, file.name, `in`
)
} catch (e: FileNotFoundException) {
e.printStackTrace()
} finally {
if (`in` != null) {
try {
`in`.close()
} catch (e: IOException) {
e.printStackTrace()
}
}
}
}
package com.qimai.log;
import android.util.Log;
import org.apache.commons.net.ftp.FTP;
import org.apache.commons.net.ftp.FTPClient;
import org.apache.commons.net.ftp.FTPReply;
import java.io.IOException;
import java.io.InputStream;
import java.net.SocketException;
public class FtpUtil {
private static final String TAG = "FtpUtil";
/**
* Description: 向FTP服务器上传文件
*
* @param host FTP服务器hostname
* @param port FTP服务器端口
* @param username FTP登录账号
* @param password FTP登录密码
* @param basePath FTP服务器基础目录
* @param filePath FTP服务器文件存放路径。例如分日期存放:/2017/08/06。文件的路径为basePath+filePath
* @param filename 上传到FTP服务器上的文件名
* @param input 输入流
* @return 成功返回true,否则返回false
*/
public static boolean uploadFile(String host, int port, String username, String password, String basePath,
String filePath, String filename, InputStream input)
{
Log.d(TAG, "uploadFile: thread= "+Thread.currentThread().getName());
boolean result = false;
FTPClient ftp = new FTPClient();
try
{
int reply;
ftp.connect(host, port);// 连接FTP服务器
// 如果采用默认端口,可以使用ftp.connect(host)的方式直接连接FTP服务器
ftp.login(username, password);// 登录
reply = ftp.getReplyCode();
if (!FTPReply.isPositiveCompletion(reply))
{
ftp.disconnect();
return result;
}
// 切换到上传目录
String onepath=new String((basePath + filePath).getBytes("GBK"),"iso-8859-1");
if (!ftp.changeWorkingDirectory(onepath))
{
ftp.makeDirectory(onepath);
ftp.changeWorkingDirectory(onepath);
/* Log.d(TAG, "uploadFile: 切换到上传目录");
// 如果目录不存在创建目录
String[] dirs = filePath.split("/");
String tempPath = basePath;*/
/*for (String dir : dirs)
{
if (null == dir || "".equals(dir))
continue;
tempPath += "/" + dir;
if (!ftp.changeWorkingDirectory(tempPath))
{
if (!ftp.makeDirectory(tempPath))
{
return result;
}
else
{
ftp.changeWorkingDirectory(tempPath);
}
}
}*/
}
// 设置上传文件的类型为二进制类型
ftp.setFileType(FTP.BINARY_FILE_TYPE);
// 上传文件
ftp.enterLocalPassiveMode();
if (!ftp.storeFile(new String(filename.getBytes("GBK"),"iso-8859-1"), input)) {
Log.d(TAG, "uploadFile: upload fail");
return result;
}
Log.d(TAG, "uploadFile: 上传成功");
input.close();
ftp.logout();
result = true;
}
catch (IOException e)
{
e.printStackTrace();
Log.d(TAG, "uploadFile: e= "+e.getMessage());
}
finally
{
if (ftp.isConnected())
{
try
{
ftp.disconnect();
}
catch (IOException ioe)
{
}
}
}
return result;
}
/**
* Description: 向FTP服务器上传文件
*
* @param host FTP服务器hostname
* @param port FTP服务器端口
* @param username FTP登录账号
* @param password FTP登录密码
* @param basePath FTP服务器基础目录
* @param filePath FTP服务器文件存放路径。例如分日期存放:/2015/01/01。文件的路径为basePath+filePath
* @param filename 上传到FTP服务器上的文件名
* @param input 输入流
* @return 成功返回true,否则返回false
*/
public static boolean uploadFile1(String host, int port, String username, String password, String basePath,
String filePath, String filename, InputStream input) {
boolean result = false;
FTPClient ftp = new FTPClient();
try {
int reply;
ftp.connect(host, port);// 连接FTP服务器
// 如果采用默认端口,可以使用ftp.connect(host)的方式直接连接FTP服务器
ftp.login(username, password);// 登录
ftp.setBufferSize(1024);
ftp.setControlEncoding("UTF-8");
ftp.enterLocalPassiveMode();
reply = ftp.getReplyCode();
if (!FTPReply.isPositiveCompletion(reply)) {
ftp.disconnect();
return result;
}
//切换到上传目录
if (!ftp.changeWorkingDirectory(basePath + filePath)) {
//如果目录不存在创建目录
//String[] dirs = filePath.split("/");
String tempPath = basePath;
/* for (String dir : dirs) {
if (null == dir || "".equals(dir)) continue;
tempPath += "/" + dir;
if (!ftp.changeWorkingDirectory(tempPath)) {
if (!ftp.makeDirectory(tempPath)) {
return result;
} else {
ftp.changeWorkingDirectory(tempPath);
}
}
}*/
ftp.changeWorkingDirectory(tempPath);
}
//创建目录
if (!ftp.changeWorkingDirectory(filePath)){
ftp.makeDirectory(filePath);
}
//设置上传文件的类型为二进制类型
ftp.setFileType(FTP.BINARY_FILE_TYPE);
//上传文件
if (!ftp.storeFile(filePath+"/"+filename, input)) {
return result;
}
input.close();
ftp.logout();
result = true;
} catch (IOException e) {
Log.d(TAG, "uploadFile: e= "+e.getMessage());
e.printStackTrace();
e.printStackTrace();
} finally {
if (ftp.isConnected()) {
try {
ftp.disconnect();
} catch (IOException ioe) {
}
}
}
return result;
}
}
package com.qimai.log
import android.content.ContentProvider
import android.content.ContentValues
import android.database.Cursor
import android.net.Uri
class InitContentProvider : ContentProvider() {
override fun delete(uri: Uri, selection: String?, selectionArgs: Array<String>?): Int {
throw UnsupportedOperationException("Not yet implemented")
}
override fun getType(uri: Uri): String? {
throw UnsupportedOperationException("Not yet implemented")
}
override fun insert(uri: Uri, values: ContentValues?): Uri? {
throw UnsupportedOperationException("Not yet implemented")
}
override fun onCreate(): Boolean {
context?.let { LogInit.getInstance().init(it) }
return true
}
override fun query(
uri: Uri, projection: Array<String>?, selection: String?,
selectionArgs: Array<String>?, sortOrder: String?
): Cursor? {
throw UnsupportedOperationException("Not yet implemented")
}
override fun update(
uri: Uri, values: ContentValues?, selection: String?,
selectionArgs: Array<String>?
): Int {
throw UnsupportedOperationException("Not yet implemented")
}
}
\ No newline at end of file
package com.qimai.log
import android.app.Activity
import android.app.Application
import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.os.Environment
import com.qimai.log.service.ScheduledExecutionService
import com.tencent.mars.xlog.Log
import com.tencent.mars.xlog.Xlog
class LogInit private constructor() {
private lateinit var mLogConfig: QmLogConfig
@JvmOverloads
fun init(application: Context) {
init(QmLogConfig(application))
}
@JvmOverloads
fun init(
config: QmLogConfig
) {
this.mLogConfig = config
//addObserver(config.app)
System.loadLibrary("c++_shared")
System.loadLibrary("marsxlog")
var logPath = ""
if (BuildConfig.DEBUG) {
logPath = config.logDebugPath
} else {
logPath = config.logReleasePath
}
val logConfig = Xlog.XLogConfig()
logConfig.mode = Xlog.AppednerModeAsync
logConfig.logdir = logPath
logConfig.nameprefix = "log"
logConfig.pubkey = ""
logConfig.compressmode = Xlog.ZLIB_MODE
logConfig.compresslevel = 0
logConfig.cachedays = 3
logConfig.cachedir = ""
// if (BuildConfig.DEBUG) {
logConfig.level = Xlog.LEVEL_DEBUG
Xlog.setConsoleLogOpen(true)
// } else {
// logConfig.level = Xlog.LEVEL_ALL
// Xlog.setConsoleLogOpen(true)
//}
Xlog.appenderOpen(logConfig)
Log.setLogImp(Xlog())
//检查是否有过期的通知数据
// val intent = Intent(getConfig().app, ScheduledExecutionService::class.java)
// getConfig().app.startService(intent)
}
private fun addObserver(mApplication: Context) {
if (mApplication is Application) {
mApplication.registerActivityLifecycleCallbacks(object :
Application.ActivityLifecycleCallbacks {
override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) {
}
override fun onActivityStarted(activity: Activity) {
}
override fun onActivityResumed(activity: Activity) {
}
override fun onActivityPaused(activity: Activity) {
}
override fun onActivityStopped(activity: Activity) {
}
override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle) {
}
override fun onActivityDestroyed(activity: Activity) {
Log.appenderClose()
Log.appenderFlush(false)
}
})
}
}
companion object {
private val mInstance: LogInit = LogInit()
fun getInstance(): LogInit {
return mInstance
}
}
fun getConfig() = mLogConfig
}
\ No newline at end of file
package com.qimai.log;/*
* Copyright (c) 2018-present, 美团点评
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.zip.GZIPInputStream;
import java.util.zip.InflaterInputStream;
import javax.crypto.Cipher;
import javax.crypto.CipherInputStream;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
public class LoganParser {
private static final String ALGORITHM = "AES";
private static final String ALGORITHM_TYPE = "AES/CBC/NoPadding";
private Cipher mDecryptCipher;
private byte[] mEncryptKey16; //128位ase加密Key
private byte[] mEncryptIv16; //128位aes加密IV
public LoganParser(byte[] encryptKey16, byte[] encryptIv16) {
mEncryptKey16 = encryptKey16;
mEncryptIv16 = encryptIv16;
initEncrypt();
}
private void initEncrypt() {
SecretKeySpec secretKeySpec = new SecretKeySpec(mEncryptKey16, ALGORITHM);
try {
mDecryptCipher = Cipher.getInstance(ALGORITHM_TYPE);
mDecryptCipher.init(Cipher.DECRYPT_MODE, secretKeySpec, new IvParameterSpec(mEncryptIv16));
} catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException | InvalidAlgorithmParameterException e) {
e.printStackTrace();
}
}
public void parse(InputStream is, OutputStream os) {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int t = 0;
try {
while ((t = is.read(buffer)) >= 0) {
bos.write(buffer, 0, t);
bos.flush();
}
byte[] content = bos.toByteArray();
for (int i = 0; i < content.length; i++) {
byte start = content[i];
if (start == '\1') {
i++;
int length = (content[i] & 0xFF) << 24 |
(content[i + 1] & 0xFF) << 16 |
(content[i + 2] & 0xFF) << 8 |
(content[i + 3] & 0xFF);
i += 3;
int type;
if (length > 0) {
int temp = i + length + 1;
if (content.length - i - 1 == length) { //异常
type = 0;
} else if (content.length - i - 1 > length && '\0' == content[temp]) {
type = 1;
} else if (content.length - i - 1 > length && '\1' == content[temp]) { //异常
type = 2;
} else {
i -= 4;
continue;
}
byte[] dest = new byte[length];
System.arraycopy(content, i + 1, dest, 0, length);
ByteArrayOutputStream uncompressBytesArray = new ByteArrayOutputStream();
InflaterInputStream inflaterOs = null;
byte[] uncompressByte;
try {
uncompressBytesArray.reset();
inflaterOs = new GZIPInputStream(new CipherInputStream(new ByteArrayInputStream(dest), mDecryptCipher));
int e = 0;
while ((e = inflaterOs.read(buffer)) >= 0) {
uncompressBytesArray.write(buffer, 0, e);
}
} catch (Exception e) {
e.printStackTrace();
}
uncompressByte = uncompressBytesArray.toByteArray();
uncompressBytesArray.reset();
os.write(uncompressByte);
if (inflaterOs != null)
inflaterOs.close();
i += length;
if (type == 1) {
i++;
}
}
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
package com.qimai.log
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
}
}
\ No newline at end of file
package com.qimai.log
import com.blankj.utilcode.util.*
import com.dianping.logan.Logan
import com.dianping.logan.LoganConfig
import com.qimai.log.bean.LogUpBean
import com.qimai.log.ui.LogUpFloatView
import kotlinx.coroutines.*
import java.io.ByteArrayOutputStream
import java.io.File
import java.io.FileInputStream
/**
* 上传日志相关操作
*/
object QLog {
//region ------ 日志类型 ------
//调试信息
const val D: Int = 2
//提示性的消息
const val I: Int = 3
//错误信息
const val E: Int = 4
//警告信息
const val W: Int = 5
//endregion
//ase加密Key
private const val encryptKey16 = "0123456789012345"
//aes加密IV
private const val encryptIv16 = "0123456789012345"
// 到天的日期格式
private const val formatDay = "yyyy-MM-dd"
//每天的毫秒数
private const val millisDay = 86_400_000
//零点字符串
private const val zero = "00:00:00"
//log 文件路径
private var filePath: String? = null
/**
* 初始化
*/
fun init(debug: Boolean = false) {
filePath =
"${Utils.getApp().getExternalFilesDir(null)?.absolutePath}${File.separator}Logan_v1"
val config: LoganConfig = LoganConfig.Builder()
.setCachePath(Utils.getApp().applicationContext.filesDir.absolutePath)
.setEncryptKey16(encryptKey16.toByteArray())
.setEncryptIV16(encryptIv16.toByteArray())
.setPath(filePath)
.build()
Logan.init(config)
Logan.setDebug(debug)
Logan.setOnLoganProtocolStatus { cmd, code ->
LogUtils.d("clogan > cmd : $cmd | code : $code")
}
GlobalScope.launch(Dispatchers.IO) {
delay(1_000)
withContext(Dispatchers.Main) {
LogUpFloatView.show()
}
}
}
/**
* 写入日志
* @param log String - 日志内容
* @param tag Int - 日志类型
* @param now Boolean - 是否立即写入
*/
fun write(log: String, tag: Int = D, now: Boolean = false) {
Logan.w(log, tag)
if (now) Logan.f()
}
/**
* 立即写入
*/
fun f() = Logan.f()
/**
* 获取日志文件保存路径
*/
fun getLogFilePath(): String? = filePath
/**
* 按日期获取日志文件,日期格式 yyyy-MM-dd
*/
fun getLogFile(dateStr: String): File? {
val timeStamp = TimeUtils.string2Millis("$dateStr $zero")
return FileUtils.listFilesInDir(filePath).find { it.name == "$timeStamp" }
}
/**
* 获取一段时间内所有日志文件
*
* @param startDate String 开始时间,格式为:yyyy-MM-dd
* @param endDate String 结束时间,格式为:yyyy-MM-dd
* @return MutableList<File> 文件列表
*/
fun getLogFileByPeriod(startDate: String, endDate: String): MutableList<File> {
val dateList = getPeriodDateList(startDate, endDate)
return FileUtils.listFilesInDirWithFilter(filePath) {
dateList.any { a -> "${TimeUtils.string2Millis("$a $zero")}" == it.name }
}
}
/**
*获取所有日志文件
*/
fun getAllLogFile(): MutableList<File> = FileUtils.listFilesInDir(filePath)
/**
* 按日期获取日志文件内容,日期格式 yyyy-MM-dd
*/
fun getLogFileContent(dateStr: String): String {
val file = getLogFile(dateStr)
return if (file != null) getLogFileContent(file) else ""
}
/**
* 获取一天内一段时间内的日志内容
*
* @param startDate String 开始时间,格式为 yyyy-MM-dd HH:mm:ss
* @param endDate String 结束时间,格式为 yyyy-MM-dd HH:mm:ss
* @return List<LogUpBean> 日志内容
*/
fun getLogListByPeriod(startDate: String, endDate: String): List<LogUpBean?> {
//开始时间戳
val startMillis: Long = TimeUtils.string2Millis(startDate)
//结束时间戳
val endMillis: Long = TimeUtils.string2Millis(endDate)
//当天时间字符串,用于查找文件,格式:yyyy-MM-dd
val dateStr = TimeUtils.millis2String(startMillis, formatDay)
return getLogFileContent(dateStr)
.trim()
.split("\n")
.map { GsonUtils.fromJson(it, LogUpBean::class.java) ?: null }
.filter { it?.l in startMillis..endMillis }
.sortedByDescending { it?.l }
}
/**
* 获取一天内一段时间内的日志内容
*
* @param startDate String 开始时间,格式为 yyyy-MM-dd HH:mm:ss
* @param endDate String 结束时间,格式为 yyyy-MM-dd HH:mm:ss
* @return String 日志内容
*/
fun getLogContentByPeriod(startDate: String, endDate: String): String {
val list = getLogListByPeriod(startDate, endDate)
return GsonUtils.toJson(list)
}
/**
* 获取所有文件内容
*/
fun getAllLogFileContent(): String {
return getAllLogFile()
.fold("") { total, next ->
val nextContent = getLogFileContent(next)
return if (total.isEmpty()) nextContent else "${total}\n${nextContent}"
}
}
/**
* 解析日志文件内容
*/
fun getLogFileContent(file: File): String {
val uncompressBytesArray = ByteArrayOutputStream()
val loganParser = LoganParser(encryptKey16.toByteArray(), encryptIv16.toByteArray())
loganParser.parse(FileInputStream(file), uncompressBytesArray)
return String(uncompressBytesArray.toByteArray())
}
/**
* 获取一段时间内时间列表
*
* @param startDate String 开始时间,格式为 yyyy-MM-dd
* @param endDate String 结束时间,格式为 yyyy-MM-dd
* @return MutableList<String> 时间列表 [yyyy-MM-dd,yyyy-MM-dd]
*/
private fun getPeriodDateList(startDate: String, endDate: String): MutableList<String> {
val start: Long = TimeUtils.string2Millis("$startDate $zero")
var end: Long = TimeUtils.string2Millis("$endDate $zero")
val dateList = mutableListOf<String>()
while (end >= start) {
dateList.add(TimeUtils.millis2String(end, formatDay))
end -= millisDay
}
return dateList
}
/**
* 按名称删除文件
*/
fun deleteLogByName(name: String) {
FileUtils.delete("$filePath/$name")
}
/**
* 按日期删除日志
* @param dateStr String
*/
fun deleteLogByDate(dateStr: String) {
getLogFile(dateStr)?.run { FileUtils.delete(this) }
}
/**
* 获取所有日志文件信息
*/
fun getAllFileInfo(): Map<String, Long> {
return Logan.getAllFilesInfo()
}
/**
* 清空所有日志文件
*/
fun clear() {
FileUtils.deleteAllInDir(filePath)
}
}
\ No newline at end of file
package com.qimai.log
import android.content.Intent
import com.tencent.mars.xlog.Log
object QmLog {
fun d(tag: String, message: String) {
Log.d(tag, message)
}
fun e(tag: String, message: String) {
Log.e(tag, message)
}
fun v(tag: String, message: String) {
Log.v(tag, message)
}
fun w(tag: String, message: String) {
Log.w(tag, message)
}
fun i(tag: String, message: String) {
Log.i(tag, message)
}
fun flush() {
Log.appenderClose()
}
/**
* 上传log
*/
fun uploadLog(config: UploadConfig) {
//刷新下
Log.appenderFlush(false)
val intent = Intent(getConfig().app, UploadService::class.java)
intent.putExtra("upload", config)
getConfig().app.startService(intent)
}
fun uploadLog(config: UploadConfig,callBack: UploadCallBack) {
//刷新下
Log.appenderFlush(false)
FtpUpload.upload(config,callBack)
}
fun getConfig(): QmLogConfig {
val config: QmLogConfig? = LogInit.getInstance().getConfig()
if (config == null) {
throw IllegalArgumentException("please call LogInit.init() first")
} else {
return config
}
}
//上传log
/***
* filePath 保存logo的路径
*/
fun uploadXog(config: (filePath: String) -> Unit) {
//刷新下
Log.appenderFlush(false)
// final String logPath = SDCARD + "/ptcy/log";
val logPath: String = if (BuildConfig.DEBUG) {
getConfig().logDebugPath
} else {
getConfig().logReleasePath
}
config(logPath)
// val intent = Intent(getConfig().app, UploadService::class.java)
// getConfig().app.startService(intent)
}
}
\ No newline at end of file
package com.qimai.log
import android.content.Context
/**
* 基本参数配置
*/
class QmLogConfig(val app: Context, var logDebugPath: String = app.filesDir.toString() + "/debug/xlog",
var logReleasePath: String = app.filesDir.toString() + "/release/xlog",
var expiredDays: Int = 10) {
}
\ No newline at end of file
package com.qimai.log
interface UploadCallBack {
fun onLoading()
fun onError(e:String)
fun onSuccess()
}
\ No newline at end of file
package com.qimai.log
import android.os.Parcelable
/**
* @tag 文件名字
*/
class UploadConfig(var tag: String): java.io.Serializable {
}
\ No newline at end of file
package com.qimai.log;
import android.app.IntentService;
import android.content.Intent;
import android.os.Environment;
import android.util.Log;
import java.io.File;
import java.util.Iterator;
import java.util.NavigableSet;
import java.util.TreeMap;
import androidx.annotation.Nullable;
/**
* Created by wanglei on 18-7-16.
* <p>
* 根据通知去上传日志
*/
public class UploadService extends IntentService {
public UploadService() {
super("upload");
}
private static final String TAG = "UploadService";
/**
* Creates an IntentService. Invoked by your subclass's constructor.
*
* @param name Used to name the worker thread, important only for debugging.
*/
public UploadService(String name) {
super(name);
}
@Override
protected void onHandleIntent(@Nullable Intent intent) {
Log.d(TAG, "onHandleIntent: ");
// updateOldOrder();
try {
UploadConfig receiverBean = (UploadConfig) intent.getSerializableExtra("upload");
if (receiverBean != null) {
uploadLog(receiverBean);
}
} catch (Exception e) {
}
}
private void uploadLog(UploadConfig config) {
//DBCopyUtils.copyLogToFTP();
//int requestNum = receiverBean.getNum();
File file = null;
// final String logPath = SDCARD + "/ptcy/log";
final String logPath;
if (BuildConfig.DEBUG) {
logPath = QmLog.INSTANCE.getConfig().getLogDebugPath();
} else {
logPath = QmLog.INSTANCE.getConfig().getLogReleasePath();
}
file = new File(logPath);
if (!file.exists()) {
return;
}
//file.lastModified();
File[] files = convertTimeDesc(file.listFiles());
if (files == null || files.length == 0) {
return;
}
// int maxFile = files.length;
// int endNum = Math.min(maxFile,num);
for (int i = 0; i < files.length; i++) {
File tempFile = files[i];
DBCopyUtils.copyLogToFTP(tempFile,config.getTag());
}
}
private File[] convertTimeDesc(File[] files) {
if (files == null || files.length == 0) {
return null;
}
int endNum = files.length;
File[] filesAll = new File[20];
TreeMap<Long, File> longFileTreeMap = new TreeMap<>();
for (int i = 0; i < files.length; i++) {
Long temp = files[i].lastModified();
if (files[i].exists()) {
longFileTreeMap.put(temp, files[i]);
}
}
NavigableSet<Long> navigableSet = longFileTreeMap.descendingKeySet();
Iterator<Long> iterator = navigableSet.iterator();
int index = 0;
while (iterator.hasNext()) {
File file = longFileTreeMap.get(iterator.next());
Log.d(TAG, "convertTimeDesc: file name= " + file.getName());
filesAll[index] = file;
index++;
if (index >= endNum) {
break;
}
}
return filesAll;
}
}
package com.qimai.log
import android.view.View
/**
* Created by chuxiaoshan on 2021/11/15 20:25
*
* Description:
*/
/**
* 扩展属性参数:用于标记是否可点击
*/
private var <T : View> T.viewClickable: Boolean
get() = if (getTag(1 + 2 shl 24) != null) getTag(1 + 2 shl 24) as Boolean else true
set(value) {
setTag(1 + 2 shl 24, value)
}
/**
* 设置点击监听, 并自定义防抖时间
*
* @receiver T View
* @param time Long 防抖间隔时长 默认300
* @param action Function1<[@kotlin.ParameterName] View, Unit> 点击事件
*/
fun <T : View> T.click(time: Long = 300, action: (T: View) -> Unit) {
setOnClickListener {
if (viewClickable) {
viewClickable = false
action(it)
postDelayed({ viewClickable = true }, time)
}
}
}
\ No newline at end of file
package com.qimai.log.bean
/**
* Created by chuxiaoshan on 2021/11/11 14:46
*
* Description:
*/
data class LogUpBean(
//{
// "c": "hello world!!!!",
// "f": 312,
// "l": 1636363201246,
// "n": "main",
// "i": 2,
// "m": true
//}
//日志内容
var c: String? = null,
//日志类型
var f: Int,
//日志记录时间
var l: Long,
//:线程名称
var n: String? = null,
// 线程id
var i: Int,
//是否是主线程
var m: Boolean
)
\ No newline at end of file
package com.qimai.log.database
import android.app.Application
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
/**
* 获取数据库操作类
*/
class PushDataBaseVm(application: Application) : AndroidViewModel(application) {
private val mDataBse: PushNotifiInfoDao by lazy {
PushNotifiDataBase.getDatabase(application).pushInfoDao()
}
fun insertPushInfo(notifiInfo: PushNotifiInfo) {
viewModelScope.launch(Dispatchers.IO) {
mDataBse.insert(notifiInfo)
}
}
fun updatePushInfo(msg_id: String, status: Boolean, errorMsg: String? = null) {
viewModelScope.launch(Dispatchers.IO) {
mDataBse.updateStatus(msg_id, status, errorMsg)
}
}
}
\ No newline at end of file
package com.qimai.log.database
import android.content.Context
import androidx.room.Database
import androidx.room.Room
import androidx.room.RoomDatabase
/**
*
*/
@Database(entities = [PushNotifiInfo::class], version = 1, exportSchema = false)
abstract class PushNotifiDataBase : RoomDatabase() {
abstract fun pushInfoDao(): PushNotifiInfoDao
companion object {
// Singleton prevents multiple instances of database opening at the
// same time.
@Volatile
private var INSTANCE: PushNotifiDataBase? = null
fun getDatabase(context: Context): PushNotifiDataBase {
val tempInstance = INSTANCE
if (tempInstance != null) {
return tempInstance
}
synchronized(this) {
val instance = Room.databaseBuilder(
context.applicationContext,
PushNotifiDataBase::class.java,
"push_database"
).build()
INSTANCE = instance
return instance
}
}
}
}
\ No newline at end of file
package com.qimai.log.database
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.PrimaryKey
@Entity(tableName = "push_info")
data class PushNotifiInfo @JvmOverloads
constructor(
@PrimaryKey @ColumnInfo(name = "msg_id") val msg_id: String, //消息id
@ColumnInfo(name = "status") var status: Boolean, // 状态
@ColumnInfo(name = "error_msg") var errorMsg: String? = null, //错误原因
@ColumnInfo(name = "push_type") val pushType: String, //用的什么推送
@ColumnInfo(name = "rawText") var rawText: String? = null, //原始信息
@ColumnInfo(name = "time") var time: String? = null, //格式化时间
@ColumnInfo(name = "device_id") var deviceId: String? = null, //推送的devicedId
@ColumnInfo(name = "model") var model: String? = null, //手机型号
@ColumnInfo(name = "app_version") var version: String? = null, //app版本
@ColumnInfo(name = "time_stamp") var timeStamp: Long? = null //时间戳
) {
}
\ No newline at end of file
package com.qimai.log.database
import androidx.room.*
import java.sql.Timestamp
/**
* 数据库操作类
*/
@Dao
interface PushNotifiInfoDao {
@Insert(onConflict = OnConflictStrategy.IGNORE)
suspend fun insert(word: PushNotifiInfo)
@Query("update push_info set status= :status ,error_msg = :errorMsg where msg_id=:msg_id")
suspend fun updateStatus(msg_id: String, status: Boolean, errorMsg: String? = null)
//删除过期数据
@Query("delete from push_info where time_stamp < :timestamp")
fun deleteExpiredItems(timestamp: Long)
}
\ No newline at end of file
package com.qimai.log.service
import android.app.IntentService
import android.content.Intent
import com.qimai.log.QmLog
import com.qimai.log.database.PushNotifiDataBase
import com.qimai.log.database.PushNotifiInfoDao
import com.qimai.log.QmLogConfig
/**
* 定时执行任务
*/
class ScheduledExecutionService(name: String = "schedule") : IntentService(name) {
private val mDataBse: PushNotifiInfoDao by lazy {
PushNotifiDataBase.getDatabase(application).pushInfoDao()
}
override fun onHandleIntent(intent: Intent?) {
val totalTime: Long = (QmLog.getConfig().expiredDays * 24 * 60 * 60 * 1000).toLong()
mDataBse.deleteExpiredItems((System.currentTimeMillis() - totalTime))
}
}
\ No newline at end of file
package com.qimai.log.ui
import android.annotation.SuppressLint
import android.graphics.Color
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.LinearLayoutManager
import com.blankj.utilcode.util.*
import com.qimai.log.QLog
import com.qimai.log.databinding.ActivityLogUpBinding
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
class LogUpActivity : AppCompatActivity() {
private lateinit var bind: ActivityLogUpBinding
private val mAdapter by lazy { LogUpAdapter() }
override fun onCreate(savedInstanceState: Bundle?) {
BarUtils.setStatusBarColor(this, Color.WHITE)
BarUtils.setStatusBarLightMode(this, true)
super.onCreate(savedInstanceState)
bind = ActivityLogUpBinding.inflate(layoutInflater)
setContentView(bind.root)
init()
}
@SuppressLint("SetTextI18n")
private fun init() {
val startDate = TimeUtils.getNowString(TimeUtils.getSafeDateFormat("yyyy-MM-dd 00:00:00"))
val endDate = TimeUtils.getNowString(TimeUtils.getSafeDateFormat("yyyy-MM-dd HH:mm:ss"))
bind.etDate.setText("$startDate -- $endDate")
bind.rv.layoutManager = LinearLayoutManager(this)
bind.rv.adapter = mAdapter
bind.btnSearch.setOnClickListener { getLogData() }
getLogData()
}
private fun getLogData() {
lifecycleScope.launch(Dispatchers.IO) {
val timeStr = bind.etDate.text.toString()
val timeSplit = timeStr.split(" -- ")
val startTime = timeSplit[0]
val endTime = timeSplit[1]
QLog.f()
val list = QLog.getLogListByPeriod(startTime, endTime)
withContext(Dispatchers.Main) {
if (list.isNullOrEmpty()) {
ToastUtils.showShort("未查询到日志")
return@withContext
}
KeyboardUtils.hideSoftInput(bind.btnSearch)
bind.etDate.clearFocus()
mAdapter.setNewData(list)
}
}
}
}
\ No newline at end of file
package com.qimai.log.ui
import android.annotation.SuppressLint
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import com.blankj.utilcode.util.ClipboardUtils
import com.blankj.utilcode.util.TimeUtils
import com.blankj.utilcode.util.ToastUtils
import com.qimai.log.R
import com.qimai.log.bean.LogUpBean
/**
* Created by chuxiaoshan on 2021/11/15 14:42
*
* Description: 日志渲染adapter
*/
class LogUpAdapter(
private var dataSet: List<LogUpBean?> = listOf()
) : RecyclerView.Adapter<LogUpAdapter.ViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val view = LayoutInflater.from(parent.context).inflate(R.layout.item_log, parent, false)
return ViewHolder(view)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val item = dataSet[position]
val content = "时间:${TimeUtils.millis2String(item?.l ?: 0, "yyyy-MM-dd HH:mm:ss")}\n" +
"类型:${item?.f}\n" +
"线程:${item?.i} ${item?.n}\n" +
"内容:\n${item?.c}\n"
holder.tvContent.text = content
holder.btnCopy.setOnClickListener {
ClipboardUtils.copyText(content)
ToastUtils.showShort("内容已复制到粘贴板")
}
}
override fun getItemCount() = dataSet.size
@SuppressLint("NotifyDataSetChanged")
fun setNewData(data: List<LogUpBean?>) {
if (dataSet === data) return
dataSet = data
notifyDataSetChanged()
}
class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
val tvContent: TextView = view.findViewById(R.id.tv_content)
val btnCopy: TextView = view.findViewById(R.id.btn_copy)
}
}
\ No newline at end of file
package com.qimai.log.ui
import android.content.Intent
import android.content.Intent.FLAG_ACTIVITY_NEW_TASK
import android.os.Build
import androidx.constraintlayout.widget.ConstraintLayout
import com.blankj.utilcode.util.*
import com.lzf.easyfloat.EasyFloat
import com.lzf.easyfloat.enums.ShowPattern
import com.lzf.easyfloat.enums.SidePattern
import com.qimai.log.R
import com.qimai.log.click
/**
* Created by chuxiaoshan on 2021/5/27 14:10
*
* Description: 浮框
*/
object LogUpFloatView {
//标识浮框的tag
private const val FLOAT_VIEW_TAG = "tag_float_view"
/**
* 浮框权限判断与申请
*/
private fun requestPermission() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (!PermissionUtils.isGrantedDrawOverlays()) {
PermissionUtils.requestDrawOverlays(object :
PermissionUtils.SimpleCallback {
override fun onGranted() {
LogUtils.d("---1111--->onGranted")
initFloat()
}
override fun onDenied() {
LogUtils.d("---1111--->onDenied")
ToastUtils.showShort("浮框权限未打开")
}
})
} else {
initFloat()
LogUtils.d("---1111--->浮框权限本来就是打开的")
}
} else {
LogUtils.d("---1111--->低版本")
initFloat()
}
}
private fun initFloat() {
try {
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.LOLLIPOP_MR1) {
EasyFloat.dismiss(FLOAT_VIEW_TAG)
}
EasyFloat.with(Utils.getApp())
.setTag(FLOAT_VIEW_TAG)
.setShowPattern(ShowPattern.FOREGROUND)
.setLocation(
ScreenUtils.getScreenWidth() - ConvertUtils.dp2px(58f),
ScreenUtils.getScreenHeight() - ConvertUtils.dp2px(200f)
)
.setSidePattern(SidePattern.RESULT_HORIZONTAL)
.setImmersionStatusBar(true)
.setFilter(LogUpActivity::class.java)
.setLayout(R.layout.float_logup) {
it.findViewById<ConstraintLayout>(R.id.cl_float_view).click {
Utils.getApp().startActivity(Intent(
Utils.getApp(),
LogUpActivity::class.java
).apply {
flags = FLAG_ACTIVITY_NEW_TASK
})
}
}.show()
} catch (e: Exception) {
LogUtils.e("---e--->${e.printStackTrace()}")
}
}
/**
* 初始化并显示全局浮框
*/
fun show() {
requestPermission()
}
}
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="#ffee58" />
<corners android:radius="100dp" />
</shape>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true">
<EditText
android:id="@+id/et_date"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginVertical="16dp"
android:layout_marginLeft="16dp"
android:text="2021-11-11 15:21:56 -- 2021-11-11 15:34:56"
android:textSize="14sp"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toLeftOf="@+id/btn_search"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="@+id/btn_search"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginRight="16dp"
android:text="查询"
app:layout_constraintBottom_toBottomOf="@+id/et_date"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="@+id/et_date" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rv"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginTop="16dp"
android:clipToPadding="false"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@+id/et_date"
tools:listitem="@layout/item_log" />
</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/cl_float_view"
android:layout_width="50dp"
android:layout_height="50dp"
android:layout_margin="4dp"
android:background="@drawable/circle_log"
android:clickable="true"
android:clipToPadding="false"
android:elevation="2dp"
android:focusable="true">
<TextView
android:id="@+id/tv_clear_back"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="LogUp"
android:textColor="#666666"
android:textSize="14sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:background="@color/white"
android:elevation="2dp">
<TextView
android:id="@+id/tv_content"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:padding="16dp"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:text="时间:2021-11-15 12:34:56\n类型:错误\n线程:123 Thread-5\n内容:\ngheioirnerinewrpewrgheioirnerinewrpewrgheioirnerinewrpewrioirnerinewrpewrioirnerinewrpewrioirnerinewrpewrioirnerinewrpewrioirnerinewrpewrioirnerinewrpewrioirnerinewrpewrioirnerinewrpewrioirnerinewrpewrioirnerinewrpewrioirnerinewrpewr" />
<TextView
android:id="@+id/btn_copy"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:clickable="true"
android:focusable="true"
android:paddingHorizontal="16dp"
android:paddingVertical="8dp"
android:text="复制"
android:textColor="#FF9919"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file
@if "%DEBUG%" == "" @echo off @if "%DEBUG%" == "" @echo off
@rem ########################################################################## @rem ##########################################################################
@rem @rem
@rem Gradle startup script for Windows @rem Gradle startup script for Windows
@rem @rem
@rem ########################################################################## @rem ##########################################################################
@rem Set local scope for the variables with windows NT shell @rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0 set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=. if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0 set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME% set APP_HOME=%DIRNAME%
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS= set DEFAULT_JVM_OPTS=
@rem Find java.exe @rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1 %JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto init if "%ERRORLEVEL%" == "0" goto init
echo. echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo. echo.
echo Please set the JAVA_HOME variable in your environment to match the echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation. echo location of your Java installation.
goto fail goto fail
:findJavaFromJavaHome :findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=% set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto init if exist "%JAVA_EXE%" goto init
echo. echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo. echo.
echo Please set the JAVA_HOME variable in your environment to match the echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation. echo location of your Java installation.
goto fail goto fail
:init :init
@rem Get command-line arguments, handling Windows variants @rem Get command-line arguments, handling Windows variants
if not "%OS%" == "Windows_NT" goto win9xME_args if not "%OS%" == "Windows_NT" goto win9xME_args
:win9xME_args :win9xME_args
@rem Slurp the command line arguments. @rem Slurp the command line arguments.
set CMD_LINE_ARGS= set CMD_LINE_ARGS=
set _SKIP=2 set _SKIP=2
:win9xME_args_slurp :win9xME_args_slurp
if "x%~1" == "x" goto execute if "x%~1" == "x" goto execute
set CMD_LINE_ARGS=%* set CMD_LINE_ARGS=%*
:execute :execute
@rem Setup the command line @rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle @rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
:end :end
@rem End local scope for the variables with windows NT shell @rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd if "%ERRORLEVEL%"=="0" goto mainEnd
:fail :fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code! rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1 exit /b 1
:mainEnd :mainEnd
if "%OS%"=="Windows_NT" endlocal if "%OS%"=="Windows_NT" endlocal
:omega :omega
package com.bigkoo.pickerview.adapter; package com.bigkoo.pickerview.adapter;
import com.contrarywind.adapter.WheelAdapter; import com.contrarywind.adapter.WheelAdapter;
import java.util.List; import java.util.List;
/** /**
* The simple Array wheel adapter * The simple Array wheel adapter
* @param <T> the element type * @param <T> the element type
*/ */
public class ArrayWheelAdapter<T> implements WheelAdapter { public class ArrayWheelAdapter<T> implements WheelAdapter {
// items // items
private List<T> items; private List<T> items;
/** /**
* Constructor * Constructor
* @param items the items * @param items the items
*/ */
public ArrayWheelAdapter(List<T> items) { public ArrayWheelAdapter(List<T> items) {
this.items = items; this.items = items;
} }
@Override @Override
public Object getItem(int index) { public Object getItem(int index) {
if (index >= 0 && index < items.size()) { if (index >= 0 && index < items.size()) {
return items.get(index); return items.get(index);
} }
return ""; return "";
} }
@Override @Override
public int getItemsCount() { public int getItemsCount() {
return items.size(); return items.size();
} }
@Override @Override
public int indexOf(Object o){ public int indexOf(Object o){
return items.indexOf(o); return items.indexOf(o);
} }
} }
package com.bigkoo.pickerview.adapter; package com.bigkoo.pickerview.adapter;
import com.contrarywind.adapter.WheelAdapter; import com.contrarywind.adapter.WheelAdapter;
/** /**
* Numeric Wheel adapter. * Numeric Wheel adapter.
*/ */
public class NumericWheelAdapter implements WheelAdapter { public class NumericWheelAdapter implements WheelAdapter {
private int minValue; private int minValue;
private int maxValue; private int maxValue;
/** /**
* Constructor * Constructor
* @param minValue the wheel min value * @param minValue the wheel min value
* @param maxValue the wheel max value * @param maxValue the wheel max value
*/ */
public NumericWheelAdapter(int minValue, int maxValue) { public NumericWheelAdapter(int minValue, int maxValue) {
this.minValue = minValue; this.minValue = minValue;
this.maxValue = maxValue; this.maxValue = maxValue;
} }
@Override @Override
public Object getItem(int index) { public Object getItem(int index) {
if (index >= 0 && index < getItemsCount()) { if (index >= 0 && index < getItemsCount()) {
int value = minValue + index; int value = minValue + index;
return value; return value;
} }
return 0; return 0;
} }
@Override @Override
public int getItemsCount() { public int getItemsCount() {
return maxValue - minValue + 1; return maxValue - minValue + 1;
} }
@Override @Override
public int indexOf(Object o){ public int indexOf(Object o){
try { try {
return (int)o - minValue; return (int)o - minValue;
} catch (Exception e) { } catch (Exception e) {
return -1; return -1;
} }
} }
} }
package com.bigkoo.pickerview.utils; package com.bigkoo.pickerview.utils;
import java.text.SimpleDateFormat; import java.text.SimpleDateFormat;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Calendar; import java.util.Calendar;
import java.util.Date; import java.util.Date;
import java.util.GregorianCalendar; import java.util.GregorianCalendar;
import java.util.Locale; import java.util.Locale;
public class ChinaDate { public class ChinaDate {
/** /**
* <lunarInfo 数组值的计算原理> * <lunarInfo 数组值的计算原理>
* *
* 0x代表十六进制,后面的五位数是十六进制数。 * 0x代表十六进制,后面的五位数是十六进制数。
* 举个例子: 1980年的数据是 0x095b0 * 举个例子: 1980年的数据是 0x095b0
* 二进制: 0000 1001 0101 1011 0000 * 二进制: 0000 1001 0101 1011 0000
* 1-4: 表示当年是否为闰年,是的话为1,否则为0。 * 1-4: 表示当年是否为闰年,是的话为1,否则为0。
* 5-16: 为除了闰月外的正常月份是大月还是小月,1为30天,0为29天。 * 5-16: 为除了闰月外的正常月份是大月还是小月,1为30天,0为29天。
* 注意: 从1月到12月对应的是第16位到第5位。 * 注意: 从1月到12月对应的是第16位到第5位。
* 17-20: 非闰年为0,大于0表示闰月月份,仅当存在闰月的情况下有意义。 * 17-20: 非闰年为0,大于0表示闰月月份,仅当存在闰月的情况下有意义。
*/ */
final private static long[] lunarInfo = new long[]{ final private static long[] lunarInfo = new long[]{
0x04bd8, 0x04ae0, 0x0a570, 0x054d5, 0x0d260, 0x0d950, 0x16554, 0x056a0, 0x09ad0, 0x055d2,//1900-1909 0x04bd8, 0x04ae0, 0x0a570, 0x054d5, 0x0d260, 0x0d950, 0x16554, 0x056a0, 0x09ad0, 0x055d2,//1900-1909
0x04ae0, 0x0a5b6, 0x0a4d0, 0x0d250, 0x1d255, 0x0b540, 0x0d6a0, 0x0ada2, 0x095b0, 0x14977,//1910-1919 0x04ae0, 0x0a5b6, 0x0a4d0, 0x0d250, 0x1d255, 0x0b540, 0x0d6a0, 0x0ada2, 0x095b0, 0x14977,//1910-1919
0x04970, 0x0a4b0, 0x0b4b5, 0x06a50, 0x06d40, 0x1ab54, 0x02b60, 0x09570, 0x052f2, 0x04970,//1920-1929 0x04970, 0x0a4b0, 0x0b4b5, 0x06a50, 0x06d40, 0x1ab54, 0x02b60, 0x09570, 0x052f2, 0x04970,//1920-1929
0x06566, 0x0d4a0, 0x0ea50, 0x06e95, 0x05ad0, 0x02b60, 0x186e3, 0x092e0, 0x1c8d7, 0x0c950,//1930-1939 0x06566, 0x0d4a0, 0x0ea50, 0x06e95, 0x05ad0, 0x02b60, 0x186e3, 0x092e0, 0x1c8d7, 0x0c950,//1930-1939
0x0d4a0, 0x1d8a6, 0x0b550, 0x056a0, 0x1a5b4, 0x025d0, 0x092d0, 0x0d2b2, 0x0a950, 0x0b557,//1940-1949 0x0d4a0, 0x1d8a6, 0x0b550, 0x056a0, 0x1a5b4, 0x025d0, 0x092d0, 0x0d2b2, 0x0a950, 0x0b557,//1940-1949
0x06ca0, 0x0b550, 0x15355, 0x04da0, 0x0a5b0, 0x14573, 0x052b0, 0x0a9a8, 0x0e950, 0x06aa0,//1950-1959 0x06ca0, 0x0b550, 0x15355, 0x04da0, 0x0a5b0, 0x14573, 0x052b0, 0x0a9a8, 0x0e950, 0x06aa0,//1950-1959
0x0aea6, 0x0ab50, 0x04b60, 0x0aae4, 0x0a570, 0x05260, 0x0f263, 0x0d950, 0x05b57, 0x056a0,//1960-1969 0x0aea6, 0x0ab50, 0x04b60, 0x0aae4, 0x0a570, 0x05260, 0x0f263, 0x0d950, 0x05b57, 0x056a0,//1960-1969
0x096d0, 0x04dd5, 0x04ad0, 0x0a4d0, 0x0d4d4, 0x0d250, 0x0d558, 0x0b540, 0x0b6a0, 0x195a6,//1970-1979 0x096d0, 0x04dd5, 0x04ad0, 0x0a4d0, 0x0d4d4, 0x0d250, 0x0d558, 0x0b540, 0x0b6a0, 0x195a6,//1970-1979
0x095b0, 0x049b0, 0x0a974, 0x0a4b0, 0x0b27a, 0x06a50, 0x06d40, 0x0af46, 0x0ab60, 0x09570,//1980-1989 0x095b0, 0x049b0, 0x0a974, 0x0a4b0, 0x0b27a, 0x06a50, 0x06d40, 0x0af46, 0x0ab60, 0x09570,//1980-1989
0x04af5, 0x04970, 0x064b0, 0x074a3, 0x0ea50, 0x06b58, 0x055c0, 0x0ab60, 0x096d5, 0x092e0,//1990-1999 0x04af5, 0x04970, 0x064b0, 0x074a3, 0x0ea50, 0x06b58, 0x055c0, 0x0ab60, 0x096d5, 0x092e0,//1990-1999
0x0c960, 0x0d954, 0x0d4a0, 0x0da50, 0x07552, 0x056a0, 0x0abb7, 0x025d0, 0x092d0, 0x0cab5,//2000-2009 0x0c960, 0x0d954, 0x0d4a0, 0x0da50, 0x07552, 0x056a0, 0x0abb7, 0x025d0, 0x092d0, 0x0cab5,//2000-2009
0x0a950, 0x0b4a0, 0x0baa4, 0x0ad50, 0x055d9, 0x04ba0, 0x0a5b0, 0x15176, 0x052b0, 0x0a930,//2010-2019 0x0a950, 0x0b4a0, 0x0baa4, 0x0ad50, 0x055d9, 0x04ba0, 0x0a5b0, 0x15176, 0x052b0, 0x0a930,//2010-2019
0x07954, 0x06aa0, 0x0ad50, 0x05b52, 0x04b60, 0x0a6e6, 0x0a4e0, 0x0d260, 0x0ea65, 0x0d530,//2020-2029 0x07954, 0x06aa0, 0x0ad50, 0x05b52, 0x04b60, 0x0a6e6, 0x0a4e0, 0x0d260, 0x0ea65, 0x0d530,//2020-2029
0x05aa0, 0x076a3, 0x096d0, 0x04afb, 0x04ad0, 0x0a4d0, 0x1d0b6, 0x0d250, 0x0d520, 0x0dd45,//2030-2039 0x05aa0, 0x076a3, 0x096d0, 0x04afb, 0x04ad0, 0x0a4d0, 0x1d0b6, 0x0d250, 0x0d520, 0x0dd45,//2030-2039
0x0b5a0, 0x056d0, 0x055b2, 0x049b0, 0x0a577, 0x0a4b0, 0x0aa50, 0x1b255, 0x06d20, 0x0ada0,//2040-2049 0x0b5a0, 0x056d0, 0x055b2, 0x049b0, 0x0a577, 0x0a4b0, 0x0aa50, 0x1b255, 0x06d20, 0x0ada0,//2040-2049
0x14b63, 0x09370, 0x049f8, 0x04970, 0x064b0, 0x168a6, 0x0ea50, 0x06b20, 0x1a6c4, 0x0aae0,//2050-2059 0x14b63, 0x09370, 0x049f8, 0x04970, 0x064b0, 0x168a6, 0x0ea50, 0x06b20, 0x1a6c4, 0x0aae0,//2050-2059
0x0a2e0, 0x0d2e3, 0x0c960, 0x0d557, 0x0d4a0, 0x0da50, 0x05d55, 0x056a0, 0x0a6d0, 0x055d4,//2060-2069 0x0a2e0, 0x0d2e3, 0x0c960, 0x0d557, 0x0d4a0, 0x0da50, 0x05d55, 0x056a0, 0x0a6d0, 0x055d4,//2060-2069
0x052d0, 0x0a9b8, 0x0a950, 0x0b4a0, 0x0b6a6, 0x0ad50, 0x055a0, 0x0aba4, 0x0a5b0, 0x052b0,//2070-2079 0x052d0, 0x0a9b8, 0x0a950, 0x0b4a0, 0x0b6a6, 0x0ad50, 0x055a0, 0x0aba4, 0x0a5b0, 0x052b0,//2070-2079
0x0b273, 0x06930, 0x07337, 0x06aa0, 0x0ad50, 0x14b55, 0x04b60, 0x0a570, 0x054e4, 0x0d160,//2080-2089 0x0b273, 0x06930, 0x07337, 0x06aa0, 0x0ad50, 0x14b55, 0x04b60, 0x0a570, 0x054e4, 0x0d160,//2080-2089
0x0e968, 0x0d520, 0x0daa0, 0x16aa6, 0x056d0, 0x04ae0, 0x0a9d4, 0x0a2d0, 0x0d150, 0x0f252,//2090-2099 0x0e968, 0x0d520, 0x0daa0, 0x16aa6, 0x056d0, 0x04ae0, 0x0a9d4, 0x0a2d0, 0x0d150, 0x0f252,//2090-2099
0x0d520};//2100 0x0d520};//2100
private final static String[] nStr1 = new String[]{"", "正", "二", "三", "四", private final static String[] nStr1 = new String[]{"", "正", "二", "三", "四",
"五", "六", "七", "八", "九", "十", "冬", "腊"}; "五", "六", "七", "八", "九", "十", "冬", "腊"};
private final static String[] Gan = new String[]{"甲", "乙", "丙", "丁", "戊", private final static String[] Gan = new String[]{"甲", "乙", "丙", "丁", "戊",
"己", "庚", "辛", "壬", "癸"}; "己", "庚", "辛", "壬", "癸"};
private final static String[] Zhi = new String[]{"子", "丑", "寅", "卯", "辰", private final static String[] Zhi = new String[]{"子", "丑", "寅", "卯", "辰",
"巳", "午", "未", "申", "酉", "戌", "亥"}; "巳", "午", "未", "申", "酉", "戌", "亥"};
private final static String[] Animals = new String[]{"鼠", "牛", "虎", "兔", private final static String[] Animals = new String[]{"鼠", "牛", "虎", "兔",
"龙", "蛇", "马", "羊", "猴", "鸡", "狗", "猪"}; "龙", "蛇", "马", "羊", "猴", "鸡", "狗", "猪"};
/** /**
* 传回农历 * 传回农历
* *
* @param y 年的总天数 * @param y 年的总天数
* @return 农历 * @return 农历
*/ */
final private static int lYearDays(int y) { final private static int lYearDays(int y) {
int i, sum = 348; int i, sum = 348;
for (i = 0x8000; i > 0x8; i >>= 1) { for (i = 0x8000; i > 0x8; i >>= 1) {
if ((lunarInfo[y - 1900] & i) != 0) if ((lunarInfo[y - 1900] & i) != 0)
sum += 1; sum += 1;
} }
return (sum + leapDays(y)); return (sum + leapDays(y));
} }
/** /**
* 传回农历 * 传回农历
* *
* @param y 年闰月的天数 * @param y 年闰月的天数
* @return 农历 * @return 农历
*/ */
final public static int leapDays(int y) { final public static int leapDays(int y) {
if (leapMonth(y) != 0) { if (leapMonth(y) != 0) {
if ((lunarInfo[y - 1900] & 0x10000) != 0) if ((lunarInfo[y - 1900] & 0x10000) != 0)
return 30; return 30;
else else
return 29; return 29;
} else } else
return 0; return 0;
} }
/** /**
* 传回农历 * 传回农历
* *
* @param y 年闰哪个月 1-12 , 没闰传回 0 * @param y 年闰哪个月 1-12 , 没闰传回 0
* @return 农历 * @return 农历
*/ */
final public static int leapMonth(int y) { final public static int leapMonth(int y) {
return (int) (lunarInfo[y - 1900] & 0xf); return (int) (lunarInfo[y - 1900] & 0xf);
} }
/** /**
* 传回农历 y * 传回农历 y
* *
* @param y y年m月的总天数 * @param y y年m月的总天数
* @param m y年m月的总天数 * @param m y年m月的总天数
* @return 农历 * @return 农历
*/ */
final public static int monthDays(int y, int m) { final public static int monthDays(int y, int m) {
if ((lunarInfo[y - 1900] & (0x10000 >> m)) == 0) if ((lunarInfo[y - 1900] & (0x10000 >> m)) == 0)
return 29; return 29;
else else
return 30; return 30;
} }
/** /**
* 传回农历 * 传回农历
* *
* @param y 年的生肖 * @param y 年的生肖
* @return * @return
*/ */
final public static String AnimalsYear(int y) { final public static String AnimalsYear(int y) {
return Animals[(y - 4) % 12]; return Animals[(y - 4) % 12];
} }
/** /**
* 传入 * 传入
* *
* @param num 月日的offset 传回干支,0是甲子 * @param num 月日的offset 传回干支,0是甲子
* @return 干支 * @return 干支
*/ */
final private static String cyclicalm(int num) { final private static String cyclicalm(int num) {
return (Gan[num % 10] + Zhi[num % 12]); return (Gan[num % 10] + Zhi[num % 12]);
} }
/** /**
* 传入 offset 传回干支 * 传入 offset 传回干支
* *
* @param y 0是甲子 * @param y 0是甲子
* @return 干支 * @return 干支
*/ */
final public static String cyclical(int y) { final public static String cyclical(int y) {
int num = y - 1900 + 36; int num = y - 1900 + 36;
return (cyclicalm(num)); return (cyclicalm(num));
} }
/** /**
* 传出y年m月d日对应的农历.year0 .month1 .day2 .yearCyl3 .monCyl4 .dayCyl5 .isLeap6 * 传出y年m月d日对应的农历.year0 .month1 .day2 .yearCyl3 .monCyl4 .dayCyl5 .isLeap6
* *
* @param y 年 * @param y 年
* @param m 月 * @param m 月
* @param d 日 * @param d 日
* @return y年m月d日对应的农历 * @return y年m月d日对应的农历
*/ */
final public static long[] calElement(int y, int m, int d) { final public static long[] calElement(int y, int m, int d) {
long[] nongDate = new long[7]; long[] nongDate = new long[7];
int i = 0, temp = 0, leap = 0; int i = 0, temp = 0, leap = 0;
Date baseDate = new GregorianCalendar(0 + 1900, 0, 31).getTime(); Date baseDate = new GregorianCalendar(0 + 1900, 0, 31).getTime();
Date objDate = new GregorianCalendar(y, m - 1, d).getTime(); Date objDate = new GregorianCalendar(y, m - 1, d).getTime();
long offset = (objDate.getTime() - baseDate.getTime()) / 86400000L; long offset = (objDate.getTime() - baseDate.getTime()) / 86400000L;
nongDate[5] = offset + 40; nongDate[5] = offset + 40;
nongDate[4] = 14; nongDate[4] = 14;
for (i = 1900; i < 2100 && offset > 0; i++) { for (i = 1900; i < 2100 && offset > 0; i++) {
temp = lYearDays(i); temp = lYearDays(i);
offset -= temp; offset -= temp;
nongDate[4] += 12; nongDate[4] += 12;
} }
if (offset < 0) { if (offset < 0) {
offset += temp; offset += temp;
i--; i--;
nongDate[4] -= 12; nongDate[4] -= 12;
} }
nongDate[0] = i; nongDate[0] = i;
nongDate[3] = i - 1864; nongDate[3] = i - 1864;
leap = leapMonth(i); // 闰哪个月 leap = leapMonth(i); // 闰哪个月
nongDate[6] = 0; nongDate[6] = 0;
for (i = 1; i < 13 && offset > 0; i++) { for (i = 1; i < 13 && offset > 0; i++) {
// 闰月 // 闰月
if (leap > 0 && i == (leap + 1) && nongDate[6] == 0) { if (leap > 0 && i == (leap + 1) && nongDate[6] == 0) {
--i; --i;
nongDate[6] = 1; nongDate[6] = 1;
temp = leapDays((int) nongDate[0]); temp = leapDays((int) nongDate[0]);
} else { } else {
temp = monthDays((int) nongDate[0], i); temp = monthDays((int) nongDate[0], i);
} }
// 解除闰月 // 解除闰月
if (nongDate[6] == 1 && i == (leap + 1)) if (nongDate[6] == 1 && i == (leap + 1))
nongDate[6] = 0; nongDate[6] = 0;
offset -= temp; offset -= temp;
if (nongDate[6] == 0) if (nongDate[6] == 0)
nongDate[4]++; nongDate[4]++;
} }
if (offset == 0 && leap > 0 && i == leap + 1) { if (offset == 0 && leap > 0 && i == leap + 1) {
if (nongDate[6] == 1) { if (nongDate[6] == 1) {
nongDate[6] = 0; nongDate[6] = 0;
} else { } else {
nongDate[6] = 1; nongDate[6] = 1;
--i; --i;
--nongDate[4]; --nongDate[4];
} }
} }
if (offset < 0) { if (offset < 0) {
offset += temp; offset += temp;
--i; --i;
--nongDate[4]; --nongDate[4];
} }
nongDate[1] = i; nongDate[1] = i;
nongDate[2] = offset + 1; nongDate[2] = offset + 1;
return nongDate; return nongDate;
} }
public final static String getChinaDate(int day) { public final static String getChinaDate(int day) {
String a = ""; String a = "";
if (day == 10) if (day == 10)
return "初十"; return "初十";
if (day == 20) if (day == 20)
return "二十"; return "二十";
if (day == 30) if (day == 30)
return "三十"; return "三十";
int two = (int) ((day) / 10); int two = (int) ((day) / 10);
if (two == 0) if (two == 0)
a = "初"; a = "初";
if (two == 1) if (two == 1)
a = "十"; a = "十";
if (two == 2) if (two == 2)
a = "廿"; a = "廿";
if (two == 3) if (two == 3)
a = "三"; a = "三";
int one = (int) (day % 10); int one = (int) (day % 10);
switch (one) { switch (one) {
case 1: case 1:
a += "一"; a += "一";
break; break;
case 2: case 2:
a += "二"; a += "二";
break; break;
case 3: case 3:
a += "三"; a += "三";
break; break;
case 4: case 4:
a += "四"; a += "四";
break; break;
case 5: case 5:
a += "五"; a += "五";
break; break;
case 6: case 6:
a += "六"; a += "六";
break; break;
case 7: case 7:
a += "七"; a += "七";
break; break;
case 8: case 8:
a += "八"; a += "八";
break; break;
case 9: case 9:
a += "九"; a += "九";
break; break;
} }
return a; return a;
} }
public static String getCurrentLunarDate() { public static String getCurrentLunarDate() {
Calendar today = Calendar.getInstance(Locale.SIMPLIFIED_CHINESE); Calendar today = Calendar.getInstance(Locale.SIMPLIFIED_CHINESE);
int year = today.get(Calendar.YEAR); int year = today.get(Calendar.YEAR);
int month = today.get(Calendar.MONTH) + 1; int month = today.get(Calendar.MONTH) + 1;
int date = today.get(Calendar.DATE); int date = today.get(Calendar.DATE);
long[] l = calElement(year, month, date); long[] l = calElement(year, month, date);
StringBuffer sToday = new StringBuffer(); StringBuffer sToday = new StringBuffer();
try { try {
sToday.append(sdf.format(today.getTime())); sToday.append(sdf.format(today.getTime()));
sToday.append(" 农历"); sToday.append(" 农历");
sToday.append(cyclical(year)); sToday.append(cyclical(year));
sToday.append('('); sToday.append('(');
sToday.append(AnimalsYear(year)); sToday.append(AnimalsYear(year));
sToday.append(")年"); sToday.append(")年");
sToday.append(nStr1[(int) l[1]]); sToday.append(nStr1[(int) l[1]]);
sToday.append("月"); sToday.append("月");
sToday.append(getChinaDate((int) (l[2]))); sToday.append(getChinaDate((int) (l[2])));
return sToday.toString(); return sToday.toString();
} finally { } finally {
sToday = null; sToday = null;
} }
} }
public static String oneDay(int year, int month, int day) { public static String oneDay(int year, int month, int day) {
// Calendar today = Calendar.getInstance(Locale.SIMPLIFIED_CHINESE); // Calendar today = Calendar.getInstance(Locale.SIMPLIFIED_CHINESE);
long[] l = calElement(year, month, day); long[] l = calElement(year, month, day);
StringBuffer sToday = new StringBuffer(); StringBuffer sToday = new StringBuffer();
try { try {
// sToday.append(sdf.format(today.getTime())); // sToday.append(sdf.format(today.getTime()));
sToday.append(" 农历"); sToday.append(" 农历");
sToday.append(cyclical(year)); sToday.append(cyclical(year));
sToday.append('('); sToday.append('(');
sToday.append(AnimalsYear(year)); sToday.append(AnimalsYear(year));
sToday.append(")年"); sToday.append(")年");
sToday.append(nStr1[(int) l[1]]); sToday.append(nStr1[(int) l[1]]);
sToday.append("月"); sToday.append("月");
sToday.append(getChinaDate((int) (l[2]))); sToday.append(getChinaDate((int) (l[2])));
return sToday.toString(); return sToday.toString();
} finally { } finally {
sToday = null; sToday = null;
} }
} }
private static SimpleDateFormat sdf = new SimpleDateFormat("yyyy年M月d日 EEEEE"); private static SimpleDateFormat sdf = new SimpleDateFormat("yyyy年M月d日 EEEEE");
/** /**
* @param lunarYear 农历年份 * @param lunarYear 农历年份
* @return String of Ganzhi: 甲子年 * @return String of Ganzhi: 甲子年
* 甲乙丙丁戊己庚辛壬癸 * 甲乙丙丁戊己庚辛壬癸
* 子丑寅卯辰巳无为申酉戌亥 * 子丑寅卯辰巳无为申酉戌亥
*/ */
public static String getLunarYearText(int lunarYear) { public static String getLunarYearText(int lunarYear) {
return Gan[(lunarYear - 4) % 10] + Zhi[(lunarYear - 4) % 12] + "年"; return Gan[(lunarYear - 4) % 10] + Zhi[(lunarYear - 4) % 12] + "年";
} }
public static ArrayList<String> getYears(int startYear, int endYear) { public static ArrayList<String> getYears(int startYear, int endYear) {
ArrayList<String> years = new ArrayList<>(); ArrayList<String> years = new ArrayList<>();
for (int i = startYear; i < endYear; i++) { for (int i = startYear; i < endYear; i++) {
years.add(String.format("%s(%d)", getLunarYearText(i), i)); years.add(String.format("%s(%d)", getLunarYearText(i), i));
} }
return years; return years;
} }
/** /**
* 获取year年的所有月份 * 获取year年的所有月份
* *
* @param year 年 * @param year 年
* @return 月份列表 * @return 月份列表
*/ */
public static ArrayList<String> getMonths(int year) { public static ArrayList<String> getMonths(int year) {
ArrayList<String> baseMonths = new ArrayList<>(); ArrayList<String> baseMonths = new ArrayList<>();
for (int i = 1; i < nStr1.length; i++) { for (int i = 1; i < nStr1.length; i++) {
baseMonths.add(nStr1[i] + "月"); baseMonths.add(nStr1[i] + "月");
} }
if (leapMonth(year) != 0) { if (leapMonth(year) != 0) {
baseMonths.add(leapMonth(year), "闰" + nStr1[leapMonth(year)] + "月"); baseMonths.add(leapMonth(year), "闰" + nStr1[leapMonth(year)] + "月");
} }
return baseMonths; return baseMonths;
} }
/** /**
* 获取每月农历显示名称 * 获取每月农历显示名称
* *
* @param maxDay 天 * @param maxDay 天
* @return 名称列表 * @return 名称列表
*/ */
public static ArrayList<String> getLunarDays(int maxDay) { public static ArrayList<String> getLunarDays(int maxDay) {
ArrayList<String> days = new ArrayList<>(); ArrayList<String> days = new ArrayList<>();
for (int i = 1; i <= maxDay; i++) { for (int i = 1; i <= maxDay; i++) {
days.add(getChinaDate(i)); days.add(getChinaDate(i));
} }
return days; return days;
} }
/*public static int getMaxHour(int year, int month, int day) { /*public static int getMaxHour(int year, int month, int day) {
}*/ }*/
/**** /****
* 获取这天的最大时 * 获取这天的最大时
* */ * */
} }
package com.bigkoo.pickerview.utils; package com.bigkoo.pickerview.utils;
import android.util.Log; import android.util.Log;
import java.util.Date; import java.util.Date;
import java.util.GregorianCalendar; import java.util.GregorianCalendar;
/** /**
* author: Jerry on 2016/7/11 11:29. * author: Jerry on 2016/7/11 11:29.
* description:工具类,实现公农历互转 * description:工具类,实现公农历互转
*/ */
public class LunarCalendar { public class LunarCalendar {
/** /**
* 支持转换的最小农历年份 * 支持转换的最小农历年份
*/ */
public static final int MIN_YEAR = 1900; public static final int MIN_YEAR = 1900;
/** /**
* 支持转换的最大农历年份 * 支持转换的最大农历年份
*/ */
public static final int MAX_YEAR = 2099; public static final int MAX_YEAR = 2099;
/** /**
* 公历每月前的天数 * 公历每月前的天数
*/ */
private static final int DAYS_BEFORE_MONTH[] = {0, 31, 59, 90, 120, 151, 181, private static final int DAYS_BEFORE_MONTH[] = {0, 31, 59, 90, 120, 151, 181,
212, 243, 273, 304, 334, 365}; 212, 243, 273, 304, 334, 365};
/** /**
* 用来表示1900年到2099年间农历年份的相关信息,共24位bit的16进制表示,其中: * 用来表示1900年到2099年间农历年份的相关信息,共24位bit的16进制表示,其中:
* 1. 前4位表示该年闰哪个月; * 1. 前4位表示该年闰哪个月;
* 2. 5-17位表示农历年份13个月的大小月分布,0表示小,1表示大; * 2. 5-17位表示农历年份13个月的大小月分布,0表示小,1表示大;
* 3. 最后7位表示农历年首(正月初一)对应的公历日期。 * 3. 最后7位表示农历年首(正月初一)对应的公历日期。
* <p> * <p>
* 以2014年的数据0x955ABF为例说明: * 以2014年的数据0x955ABF为例说明:
* 1001 0101 0101 1010 1011 1111 * 1001 0101 0101 1010 1011 1111
* 闰九月 农历正月初一对应公历1月31号 * 闰九月 农历正月初一对应公历1月31号
*/ */
private static final int LUNAR_INFO[] = { private static final int LUNAR_INFO[] = {
0x84B6BF,/*1900*/ 0x84B6BF,/*1900*/
0x04AE53, 0x0A5748, 0x5526BD, 0x0D2650, 0x0D9544, 0x46AAB9, 0x056A4D, 0x09AD42, 0x24AEB6, 0x04AE4A,/*1901-1910*/ 0x04AE53, 0x0A5748, 0x5526BD, 0x0D2650, 0x0D9544, 0x46AAB9, 0x056A4D, 0x09AD42, 0x24AEB6, 0x04AE4A,/*1901-1910*/
0x6A4DBE, 0x0A4D52, 0x0D2546, 0x5D52BA, 0x0B544E, 0x0D6A43, 0x296D37, 0x095B4B, 0x749BC1, 0x049754,/*1911-1920*/ 0x6A4DBE, 0x0A4D52, 0x0D2546, 0x5D52BA, 0x0B544E, 0x0D6A43, 0x296D37, 0x095B4B, 0x749BC1, 0x049754,/*1911-1920*/
0x0A4B48, 0x5B25BC, 0x06A550, 0x06D445, 0x4ADAB8, 0x02B64D, 0x095742, 0x2497B7, 0x04974A, 0x664B3E,/*1921-1930*/ 0x0A4B48, 0x5B25BC, 0x06A550, 0x06D445, 0x4ADAB8, 0x02B64D, 0x095742, 0x2497B7, 0x04974A, 0x664B3E,/*1921-1930*/
0x0D4A51, 0x0EA546, 0x56D4BA, 0x05AD4E, 0x02B644, 0x393738, 0x092E4B, 0x7C96BF, 0x0C9553, 0x0D4A48,/*1931-1940*/ 0x0D4A51, 0x0EA546, 0x56D4BA, 0x05AD4E, 0x02B644, 0x393738, 0x092E4B, 0x7C96BF, 0x0C9553, 0x0D4A48,/*1931-1940*/
0x6DA53B, 0x0B554F, 0x056A45, 0x4AADB9, 0x025D4D, 0x092D42, 0x2C95B6, 0x0A954A, 0x7B4ABD, 0x06CA51,/*1941-1950*/ 0x6DA53B, 0x0B554F, 0x056A45, 0x4AADB9, 0x025D4D, 0x092D42, 0x2C95B6, 0x0A954A, 0x7B4ABD, 0x06CA51,/*1941-1950*/
0x0B5546, 0x555ABB, 0x04DA4E, 0x0A5B43, 0x352BB8, 0x052B4C, 0x8A953F, 0x0E9552, 0x06AA48, 0x6AD53C,/*1951-1960*/ 0x0B5546, 0x555ABB, 0x04DA4E, 0x0A5B43, 0x352BB8, 0x052B4C, 0x8A953F, 0x0E9552, 0x06AA48, 0x6AD53C,/*1951-1960*/
0x0AB54F, 0x04B645, 0x4A5739, 0x0A574D, 0x052642, 0x3E9335, 0x0D9549, 0x75AABE, 0x056A51, 0x096D46,/*1961-1970*/ 0x0AB54F, 0x04B645, 0x4A5739, 0x0A574D, 0x052642, 0x3E9335, 0x0D9549, 0x75AABE, 0x056A51, 0x096D46,/*1961-1970*/
0x54AEBB, 0x04AD4F, 0x0A4D43, 0x4D26B7, 0x0D254B, 0x8D52BF, 0x0B5452, 0x0B6A47, 0x696D3C, 0x095B50,/*1971-1980*/ 0x54AEBB, 0x04AD4F, 0x0A4D43, 0x4D26B7, 0x0D254B, 0x8D52BF, 0x0B5452, 0x0B6A47, 0x696D3C, 0x095B50,/*1971-1980*/
0x049B45, 0x4A4BB9, 0x0A4B4D, 0xAB25C2, 0x06A554, 0x06D449, 0x6ADA3D, 0x0AB651, 0x095746, 0x5497BB,/*1981-1990*/ 0x049B45, 0x4A4BB9, 0x0A4B4D, 0xAB25C2, 0x06A554, 0x06D449, 0x6ADA3D, 0x0AB651, 0x095746, 0x5497BB,/*1981-1990*/
0x04974F, 0x064B44, 0x36A537, 0x0EA54A, 0x86B2BF, 0x05AC53, 0x0AB647, 0x5936BC, 0x092E50, 0x0C9645,/*1991-2000*/ 0x04974F, 0x064B44, 0x36A537, 0x0EA54A, 0x86B2BF, 0x05AC53, 0x0AB647, 0x5936BC, 0x092E50, 0x0C9645,/*1991-2000*/
0x4D4AB8, 0x0D4A4C, 0x0DA541, 0x25AAB6, 0x056A49, 0x7AADBD, 0x025D52, 0x092D47, 0x5C95BA, 0x0A954E,/*2001-2010*/ 0x4D4AB8, 0x0D4A4C, 0x0DA541, 0x25AAB6, 0x056A49, 0x7AADBD, 0x025D52, 0x092D47, 0x5C95BA, 0x0A954E,/*2001-2010*/
0x0B4A43, 0x4B5537, 0x0AD54A, 0x955ABF, 0x04BA53, 0x0A5B48, 0x652BBC, 0x052B50, 0x0A9345, 0x474AB9,/*2011-2020*/ 0x0B4A43, 0x4B5537, 0x0AD54A, 0x955ABF, 0x04BA53, 0x0A5B48, 0x652BBC, 0x052B50, 0x0A9345, 0x474AB9,/*2011-2020*/
0x06AA4C, 0x0AD541, 0x24DAB6, 0x04B64A, 0x6a573D, 0x0A4E51, 0x0D2646, 0x5E933A, 0x0D534D, 0x05AA43,/*2021-2030*/ 0x06AA4C, 0x0AD541, 0x24DAB6, 0x04B64A, 0x6a573D, 0x0A4E51, 0x0D2646, 0x5E933A, 0x0D534D, 0x05AA43,/*2021-2030*/
0x36B537, 0x096D4B, 0xB4AEBF, 0x04AD53, 0x0A4D48, 0x6D25BC, 0x0D254F, 0x0D5244, 0x5DAA38, 0x0B5A4C,/*2031-2040*/ 0x36B537, 0x096D4B, 0xB4AEBF, 0x04AD53, 0x0A4D48, 0x6D25BC, 0x0D254F, 0x0D5244, 0x5DAA38, 0x0B5A4C,/*2031-2040*/
0x056D41, 0x24ADB6, 0x049B4A, 0x7A4BBE, 0x0A4B51, 0x0AA546, 0x5B52BA, 0x06D24E, 0x0ADA42, 0x355B37,/*2041-2050*/ 0x056D41, 0x24ADB6, 0x049B4A, 0x7A4BBE, 0x0A4B51, 0x0AA546, 0x5B52BA, 0x06D24E, 0x0ADA42, 0x355B37,/*2041-2050*/
0x09374B, 0x8497C1, 0x049753, 0x064B48, 0x66A53C, 0x0EA54F, 0x06AA44, 0x4AB638, 0x0AAE4C, 0x092E42,/*2051-2060*/ 0x09374B, 0x8497C1, 0x049753, 0x064B48, 0x66A53C, 0x0EA54F, 0x06AA44, 0x4AB638, 0x0AAE4C, 0x092E42,/*2051-2060*/
0x3C9735, 0x0C9649, 0x7D4ABD, 0x0D4A51, 0x0DA545, 0x55AABA, 0x056A4E, 0x0A6D43, 0x452EB7, 0x052D4B,/*2061-2070*/ 0x3C9735, 0x0C9649, 0x7D4ABD, 0x0D4A51, 0x0DA545, 0x55AABA, 0x056A4E, 0x0A6D43, 0x452EB7, 0x052D4B,/*2061-2070*/
0x8A95BF, 0x0A9553, 0x0B4A47, 0x6B553B, 0x0AD54F, 0x055A45, 0x4A5D38, 0x0A5B4C, 0x052B42, 0x3A93B6,/*2071-2080*/ 0x8A95BF, 0x0A9553, 0x0B4A47, 0x6B553B, 0x0AD54F, 0x055A45, 0x4A5D38, 0x0A5B4C, 0x052B42, 0x3A93B6,/*2071-2080*/
0x069349, 0x7729BD, 0x06AA51, 0x0AD546, 0x54DABA, 0x04B64E, 0x0A5743, 0x452738, 0x0D264A, 0x8E933E,/*2081-2090*/ 0x069349, 0x7729BD, 0x06AA51, 0x0AD546, 0x54DABA, 0x04B64E, 0x0A5743, 0x452738, 0x0D264A, 0x8E933E,/*2081-2090*/
0x0D5252, 0x0DAA47, 0x66B53B, 0x056D4F, 0x04AE45, 0x4A4EB9, 0x0A4D4C, 0x0D1541, 0x2D92B5 /*2091-2099*/ 0x0D5252, 0x0DAA47, 0x66B53B, 0x056D4F, 0x04AE45, 0x4A4EB9, 0x0A4D4C, 0x0D1541, 0x2D92B5 /*2091-2099*/
}; };
private static int[] solar_1_1 = {1887, 0xec04c, 0xec23f, 0xec435, 0xec649, private static int[] solar_1_1 = {1887, 0xec04c, 0xec23f, 0xec435, 0xec649,
0xec83e, 0xeca51, 0xecc46, 0xece3a, 0xed04d, 0xed242, 0xed436, 0xec83e, 0xeca51, 0xecc46, 0xece3a, 0xed04d, 0xed242, 0xed436,
0xed64a, 0xed83f, 0xeda53, 0xedc48, 0xede3d, 0xee050, 0xee244, 0xed64a, 0xed83f, 0xeda53, 0xedc48, 0xede3d, 0xee050, 0xee244,
0xee439, 0xee64d, 0xee842, 0xeea36, 0xeec4a, 0xeee3e, 0xef052, 0xee439, 0xee64d, 0xee842, 0xeea36, 0xeec4a, 0xeee3e, 0xef052,
0xef246, 0xef43a, 0xef64e, 0xef843, 0xefa37, 0xefc4b, 0xefe41, 0xef246, 0xef43a, 0xef64e, 0xef843, 0xefa37, 0xefc4b, 0xefe41,
0xf0054, 0xf0248, 0xf043c, 0xf0650, 0xf0845, 0xf0a38, 0xf0c4d, 0xf0054, 0xf0248, 0xf043c, 0xf0650, 0xf0845, 0xf0a38, 0xf0c4d,
0xf0e42, 0xf1037, 0xf124a, 0xf143e, 0xf1651, 0xf1846, 0xf1a3a, 0xf0e42, 0xf1037, 0xf124a, 0xf143e, 0xf1651, 0xf1846, 0xf1a3a,
0xf1c4e, 0xf1e44, 0xf2038, 0xf224b, 0xf243f, 0xf2653, 0xf2848, 0xf1c4e, 0xf1e44, 0xf2038, 0xf224b, 0xf243f, 0xf2653, 0xf2848,
0xf2a3b, 0xf2c4f, 0xf2e45, 0xf3039, 0xf324d, 0xf3442, 0xf3636, 0xf2a3b, 0xf2c4f, 0xf2e45, 0xf3039, 0xf324d, 0xf3442, 0xf3636,
0xf384a, 0xf3a3d, 0xf3c51, 0xf3e46, 0xf403b, 0xf424e, 0xf4443, 0xf384a, 0xf3a3d, 0xf3c51, 0xf3e46, 0xf403b, 0xf424e, 0xf4443,
0xf4638, 0xf484c, 0xf4a3f, 0xf4c52, 0xf4e48, 0xf503c, 0xf524f, 0xf4638, 0xf484c, 0xf4a3f, 0xf4c52, 0xf4e48, 0xf503c, 0xf524f,
0xf5445, 0xf5639, 0xf584d, 0xf5a42, 0xf5c35, 0xf5e49, 0xf603e, 0xf5445, 0xf5639, 0xf584d, 0xf5a42, 0xf5c35, 0xf5e49, 0xf603e,
0xf6251, 0xf6446, 0xf663b, 0xf684f, 0xf6a43, 0xf6c37, 0xf6e4b, 0xf6251, 0xf6446, 0xf663b, 0xf684f, 0xf6a43, 0xf6c37, 0xf6e4b,
0xf703f, 0xf7252, 0xf7447, 0xf763c, 0xf7850, 0xf7a45, 0xf7c39, 0xf703f, 0xf7252, 0xf7447, 0xf763c, 0xf7850, 0xf7a45, 0xf7c39,
0xf7e4d, 0xf8042, 0xf8254, 0xf8449, 0xf863d, 0xf8851, 0xf8a46, 0xf7e4d, 0xf8042, 0xf8254, 0xf8449, 0xf863d, 0xf8851, 0xf8a46,
0xf8c3b, 0xf8e4f, 0xf9044, 0xf9237, 0xf944a, 0xf963f, 0xf9853, 0xf8c3b, 0xf8e4f, 0xf9044, 0xf9237, 0xf944a, 0xf963f, 0xf9853,
0xf9a47, 0xf9c3c, 0xf9e50, 0xfa045, 0xfa238, 0xfa44c, 0xfa641, 0xf9a47, 0xf9c3c, 0xf9e50, 0xfa045, 0xfa238, 0xfa44c, 0xfa641,
0xfa836, 0xfaa49, 0xfac3d, 0xfae52, 0xfb047, 0xfb23a, 0xfb44e, 0xfa836, 0xfaa49, 0xfac3d, 0xfae52, 0xfb047, 0xfb23a, 0xfb44e,
0xfb643, 0xfb837, 0xfba4a, 0xfbc3f, 0xfbe53, 0xfc048, 0xfc23c, 0xfb643, 0xfb837, 0xfba4a, 0xfbc3f, 0xfbe53, 0xfc048, 0xfc23c,
0xfc450, 0xfc645, 0xfc839, 0xfca4c, 0xfcc41, 0xfce36, 0xfd04a, 0xfc450, 0xfc645, 0xfc839, 0xfca4c, 0xfcc41, 0xfce36, 0xfd04a,
0xfd23d, 0xfd451, 0xfd646, 0xfd83a, 0xfda4d, 0xfdc43, 0xfde37, 0xfd23d, 0xfd451, 0xfd646, 0xfd83a, 0xfda4d, 0xfdc43, 0xfde37,
0xfe04b, 0xfe23f, 0xfe453, 0xfe648, 0xfe83c, 0xfea4f, 0xfec44, 0xfe04b, 0xfe23f, 0xfe453, 0xfe648, 0xfe83c, 0xfea4f, 0xfec44,
0xfee38, 0xff04c, 0xff241, 0xff436, 0xff64a, 0xff83e, 0xffa51, 0xfee38, 0xff04c, 0xff241, 0xff436, 0xff64a, 0xff83e, 0xffa51,
0xffc46, 0xffe3a, 0x10004e, 0x100242, 0x100437, 0x10064b, 0x100841, 0xffc46, 0xffe3a, 0x10004e, 0x100242, 0x100437, 0x10064b, 0x100841,
0x100a53, 0x100c48, 0x100e3c, 0x10104f, 0x101244, 0x101438, 0x100a53, 0x100c48, 0x100e3c, 0x10104f, 0x101244, 0x101438,
0x10164c, 0x101842, 0x101a35, 0x101c49, 0x101e3d, 0x102051, 0x10164c, 0x101842, 0x101a35, 0x101c49, 0x101e3d, 0x102051,
0x102245, 0x10243a, 0x10264e, 0x102843, 0x102a37, 0x102c4b, 0x102245, 0x10243a, 0x10264e, 0x102843, 0x102a37, 0x102c4b,
0x102e3f, 0x103053, 0x103247, 0x10343b, 0x10364f, 0x103845, 0x102e3f, 0x103053, 0x103247, 0x10343b, 0x10364f, 0x103845,
0x103a38, 0x103c4c, 0x103e42, 0x104036, 0x104249, 0x10443d, 0x103a38, 0x103c4c, 0x103e42, 0x104036, 0x104249, 0x10443d,
0x104651, 0x104846, 0x104a3a, 0x104c4e, 0x104e43, 0x105038, 0x104651, 0x104846, 0x104a3a, 0x104c4e, 0x104e43, 0x105038,
0x10524a, 0x10543e, 0x105652, 0x105847, 0x105a3b, 0x105c4f, 0x10524a, 0x10543e, 0x105652, 0x105847, 0x105a3b, 0x105c4f,
0x105e45, 0x106039, 0x10624c, 0x106441, 0x106635, 0x106849, 0x105e45, 0x106039, 0x10624c, 0x106441, 0x106635, 0x106849,
0x106a3d, 0x106c51, 0x106e47, 0x10703c, 0x10724f, 0x107444, 0x106a3d, 0x106c51, 0x106e47, 0x10703c, 0x10724f, 0x107444,
0x107638, 0x10784c, 0x107a3f, 0x107c53, 0x107e48}; 0x107638, 0x10784c, 0x107a3f, 0x107c53, 0x107e48};
private static int[] lunar_month_days = {1887, 0x1694, 0x16aa, 0x4ad5, private static int[] lunar_month_days = {1887, 0x1694, 0x16aa, 0x4ad5,
0xab6, 0xc4b7, 0x4ae, 0xa56, 0xb52a, 0x1d2a, 0xd54, 0x75aa, 0x156a, 0xab6, 0xc4b7, 0x4ae, 0xa56, 0xb52a, 0x1d2a, 0xd54, 0x75aa, 0x156a,
0x1096d, 0x95c, 0x14ae, 0xaa4d, 0x1a4c, 0x1b2a, 0x8d55, 0xad4, 0x1096d, 0x95c, 0x14ae, 0xaa4d, 0x1a4c, 0x1b2a, 0x8d55, 0xad4,
0x135a, 0x495d, 0x95c, 0xd49b, 0x149a, 0x1a4a, 0xbaa5, 0x16a8, 0x135a, 0x495d, 0x95c, 0xd49b, 0x149a, 0x1a4a, 0xbaa5, 0x16a8,
0x1ad4, 0x52da, 0x12b6, 0xe937, 0x92e, 0x1496, 0xb64b, 0xd4a, 0x1ad4, 0x52da, 0x12b6, 0xe937, 0x92e, 0x1496, 0xb64b, 0xd4a,
0xda8, 0x95b5, 0x56c, 0x12ae, 0x492f, 0x92e, 0xcc96, 0x1a94, 0xda8, 0x95b5, 0x56c, 0x12ae, 0x492f, 0x92e, 0xcc96, 0x1a94,
0x1d4a, 0xada9, 0xb5a, 0x56c, 0x726e, 0x125c, 0xf92d, 0x192a, 0x1d4a, 0xada9, 0xb5a, 0x56c, 0x726e, 0x125c, 0xf92d, 0x192a,
0x1a94, 0xdb4a, 0x16aa, 0xad4, 0x955b, 0x4ba, 0x125a, 0x592b, 0x1a94, 0xdb4a, 0x16aa, 0xad4, 0x955b, 0x4ba, 0x125a, 0x592b,
0x152a, 0xf695, 0xd94, 0x16aa, 0xaab5, 0x9b4, 0x14b6, 0x6a57, 0x152a, 0xf695, 0xd94, 0x16aa, 0xaab5, 0x9b4, 0x14b6, 0x6a57,
0xa56, 0x1152a, 0x1d2a, 0xd54, 0xd5aa, 0x156a, 0x96c, 0x94ae, 0xa56, 0x1152a, 0x1d2a, 0xd54, 0xd5aa, 0x156a, 0x96c, 0x94ae,
0x14ae, 0xa4c, 0x7d26, 0x1b2a, 0xeb55, 0xad4, 0x12da, 0xa95d, 0x14ae, 0xa4c, 0x7d26, 0x1b2a, 0xeb55, 0xad4, 0x12da, 0xa95d,
0x95a, 0x149a, 0x9a4d, 0x1a4a, 0x11aa5, 0x16a8, 0x16d4, 0xd2da, 0x95a, 0x149a, 0x9a4d, 0x1a4a, 0x11aa5, 0x16a8, 0x16d4, 0xd2da,
0x12b6, 0x936, 0x9497, 0x1496, 0x1564b, 0xd4a, 0xda8, 0xd5b4, 0x12b6, 0x936, 0x9497, 0x1496, 0x1564b, 0xd4a, 0xda8, 0xd5b4,
0x156c, 0x12ae, 0xa92f, 0x92e, 0xc96, 0x6d4a, 0x1d4a, 0x10d65, 0x156c, 0x12ae, 0xa92f, 0x92e, 0xc96, 0x6d4a, 0x1d4a, 0x10d65,
0xb58, 0x156c, 0xb26d, 0x125c, 0x192c, 0x9a95, 0x1a94, 0x1b4a, 0xb58, 0x156c, 0xb26d, 0x125c, 0x192c, 0x9a95, 0x1a94, 0x1b4a,
0x4b55, 0xad4, 0xf55b, 0x4ba, 0x125a, 0xb92b, 0x152a, 0x1694, 0x4b55, 0xad4, 0xf55b, 0x4ba, 0x125a, 0xb92b, 0x152a, 0x1694,
0x96aa, 0x15aa, 0x12ab5, 0x974, 0x14b6, 0xca57, 0xa56, 0x1526, 0x96aa, 0x15aa, 0x12ab5, 0x974, 0x14b6, 0xca57, 0xa56, 0x1526,
0x8e95, 0xd54, 0x15aa, 0x49b5, 0x96c, 0xd4ae, 0x149c, 0x1a4c, 0x8e95, 0xd54, 0x15aa, 0x49b5, 0x96c, 0xd4ae, 0x149c, 0x1a4c,
0xbd26, 0x1aa6, 0xb54, 0x6d6a, 0x12da, 0x1695d, 0x95a, 0x149a, 0xbd26, 0x1aa6, 0xb54, 0x6d6a, 0x12da, 0x1695d, 0x95a, 0x149a,
0xda4b, 0x1a4a, 0x1aa4, 0xbb54, 0x16b4, 0xada, 0x495b, 0x936, 0xda4b, 0x1a4a, 0x1aa4, 0xbb54, 0x16b4, 0xada, 0x495b, 0x936,
0xf497, 0x1496, 0x154a, 0xb6a5, 0xda4, 0x15b4, 0x6ab6, 0x126e, 0xf497, 0x1496, 0x154a, 0xb6a5, 0xda4, 0x15b4, 0x6ab6, 0x126e,
0x1092f, 0x92e, 0xc96, 0xcd4a, 0x1d4a, 0xd64, 0x956c, 0x155c, 0x1092f, 0x92e, 0xc96, 0xcd4a, 0x1d4a, 0xd64, 0x956c, 0x155c,
0x125c, 0x792e, 0x192c, 0xfa95, 0x1a94, 0x1b4a, 0xab55, 0xad4, 0x125c, 0x792e, 0x192c, 0xfa95, 0x1a94, 0x1b4a, 0xab55, 0xad4,
0x14da, 0x8a5d, 0xa5a, 0x1152b, 0x152a, 0x1694, 0xd6aa, 0x15aa, 0x14da, 0x8a5d, 0xa5a, 0x1152b, 0x152a, 0x1694, 0xd6aa, 0x15aa,
0xab4, 0x94ba, 0x14b6, 0xa56, 0x7527, 0xd26, 0xee53, 0xd54, 0x15aa, 0xab4, 0x94ba, 0x14b6, 0xa56, 0x7527, 0xd26, 0xee53, 0xd54, 0x15aa,
0xa9b5, 0x96c, 0x14ae, 0x8a4e, 0x1a4c, 0x11d26, 0x1aa4, 0x1b54, 0xa9b5, 0x96c, 0x14ae, 0x8a4e, 0x1a4c, 0x11d26, 0x1aa4, 0x1b54,
0xcd6a, 0xada, 0x95c, 0x949d, 0x149a, 0x1a2a, 0x5b25, 0x1aa4, 0xcd6a, 0xada, 0x95c, 0x949d, 0x149a, 0x1a2a, 0x5b25, 0x1aa4,
0xfb52, 0x16b4, 0xaba, 0xa95b, 0x936, 0x1496, 0x9a4b, 0x154a, 0xfb52, 0x16b4, 0xaba, 0xa95b, 0x936, 0x1496, 0x9a4b, 0x154a,
0x136a5, 0xda4, 0x15ac}; 0x136a5, 0xda4, 0x15ac};
/** /**
* 将农历日期转换为公历日期 * 将农历日期转换为公历日期
* *
* @param year 农历年份 * @param year 农历年份
* @param month 农历月 * @param month 农历月
* @param monthDay 农历日 * @param monthDay 农历日
* @param isLeapMonth 该月是否是闰月 * @param isLeapMonth 该月是否是闰月
* @return 返回农历日期对应的公历日期,year0, month1, day2. * @return 返回农历日期对应的公历日期,year0, month1, day2.
*/ */
public static final int[] lunarToSolar(int year, int month, int monthDay, public static final int[] lunarToSolar(int year, int month, int monthDay,
boolean isLeapMonth) { boolean isLeapMonth) {
int dayOffset; int dayOffset;
int leapMonth; int leapMonth;
int i; int i;
if (year < MIN_YEAR || year > MAX_YEAR || month < 1 || month > 12 if (year < MIN_YEAR || year > MAX_YEAR || month < 1 || month > 12
|| monthDay < 1 || monthDay > 30) { || monthDay < 1 || monthDay > 30) {
throw new IllegalArgumentException( throw new IllegalArgumentException(
"Illegal lunar date, must be like that:\n\t" + "Illegal lunar date, must be like that:\n\t" +
"year : 1900~2099\n\t" + "year : 1900~2099\n\t" +
"month : 1~12\n\t" + "month : 1~12\n\t" +
"day : 1~30"); "day : 1~30");
} }
dayOffset = (LUNAR_INFO[year - MIN_YEAR] & 0x001F) - 1; dayOffset = (LUNAR_INFO[year - MIN_YEAR] & 0x001F) - 1;
if (((LUNAR_INFO[year - MIN_YEAR] & 0x0060) >> 5) == 2) if (((LUNAR_INFO[year - MIN_YEAR] & 0x0060) >> 5) == 2)
dayOffset += 31; dayOffset += 31;
for (i = 1; i < month; i++) { for (i = 1; i < month; i++) {
if ((LUNAR_INFO[year - MIN_YEAR] & (0x80000 >> (i - 1))) == 0) if ((LUNAR_INFO[year - MIN_YEAR] & (0x80000 >> (i - 1))) == 0)
dayOffset += 29; dayOffset += 29;
else else
dayOffset += 30; dayOffset += 30;
} }
dayOffset += monthDay; dayOffset += monthDay;
leapMonth = (LUNAR_INFO[year - MIN_YEAR] & 0xf00000) >> 20; leapMonth = (LUNAR_INFO[year - MIN_YEAR] & 0xf00000) >> 20;
// 这一年有闰月 // 这一年有闰月
if (leapMonth != 0) { if (leapMonth != 0) {
if (month > leapMonth || (month == leapMonth && isLeapMonth)) { if (month > leapMonth || (month == leapMonth && isLeapMonth)) {
if ((LUNAR_INFO[year - MIN_YEAR] & (0x80000 >> (month - 1))) == 0) if ((LUNAR_INFO[year - MIN_YEAR] & (0x80000 >> (month - 1))) == 0)
dayOffset += 29; dayOffset += 29;
else else
dayOffset += 30; dayOffset += 30;
} }
} }
if (dayOffset > 366 || (year % 4 != 0 && dayOffset > 365)) { if (dayOffset > 366 || (year % 4 != 0 && dayOffset > 365)) {
year += 1; year += 1;
if (year % 4 == 1) if (year % 4 == 1)
dayOffset -= 366; dayOffset -= 366;
else else
dayOffset -= 365; dayOffset -= 365;
} }
int[] solarInfo = new int[3]; int[] solarInfo = new int[3];
for (i = 1; i < 13; i++) { for (i = 1; i < 13; i++) {
int iPos = DAYS_BEFORE_MONTH[i]; int iPos = DAYS_BEFORE_MONTH[i];
if (year % 4 == 0 && i > 2) { if (year % 4 == 0 && i > 2) {
iPos += 1; iPos += 1;
} }
if (year % 4 == 0 && i == 2 && iPos + 1 == dayOffset) { if (year % 4 == 0 && i == 2 && iPos + 1 == dayOffset) {
solarInfo[1] = i; solarInfo[1] = i;
solarInfo[2] = dayOffset - 31; solarInfo[2] = dayOffset - 31;
break; break;
} }
if (iPos >= dayOffset) { if (iPos >= dayOffset) {
solarInfo[1] = i; solarInfo[1] = i;
iPos = DAYS_BEFORE_MONTH[i - 1]; iPos = DAYS_BEFORE_MONTH[i - 1];
if (year % 4 == 0 && i > 2) { if (year % 4 == 0 && i > 2) {
iPos += 1; iPos += 1;
} }
if (dayOffset > iPos) if (dayOffset > iPos)
solarInfo[2] = dayOffset - iPos; solarInfo[2] = dayOffset - iPos;
else if (dayOffset == iPos) { else if (dayOffset == iPos) {
if (year % 4 == 0 && i == 2) if (year % 4 == 0 && i == 2)
solarInfo[2] = DAYS_BEFORE_MONTH[i] - DAYS_BEFORE_MONTH[i - 1] + 1; solarInfo[2] = DAYS_BEFORE_MONTH[i] - DAYS_BEFORE_MONTH[i - 1] + 1;
else else
solarInfo[2] = DAYS_BEFORE_MONTH[i] - DAYS_BEFORE_MONTH[i - 1]; solarInfo[2] = DAYS_BEFORE_MONTH[i] - DAYS_BEFORE_MONTH[i - 1];
} else } else
solarInfo[2] = dayOffset; solarInfo[2] = dayOffset;
break; break;
} }
} }
solarInfo[0] = year; solarInfo[0] = year;
return solarInfo; return solarInfo;
} }
public static final int[] solarToLunar(int year, int month, int monthDay) { public static final int[] solarToLunar(int year, int month, int monthDay) {
int[] lunarDate = new int[4]; int[] lunarDate = new int[4];
int index = year - solar_1_1[0]; int index = year - solar_1_1[0];
int data = (year << 9) | (month << 5) int data = (year << 9) | (month << 5)
| (monthDay); | (monthDay);
int solar11 = 0; int solar11 = 0;
if (solar_1_1[index] > data) { if (solar_1_1[index] > data) {
index--; index--;
} }
solar11 = solar_1_1[index]; solar11 = solar_1_1[index];
int y = getBitInt(solar11, 12, 9); int y = getBitInt(solar11, 12, 9);
int m = getBitInt(solar11, 4, 5); int m = getBitInt(solar11, 4, 5);
int d = getBitInt(solar11, 5, 0); int d = getBitInt(solar11, 5, 0);
long offset = solarToInt(year, month, long offset = solarToInt(year, month,
monthDay) - solarToInt(y, m, d); monthDay) - solarToInt(y, m, d);
int days = lunar_month_days[index]; int days = lunar_month_days[index];
int leap = getBitInt(days, 4, 13); int leap = getBitInt(days, 4, 13);
int lunarY = index + solar_1_1[0]; int lunarY = index + solar_1_1[0];
int lunarM = 1; int lunarM = 1;
int lunarD = 1; int lunarD = 1;
offset += 1; offset += 1;
for (int i = 0; i < 13; i++) { for (int i = 0; i < 13; i++) {
int dm = getBitInt(days, 1, 12 - i) == 1 ? 30 : 29; int dm = getBitInt(days, 1, 12 - i) == 1 ? 30 : 29;
if (offset > dm) { if (offset > dm) {
lunarM++; lunarM++;
offset -= dm; offset -= dm;
} else { } else {
break; break;
} }
} }
lunarD = (int) (offset); lunarD = (int) (offset);
lunarDate[0] = lunarY; lunarDate[0] = lunarY;
lunarDate[1] = lunarM; lunarDate[1] = lunarM;
boolean isLeap = false; boolean isLeap = false;
if (leap != 0 && lunarM > leap) { if (leap != 0 && lunarM > leap) {
lunarDate[1] = lunarM - 1; lunarDate[1] = lunarM - 1;
if (lunarM == leap + 1) { if (lunarM == leap + 1) {
isLeap = true; isLeap = true;
} }
} }
lunarDate[2] = lunarD; lunarDate[2] = lunarD;
lunarDate[3] = isLeap ? 1 : 0; lunarDate[3] = isLeap ? 1 : 0;
//Log.i("----------->",year+"-"+month+"-"+monthDay+"====>"+lunarDate[0]+"-"+lunarDate[1]+"-"+lunarDate[2]+"-"+lunarDate[3]); //Log.i("----------->",year+"-"+month+"-"+monthDay+"====>"+lunarDate[0]+"-"+lunarDate[1]+"-"+lunarDate[2]+"-"+lunarDate[3]);
return lunarDate; return lunarDate;
} }
/** /**
* 将公历日期转换为农历日期,且标识是否是闰月 * 将公历日期转换为农历日期,且标识是否是闰月
* *
* @param year * @param year
* @param month * @param month
* @param monthDay * @param monthDay
* @return 返回公历日期对应的农历日期,year0,month1,day2,leap3 * @return 返回公历日期对应的农历日期,year0,month1,day2,leap3
* @deprecated 不准确 * @deprecated 不准确
*/ */
@Deprecated @Deprecated
public static final int[] solarToLunarDeprecated(int year, int month, int monthDay) { public static final int[] solarToLunarDeprecated(int year, int month, int monthDay) {
int[] lunarDate = new int[4]; int[] lunarDate = new int[4];
Date baseDate = new GregorianCalendar(1900, 0, 31).getTime(); Date baseDate = new GregorianCalendar(1900, 0, 31).getTime();
Date objDate = new GregorianCalendar(year, month - 1, monthDay).getTime(); Date objDate = new GregorianCalendar(year, month - 1, monthDay).getTime();
int offset = (int) ((objDate.getTime() - baseDate.getTime()) / 86400000L); int offset = (int) ((objDate.getTime() - baseDate.getTime()) / 86400000L);
// 用offset减去每农历年的天数计算当天是农历第几天 // 用offset减去每农历年的天数计算当天是农历第几天
// iYear最终结果是农历的年份, offset是当年的第几天 // iYear最终结果是农历的年份, offset是当年的第几天
int iYear, daysOfYear = 0; int iYear, daysOfYear = 0;
for (iYear = MIN_YEAR; iYear <= MAX_YEAR && offset > 0; iYear++) { for (iYear = MIN_YEAR; iYear <= MAX_YEAR && offset > 0; iYear++) {
daysOfYear = daysInLunarYear(iYear); daysOfYear = daysInLunarYear(iYear);
offset -= daysOfYear; offset -= daysOfYear;
} }
if (offset < 0) { if (offset < 0) {
offset += daysOfYear; offset += daysOfYear;
iYear--; iYear--;
} }
// 农历年份 // 农历年份
lunarDate[0] = iYear; lunarDate[0] = iYear;
int leapMonth = leapMonth(iYear); // 闰哪个月,1-12 int leapMonth = leapMonth(iYear); // 闰哪个月,1-12
boolean isLeap = false; boolean isLeap = false;
// 用当年的天数offset,逐个减去每月(农历)的天数,求出当天是本月的第几天 // 用当年的天数offset,逐个减去每月(农历)的天数,求出当天是本月的第几天
int iMonth, daysOfMonth = 0; int iMonth, daysOfMonth = 0;
for (iMonth = 1; iMonth <= 13 && offset > 0; iMonth++) { for (iMonth = 1; iMonth <= 13 && offset > 0; iMonth++) {
daysOfMonth = daysInLunarMonth(iYear, iMonth); daysOfMonth = daysInLunarMonth(iYear, iMonth);
offset -= daysOfMonth; offset -= daysOfMonth;
} }
// 当前月超过闰月,要校正 // 当前月超过闰月,要校正
if (leapMonth != 0 && iMonth > leapMonth) { if (leapMonth != 0 && iMonth > leapMonth) {
--iMonth; --iMonth;
Log.i("----------->", year + "-" + month + "-" + monthDay + "====>" + iMonth + "-" + leapMonth); Log.i("----------->", year + "-" + month + "-" + monthDay + "====>" + iMonth + "-" + leapMonth);
if (iMonth == leapMonth) { if (iMonth == leapMonth) {
isLeap = true; isLeap = true;
} }
} }
// offset小于0时,也要校正 // offset小于0时,也要校正
if (offset < 0) { if (offset < 0) {
offset += daysOfMonth; offset += daysOfMonth;
--iMonth; --iMonth;
} }
lunarDate[1] = iMonth; lunarDate[1] = iMonth;
lunarDate[2] = offset + 1; lunarDate[2] = offset + 1;
lunarDate[3] = isLeap ? 1 : 0; lunarDate[3] = isLeap ? 1 : 0;
//Log.i("----------->",year+"-"+month+"-"+monthDay+"====>"+lunarDate[0]+"-"+lunarDate[1]+"-"+lunarDate[2]+"-"+lunarDate[3]); //Log.i("----------->",year+"-"+month+"-"+monthDay+"====>"+lunarDate[0]+"-"+lunarDate[1]+"-"+lunarDate[2]+"-"+lunarDate[3]);
return lunarDate; return lunarDate;
} }
/** /**
* 传回农历year年month月的总天数 * 传回农历year年month月的总天数
* *
* @param year 要计算的年份 * @param year 要计算的年份
* @param month 要计算的月 * @param month 要计算的月
* @return 传回天数 * @return 传回天数
*/ */
final public static int daysInMonth(int year, int month) { final public static int daysInMonth(int year, int month) {
return daysInMonth(year, month, false); return daysInMonth(year, month, false);
} }
/** /**
* 传回农历year年month月的总天数 * 传回农历year年month月的总天数
* *
* @param year 要计算的年份 * @param year 要计算的年份
* @param month 要计算的月 * @param month 要计算的月
* @param leap 当月是否是闰月 * @param leap 当月是否是闰月
* @return 传回天数,如果闰月是错误的,返回0. * @return 传回天数,如果闰月是错误的,返回0.
*/ */
public static final int daysInMonth(int year, int month, boolean leap) { public static final int daysInMonth(int year, int month, boolean leap) {
int leapMonth = leapMonth(year); int leapMonth = leapMonth(year);
int offset = 0; int offset = 0;
// 如果本年有闰月且month大于闰月时,需要校正 // 如果本年有闰月且month大于闰月时,需要校正
if (leapMonth != 0 && month > leapMonth) { if (leapMonth != 0 && month > leapMonth) {
offset = 1; offset = 1;
} }
// 不考虑闰月 // 不考虑闰月
if (!leap) { if (!leap) {
return daysInLunarMonth(year, month + offset); return daysInLunarMonth(year, month + offset);
} else { } else {
// 传入的闰月是正确的月份 // 传入的闰月是正确的月份
if (leapMonth != 0 && leapMonth == month) { if (leapMonth != 0 && leapMonth == month) {
return daysInLunarMonth(year, month + 1); return daysInLunarMonth(year, month + 1);
} }
} }
return 0; return 0;
} }
/** /**
* 传回农历 year年的总天数 * 传回农历 year年的总天数
* *
* @param year 将要计算的年份 * @param year 将要计算的年份
* @return 返回传入年份的总天数 * @return 返回传入年份的总天数
*/ */
private static int daysInLunarYear(int year) { private static int daysInLunarYear(int year) {
int i, sum = 348; int i, sum = 348;
if (leapMonth(year) != 0) { if (leapMonth(year) != 0) {
sum = 377; sum = 377;
} }
int monthInfo = LUNAR_INFO[year - MIN_YEAR] & 0x0FFF80; int monthInfo = LUNAR_INFO[year - MIN_YEAR] & 0x0FFF80;
for (i = 0x80000; i > 0x7; i >>= 1) { for (i = 0x80000; i > 0x7; i >>= 1) {
if ((monthInfo & i) != 0) if ((monthInfo & i) != 0)
sum += 1; sum += 1;
} }
return sum; return sum;
} }
/** /**
* 传回农历 year年month月的总天数,总共有13个月包括闰月 * 传回农历 year年month月的总天数,总共有13个月包括闰月
* *
* @param year 将要计算的年份 * @param year 将要计算的年份
* @param month 将要计算的月份 * @param month 将要计算的月份
* @return 传回农历 year年month月的总天数 * @return 传回农历 year年month月的总天数
*/ */
private static int daysInLunarMonth(int year, int month) { private static int daysInLunarMonth(int year, int month) {
if ((LUNAR_INFO[year - MIN_YEAR] & (0x100000 >> month)) == 0) if ((LUNAR_INFO[year - MIN_YEAR] & (0x100000 >> month)) == 0)
return 29; return 29;
else else
return 30; return 30;
} }
/** /**
* 传回农历 year年闰哪个月 1-12 , 没闰传回 0 * 传回农历 year年闰哪个月 1-12 , 没闰传回 0
* *
* @param year 将要计算的年份 * @param year 将要计算的年份
* @return 传回农历 year年闰哪个月1-12, 没闰传回 0 * @return 传回农历 year年闰哪个月1-12, 没闰传回 0
*/ */
public static int leapMonth(int year) { public static int leapMonth(int year) {
return (int) ((LUNAR_INFO[year - MIN_YEAR] & 0xF00000)) >> 20; return (int) ((LUNAR_INFO[year - MIN_YEAR] & 0xF00000)) >> 20;
} }
private static int getBitInt(int data, int length, int shift) { private static int getBitInt(int data, int length, int shift) {
return (data & (((1 << length) - 1) << shift)) >> shift; return (data & (((1 << length) - 1) << shift)) >> shift;
} }
private static long solarToInt(int y, int m, int d) { private static long solarToInt(int y, int m, int d) {
m = (m + 9) % 12; m = (m + 9) % 12;
y = y - m / 10; y = y - m / 10;
return 365 * y + y / 4 - y / 100 + y / 400 + (m * 306 + 5) / 10 return 365 * y + y / 4 - y / 100 + y / 400 + (m * 306 + 5) / 10
+ (d - 1); + (d - 1);
} }
} }
File mode changed from 100755 to 100644
File mode changed from 100755 to 100644
...@@ -51,7 +51,7 @@ repositories { ...@@ -51,7 +51,7 @@ repositories {
} }
group 'com.qmai.android.print' group 'com.qmai.android.print'
version '1.1.45-SNAPSHOT' version '1.1.75-SNAPSHOT'
gradlePublish { gradlePublish {
......
package zs.qimai.com.printer2
import zs.qimai.com.printer2.manager.DeviceManager
/**
* @author: leiwang
* @time: 2021/12/9 5:15 下午
* @desc:
*/
data class PrintDeviceStatus(
val type: Int,
val deviceManager: DeviceManager?,
val transferLink: String? = null,
var others: String? = null
) {
fun getTypeDesc(): String {
return when (type) {
CONNECT_SUCCESS -> {
"打印机连接成功"
}
CONNECT_FAILED -> {
"打印机连接失败"
}
PRINT_ERROR -> {
"打印错误"
}
PRINT_CLOSED -> {
"打印机断开"
}
BLUETOOTH_CLOSED_RECEIVER -> {
"广播通知蓝牙打印机断开"
}
USER_CANCEL_CONNECTED -> "用户主动取消连接"
PRINT_WRITE ->{
"打印下发指令"
}
else -> {
""
}
}
}
companion object {
const val CONNECT_SUCCESS = 1
const val CONNECT_FAILED = 2
//打印异常
const val PRINT_ERROR = 3
const val PRINT_CLOSED = 4
//广播收到蓝牙打印机断开
const val BLUETOOTH_CLOSED_RECEIVER = 5
//用户主动取消连接
const val USER_CANCEL_CONNECTED = 6
//设备重复添加
const val PRINT_REPEAT_ADD = 7
//下发指令
const val PRINT_WRITE = 8
}
}
\ No newline at end of file
package zs.qimai.com.printer2.callback
import zs.qimai.com.printer2.PrintDeviceStatus
/**
* @author: leiwang
* @time: 2021/12/9 3:22 下午
* @desc:
*/
interface PrintStatusChangeCallBack {
fun onChange(printDeviceStatus: PrintDeviceStatus)
}
\ No newline at end of file
...@@ -73,7 +73,7 @@ open abstract class PrintExecutor { ...@@ -73,7 +73,7 @@ open abstract class PrintExecutor {
} }
} }
private fun goToPrint(it: DeviceManager, data: ByteArray) = GlobalScope.async(Dispatchers.IO) { fun goToPrint(it: DeviceManager, data: ByteArray) = GlobalScope.async(Dispatchers.IO) {
it.writeData(data) it.writeData(data)
} }
} }
\ No newline at end of file
...@@ -5,7 +5,12 @@ import android.app.Activity ...@@ -5,7 +5,12 @@ import android.app.Activity
import android.bluetooth.BluetoothAdapter import android.bluetooth.BluetoothAdapter
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.util.Log
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleObserver
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.OnLifecycleEvent
import com.tbruyelle.rxpermissions2.RxPermissions import com.tbruyelle.rxpermissions2.RxPermissions
import zs.qimai.com.printer2.receiverManager.RequestBlueToothListReceiverManager import zs.qimai.com.printer2.receiverManager.RequestBlueToothListReceiverManager
import zs.qimai.com.printer2.callback.BlueToothSearchCallBack import zs.qimai.com.printer2.callback.BlueToothSearchCallBack
...@@ -13,16 +18,30 @@ import zs.qimai.com.printer2.callback.BlueToothSearchCallBack ...@@ -13,16 +18,30 @@ import zs.qimai.com.printer2.callback.BlueToothSearchCallBack
/**** /****
* 没有界面的Fragment 管理获取定位权限,获取搜索到的蓝牙列表 * 没有界面的Fragment 管理获取定位权限,获取搜索到的蓝牙列表
* **/ * **/
class BlueToothListFragment : Fragment() { class BlueToothListFragment : Fragment(), LifecycleObserver {
private val TAG = "BlueToothListFragment" private val TAG = "BlueToothListFragment"
var activity: Activity? = null var activity: Activity? = null
private var mOuterLifecycleOwner: LifecycleOwner? = null
var callback: BlueToothSearchCallBack? = null var callback: BlueToothSearchCallBack? = null
override fun onAttach(context: Context?) { override fun onAttach(context: Context?) {
super.onAttach(context) super.onAttach(context)
activity = context as Activity? activity = context as Activity?
} }
fun getBlueToothList(callBack1: BlueToothSearchCallBack? = null) { private var mRequestBlueToothListReceiverManager: RequestBlueToothListReceiverManager? = null
//监听生命周期
@OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
fun onDestory() {
}
fun getBlueToothList(
lifecycleOwner: LifecycleOwner,
callBack1: BlueToothSearchCallBack? = null
) {
mOuterLifecycleOwner = lifecycleOwner
mOuterLifecycleOwner!!.lifecycle.addObserver(this)
this.callback = callBack1 this.callback = callBack1
this.callback?.onSearchStart() this.callback?.onSearchStart()
//先检查权限 蓝牙搜索必须要这些权限 //先检查权限 蓝牙搜索必须要这些权限
...@@ -47,10 +66,15 @@ class BlueToothListFragment : Fragment() { ...@@ -47,10 +66,15 @@ class BlueToothListFragment : Fragment() {
} }
/** /**
* 获取列表 * 获取蓝牙列表
* **/ * **/
private fun requestBlueList() { private fun requestBlueList() {
RequestBlueToothListReceiverManager(this).blueToothSearchCallBack = this.callback mRequestBlueToothListReceiverManager = RequestBlueToothListReceiverManager(this)
mOuterLifecycleOwner?.apply {
mRequestBlueToothListReceiverManager?.blueToothSearchCallBack =
this@BlueToothListFragment.callback
}
} }
/*** /***
...@@ -82,8 +106,16 @@ class BlueToothListFragment : Fragment() { ...@@ -82,8 +106,16 @@ class BlueToothListFragment : Fragment() {
} }
override fun onDestroy() { override fun onDestroy() {
callback = null
super.onDestroy() super.onDestroy()
activity = null activity = null
callback = null callback = null
} }
//取消蓝牙搜索
fun cancelSearch() {
callback = null
mRequestBlueToothListReceiverManager?.cancelSearch()
mRequestBlueToothListReceiverManager = null
}
} }
\ No newline at end of file
...@@ -4,6 +4,7 @@ import android.util.Log ...@@ -4,6 +4,7 @@ import android.util.Log
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity import androidx.fragment.app.FragmentActivity
import androidx.fragment.app.FragmentManager import androidx.fragment.app.FragmentManager
import androidx.lifecycle.LifecycleOwner
import zs.qimai.com.printer2.callback.BlueToothSearchCallBack import zs.qimai.com.printer2.callback.BlueToothSearchCallBack
/*** /***
...@@ -12,25 +13,32 @@ import zs.qimai.com.printer2.callback.BlueToothSearchCallBack ...@@ -12,25 +13,32 @@ import zs.qimai.com.printer2.callback.BlueToothSearchCallBack
class BlueToothLists { class BlueToothLists {
private val TAG = "BlueToothLists" private val TAG = "BlueToothLists"
var mBlueToothListFragment: BlueToothListFragment? = null var mBlueToothListFragment: BlueToothListFragment? = null
private var mOuterLifecycleOwner: LifecycleOwner? = null
constructor(fragmentActivity: FragmentActivity) { constructor(fragmentActivity: FragmentActivity) {
mOuterLifecycleOwner = fragmentActivity
mBlueToothListFragment = getFragment(fragmentActivity) mBlueToothListFragment = getFragment(fragmentActivity)
} }
constructor(fragment: Fragment) { constructor(fragment: Fragment) {
mOuterLifecycleOwner = fragment.viewLifecycleOwner
mBlueToothListFragment = getFragment(fragment) mBlueToothListFragment = getFragment(fragment)
} }
private fun getFragment(fragment: Fragment): BlueToothListFragment? { private fun getFragment(fragment: Fragment): BlueToothListFragment {
mOuterLifecycleOwner = fragment
return findFragment(fragment.childFragmentManager) return findFragment(fragment.childFragmentManager)
} }
private fun getFragment(fragmentActivity: FragmentActivity): BlueToothListFragment? { private fun getFragment(fragmentActivity: FragmentActivity): BlueToothListFragment {
return findFragment(fragmentActivity.supportFragmentManager) return findFragment(fragmentActivity.supportFragmentManager)
} }
private fun findFragment(supportFragmentManager: FragmentManager?): BlueToothListFragment? { private fun findFragment(supportFragmentManager: FragmentManager?): BlueToothListFragment {
var blueToothListFragment: BlueToothListFragment? = null var blueToothListFragment: BlueToothListFragment? = null
blueToothListFragment = blueToothListFragment =
...@@ -41,7 +49,7 @@ class BlueToothLists { ...@@ -41,7 +49,7 @@ class BlueToothLists {
} }
if (!blueToothListFragment.isAdded) { if (!blueToothListFragment.isAdded) {
supportFragmentManager?.beginTransaction()?.add(blueToothListFragment, TAG) supportFragmentManager?.beginTransaction()?.add(blueToothListFragment, TAG)
?.commitNow() ?.commitNowAllowingStateLoss()
} }
return blueToothListFragment return blueToothListFragment
} }
...@@ -50,6 +58,12 @@ class BlueToothLists { ...@@ -50,6 +58,12 @@ class BlueToothLists {
* 获取搜索到的蓝牙列表 * 获取搜索到的蓝牙列表
* ***/ * ***/
fun getBlueToothList(callBack: BlueToothSearchCallBack? = null) { fun getBlueToothList(callBack: BlueToothSearchCallBack? = null) {
mBlueToothListFragment?.getBlueToothList(callBack) mOuterLifecycleOwner?.let { mBlueToothListFragment?.getBlueToothList(it, callBack) }
} }
//取消蓝牙列表搜索
fun cancelSearchBlueToothList() {
mBlueToothListFragment?.cancelSearch()
}
} }
\ No newline at end of file
...@@ -8,17 +8,14 @@ import android.util.Log ...@@ -8,17 +8,14 @@ import android.util.Log
import androidx.annotation.RequiresApi import androidx.annotation.RequiresApi
import io.reactivex.disposables.Disposable import io.reactivex.disposables.Disposable
import kotlinx.coroutines.* import kotlinx.coroutines.*
import zs.qimai.com.printer2.PrintDeviceStatus
import zs.qimai.com.printer2.printStatus.PrintStatusCallBack import zs.qimai.com.printer2.printStatus.PrintStatusCallBack
import zs.qimai.com.printer2.printStatus.PrinterStatusUtils import zs.qimai.com.printer2.printStatus.PrinterStatusUtils
import zs.qimai.com.printer2.utils.PrintFormat import zs.qimai.com.printer2.utils.PrintFormat
import zs.qimai.com.printer2.utils.PrintManagerUtils import zs.qimai.com.printer2.utils.PrintManagerUtils
import zs.qimai.com.printer2.utils.PrintManagerUtils.Companion.ACTIVITY_NOT_FOUND
import zs.qimai.com.printer2.utils.PrintManagerUtils.Companion.BOND_NONE
import zs.qimai.com.printer2.utils.PrintManagerUtils.Companion.PRINT_MODE_NOT_SUPPORT import zs.qimai.com.printer2.utils.PrintManagerUtils.Companion.PRINT_MODE_NOT_SUPPORT
import zs.qimai.com.printer2.callback.BlueToothPwdCallBack
import zs.qimai.com.printer2.callback.OnBtConnectCallBack import zs.qimai.com.printer2.callback.OnBtConnectCallBack
import zs.qimai.com.printer2.lifecycle.ActivityManagers import zs.qimai.com.printer2.utils.PrintManagerUtils.Companion.DEVICE_NOT_FIND
import zs.qimai.com.printer2.receiverManager.BlueToothPwdReceiverManager
import java.io.IOException import java.io.IOException
import java.lang.Exception import java.lang.Exception
import java.util.* import java.util.*
...@@ -38,6 +35,17 @@ class BlueDeviceManager(override var mType: Int = BT) : DeviceManager() { ...@@ -38,6 +35,17 @@ class BlueDeviceManager(override var mType: Int = BT) : DeviceManager() {
@RequiresApi(Build.VERSION_CODES.KITKAT) @RequiresApi(Build.VERSION_CODES.KITKAT)
override fun openPort() { override fun openPort() {
mOnBtConnectCallBack?.onConnectStart() mOnBtConnectCallBack?.onConnectStart()
//判断蓝牙是否开启
if (!BluetoothAdapter.getDefaultAdapter().isEnabled) {
mOnBtConnectCallBack?.onConnectError(
PrintManagerUtils.BT_DEVICE_ALREAD_CONN,
"蓝牙未打开"
)
//回调完成 赋值为Null
mOnBtConnectCallBack = null
return
}
//先判断当前设备是否已经保存过 //先判断当前设备是否已经保存过
if (DeviceManagerUtils.getInstance().isContainerBtDevice(address!!)) { if (DeviceManagerUtils.getInstance().isContainerBtDevice(address!!)) {
Log.d(TAG, "usbConnect: this device is bind") Log.d(TAG, "usbConnect: this device is bind")
...@@ -45,54 +53,181 @@ class BlueDeviceManager(override var mType: Int = BT) : DeviceManager() { ...@@ -45,54 +53,181 @@ class BlueDeviceManager(override var mType: Int = BT) : DeviceManager() {
PrintManagerUtils.BT_DEVICE_ALREAD_CONN, PrintManagerUtils.BT_DEVICE_ALREAD_CONN,
"该设备已经连接过" "该设备已经连接过"
) )
//回调完成 赋值为Null
mOnBtConnectCallBack = null
return return
} }
mBlueAdapter = BluetoothAdapter.getDefaultAdapter() GlobalScope.launch(Dispatchers.IO) {
mBlueAdapter?.let { mBlueAdapter = BluetoothAdapter.getDefaultAdapter()
it.cancelDiscovery() mBlueAdapter?.let {
mBlueToothDevice = it.getRemoteDevice(address) it.cancelDiscovery()
} try {
?: mOnBtConnectCallBack?.onConnectError( mBlueToothDevice = it.getRemoteDevice(address)
PrintManagerUtils.HARDWARE_NOT_SUPPORT, "该硬件不支持蓝牙" } catch (e: IllegalArgumentException) {
) //获取不到设备
mBlueToothDevice?.let { it -> mBlueToothDevice = null
//这里判断是否需要弹出密码输入弹窗 Log.d(TAG, "openPort: e= ${e.toString()}")
var isVerify: Boolean = it.createBond() }
//如果不需要就直接去连接 同时是未配对状态 if (mBlueToothDevice == null) {
if (isVerify && (it.bondState != BluetoothDevice.BOND_BONDED)) { mOnBtConnectCallBack?.onConnectError(DEVICE_NOT_FIND, "根据地址获取不到设备")
//获取顶部Activity //回调完成 赋值为Null
ActivityManagers.getInstance().getTopActivity()?.let { activity -> mOnBtConnectCallBack = null
//去广播中等待状态 return@launch
BlueToothPwdReceiverManager(activity) }
.callBack = object : BlueToothPwdCallBack { } ?: kotlin.run {
override fun onInputStatus(state: Int) { withContext(Dispatchers.Main) {
Log.d(TAG, "onInputSuccess: state= $state") mOnBtConnectCallBack?.onConnectError(
when (state) { PrintManagerUtils.HARDWARE_NOT_SUPPORT, "该硬件不支持蓝牙"
BluetoothDevice.BOND_BONDING -> { )
Log.d(TAG, "onInputSuccess: BOND_BONDING") //回调完成 赋值为Null
} mOnBtConnectCallBack = null
BluetoothDevice.BOND_BONDED -> { }
Log.d(TAG, "onInputSuccess: BOND_BONDED") }
//密码验证成功,继续去连接 mBlueToothDevice?.let { it ->
connectBt(it) try {
} Log.d(TAG, "connectBt: begin ")
BluetoothDevice.BOND_NONE -> { // mBluetoothSocket = it.createInsecureRfcommSocketToServiceRecord(uuid)
Log.d(TAG, "onInputSuccess: BOND_NONE") mBluetoothSocket = it.createRfcommSocketToServiceRecord(uuid)
mOnBtConnectCallBack?.onConnectError(BOND_NONE, "弹窗异常或者密码错误") if (mBluetoothSocket != null) {
//mOnBtConnectCallBack?.onConnectError(BOND_NONE, "弹窗异常或者密码错误") try {
//目前已知 Android 7版本这里必须要单独开个线程才能连接 fuck
Log.d(TAG, "connectBt: thread = ${Thread.currentThread().id}")
if (mBluetoothSocket!!.isConnected) {
mBluetoothSocket!!.close()
}
mBluetoothSocket!!.connect()
mOutPutStream = mBluetoothSocket!!.outputStream
mInPutStream = mBluetoothSocket!!.inputStream
//到这里说明配对并连接成功 判断打印机模式
PrinterStatusUtils(this@BlueDeviceManager).apply {
mPrintStatusCallBack = object : PrintStatusCallBack {
override fun searchResult(status: Int?) {
mPrintMode = status ?: ESC
mStatus = true
// name = it.productName
name = it.name
address = it.address
deviceId = it.address
/* if (name.isNullOrEmpty()) {
name = if (mPrintMode == ESC) "小票打印机" else "杯贴打印机"
}*/
DeviceManagerUtils.getInstance()
.addDevice(this@BlueDeviceManager)
mOnBtConnectCallBack?.onConnectSuccess(this@BlueDeviceManager)
//回调通知
val printDeviceStatus = PrintDeviceStatus(
PrintDeviceStatus.CONNECT_SUCCESS,
this@BlueDeviceManager,
Log.getStackTraceString(Throwable())
)
DeviceManagerUtils.getInstance()
.notifyPrintStatusChangeCallBack(printDeviceStatus)
//回调完成 赋值为Null
mOnBtConnectCallBack = null
//mUsbPrintConnCallBack?.onConnSucess(this@UsbDeviceManager)
connectSuccessNotify()
Log.d(TAG, "searchResult: status= $status")
/* status?.let {
handleConnectSuccess(status)
} ?: handleUnknownStatus()*/
}
} }
queryStatus()
} }
} catch (e: IOException) {
Log.d(TAG, "connectBt: e= $e")
name = it.name
address = it.address
deviceId = it.address
connectFailedNotify(e.message)
withContext(Dispatchers.Main) {
mOnBtConnectCallBack?.onConnectError(
PrintManagerUtils.SOCKET_ERROR,
"socket连接异常"
)
//回调完成 赋值为Null
mOnBtConnectCallBack = null
}
closePort()
mStatus = false
}
} else {
connectFailedNotify("获取不到socket")
withContext(Dispatchers.Main) {
mOnBtConnectCallBack?.onConnectError(
PrintManagerUtils.SOCKET_NOT_FOUND,
"获取不到socket"
)
//回调完成 赋值为Null
mOnBtConnectCallBack = null
} }
} }
} ?: mOnBtConnectCallBack?.onConnectError(ACTIVITY_NOT_FOUND, "获取不到Activity实例")
} else { } catch (e: Exception) {
connectBt(it) connectFailedNotify(e.message)
Log.d(TAG, "openPort: e= ${e}")
withContext(Dispatchers.Main) {
mOnBtConnectCallBack?.onConnectError(
PrintManagerUtils.SOCKET_ERROR,
"${e.message}"
)
//回调完成 赋值为Null
mOnBtConnectCallBack = null
}
}
// connectBt(it)
// return
/* if (it.bondState == BluetoothDevice.BOND_BONDED){
connectBt(it)
}else{
//这里判断是否需要弹出密码输入弹窗
var isVerify: Boolean = it.createBond()
//如果不需要就直接去连接 同时是未配对状态
if (isVerify) {
//获取顶部Activity
ActivityManagers.getInstance().getTopActivity()?.let { activity ->
//去广播中等待状态
BlueToothPwdReceiverManager(activity)
.callBack = object : BlueToothPwdCallBack {
override fun onInputStatus(state: Int) {
Log.d(TAG, "onInputSuccess: state= $state")
when (state) {
BluetoothDevice.BOND_BONDING -> {
Log.d(TAG, "onInputSuccess: BOND_BONDING")
}
BluetoothDevice.BOND_BONDED -> {
Log.d(TAG, "onInputSuccess: BOND_BONDED")
//密码验证成功,继续去连接
connectBt(it)
}
BluetoothDevice.BOND_NONE -> {
Log.d(TAG, "onInputSuccess: BOND_NONE")
mOnBtConnectCallBack?.onConnectError(BOND_NONE, "弹窗异常或者密码错误")
//mOnBtConnectCallBack?.onConnectError(BOND_NONE, "弹窗异常或者密码错误")
}
}
}
}
} ?: mOnBtConnectCallBack?.onConnectError(ACTIVITY_NOT_FOUND, "获取不到Activity实例")
} else {
connectBt(it)
}
}*/
//mOnBtConnectCallBack?.onConnectError(DEVICE_NOT_FIND, "根据地址获取不到设备")
} }
//mOnBtConnectCallBack?.onConnectError(DEVICE_NOT_FIND, "根据地址获取不到设备")
} }
} }
override fun closePort() { override fun closePort() {
printCloseNotify("")
DeviceManagerUtils.getInstance().removeDevice(this@BlueDeviceManager) DeviceManagerUtils.getInstance().removeDevice(this@BlueDeviceManager)
mStatus = false mStatus = false
job?.cancel() job?.cancel()
...@@ -145,67 +280,7 @@ class BlueDeviceManager(override var mType: Int = BT) : DeviceManager() { ...@@ -145,67 +280,7 @@ class BlueDeviceManager(override var mType: Int = BT) : DeviceManager() {
* ***/ * ***/
private fun connectBt(it: BluetoothDevice) { private fun connectBt(it: BluetoothDevice) {
GlobalScope.launch(Dispatchers.Main) { GlobalScope.launch(Dispatchers.Main) {
try {
withContext(Dispatchers.IO) {
mBluetoothSocket = it.createRfcommSocketToServiceRecord(uuid)
}
if (mBluetoothSocket != null) {
try {
withContext(Dispatchers.IO) {
if (mBluetoothSocket!!.isConnected) {
mBluetoothSocket!!.close()
}
mBluetoothSocket!!.connect()
}
mOutPutStream = mBluetoothSocket!!.outputStream
mInPutStream = mBluetoothSocket!!.inputStream
//到这里说明配对并连接成功 判断打印机模式
PrinterStatusUtils(this@BlueDeviceManager).apply {
mPrintStatusCallBack = object : PrintStatusCallBack {
override fun searchResult(status: Int?) {
mPrintMode = status ?: ESC
mStatus = true
// name = it.productName
name = it.name
address = it.address
deviceId = it.address
/* if (name.isNullOrEmpty()) {
name = if (mPrintMode == ESC) "小票打印机" else "杯贴打印机"
}*/
DeviceManagerUtils.getInstance()
.addDevice(this@BlueDeviceManager)
mOnBtConnectCallBack?.onConnectSuccess(this@BlueDeviceManager)
//mUsbPrintConnCallBack?.onConnSucess(this@UsbDeviceManager)
Log.d(TAG, "searchResult: status= $status")
/* status?.let {
handleConnectSuccess(status)
} ?: handleUnknownStatus()*/
}
}
queryStatus()
}
} catch (e: IOException) {
Log.d(TAG, "connectBt: e= $e")
mOnBtConnectCallBack?.onConnectError(
PrintManagerUtils.SOCKET_ERROR,
"socket连接异常"
)
closePort()
mStatus = false
}
} else {
mOnBtConnectCallBack?.onConnectError(
PrintManagerUtils.SOCKET_NOT_FOUND,
"获取不到socket"
)
}
} catch (e: Exception) {
mOnBtConnectCallBack?.onConnectError(
PrintManagerUtils.SOCKET_ERROR,
"${e.message}"
)
}
} }
} }
...@@ -217,8 +292,15 @@ class BlueDeviceManager(override var mType: Int = BT) : DeviceManager() { ...@@ -217,8 +292,15 @@ class BlueDeviceManager(override var mType: Int = BT) : DeviceManager() {
} }
override fun writeData(bytes: ByteArray) { override fun writeData(bytes: ByteArray) {
this.mOutPutStream?.write(bytes) try {
this.mOutPutStream?.flush() val printString = String(bytes)
printWriteNotify("指令大小: ${bytes.size} 指令内容: $printString socket状态: ${this.mBluetoothSocket?.isConnected} 线程:${Thread.currentThread().name}")
this.mOutPutStream?.write(bytes)
this.mOutPutStream?.flush()
} catch (e: Exception) {
printErrorNotify(e.message)
throw e
}
} }
/**** /****
...@@ -300,9 +382,9 @@ class BlueDeviceManager(override var mType: Int = BT) : DeviceManager() { ...@@ -300,9 +382,9 @@ class BlueDeviceManager(override var mType: Int = BT) : DeviceManager() {
return false return false
} }
override fun toString(): String { /* override fun toString(): String {
return "BlueDeviceManager(mType=$mType, len=$len, mBlueToothDevice=$mBlueToothDevice, mBlueAdapter=$mBlueAdapter, mBluetoothSocket=$mBluetoothSocket, mOnBtConnectCallBack=$mOnBtConnectCallBack, TAG='$TAG', disposable=$disposable, uuid=$uuid, job=$job)" return "BlueDeviceManager(蓝牙, 打印机名称 = $name 打印机模式 = ${if (mPrintMode == ESC) "小票打印机" else "杯贴打印机"} 票据大小 = ${mPrintSize} deviceId = ${deviceId} " + super.toString()
} }*/
} }
\ No newline at end of file
package zs.qimai.com.printer2.manager package zs.qimai.com.printer2.manager
import android.util.Log
import zs.qimai.com.printer2.PrintDeviceStatus
import java.io.InputStream import java.io.InputStream
import java.io.OutputStream import java.io.OutputStream
import java.lang.StringBuilder
/**** /****
* 设备基类 * 设备基类
...@@ -16,24 +19,33 @@ abstract class DeviceManager { ...@@ -16,24 +19,33 @@ abstract class DeviceManager {
var mPrintMode: Int? = null var mPrintMode: Int? = null
abstract var mType: Int abstract var mType: Int
var name: String? = null var name: String? = null
@Deprecated("后续用 mPrintSize 代替打印杯贴大小") @Deprecated("后续用 mPrintSize 代替打印杯贴大小")
var maxLineChars: Int = 80 var maxLineChars: Int = 80
var mPrintSize:Int = -1 var mPrintSize: Int = -1
companion object { companion object {
//小票模式 //小票模式
val ESC = 1 val ESC = 1
//标签模式 //标签模式
val TSC = 2 val TSC = 2
//蓝牙 //蓝牙
val BT = 1 val BT = 1
//usb //usb
val USB = 2 val USB = 2
//wifi(网线) //wifi(网线)
const val WIFI = 3 const val WIFI = 3
//支持小票尺寸 //支持小票尺寸
const val TSC_40_30 = 10 const val TSC_40_30 = 10
const val TSC_40_40 = 11 const val TSC_40_40 = 11
const val TSC_60_40 = 12 const val TSC_60_40 = 12
const val TSC_40_60 = 13
} }
//连接 //连接
...@@ -57,5 +69,75 @@ abstract class DeviceManager { ...@@ -57,5 +69,75 @@ abstract class DeviceManager {
return deviceId.hashCode() return deviceId.hashCode()
} }
override fun toString(): String {
val str =
"打印机名称: $name\n 打印机连接方式: ${
when (mType) {
BT -> "蓝牙"
WIFI -> "网口"
USB -> "USB"
else -> "未知"
}
} \n 打印机模式: ${if (mPrintMode == ESC) "小票打印机" else "杯贴打印机"}\n deviceId: $deviceId\n address: $address\n 票据大小: $mPrintSize"
return str
}
fun connectSuccessNotify() {
//回调通知
val printDeviceStatus = PrintDeviceStatus(
PrintDeviceStatus.CONNECT_SUCCESS,
this,
Log.getStackTraceString(Throwable())
)
DeviceManagerUtils.getInstance()
.notifyPrintStatusChangeCallBack(printDeviceStatus)
}
fun connectFailedNotify(msg: String?) {
//回调通知
val printDeviceStatus = PrintDeviceStatus(
PrintDeviceStatus.CONNECT_FAILED,
this, Log.getStackTraceString(Throwable()),
"错误原因: $msg"
)
DeviceManagerUtils.getInstance()
.notifyPrintStatusChangeCallBack(printDeviceStatus)
}
//打印异常
fun printErrorNotify(msg: String?) {
//回调通知
val printDeviceStatus = PrintDeviceStatus(
PrintDeviceStatus.PRINT_ERROR,
this, Log.getStackTraceString(Throwable()),
"错误原因: $msg"
)
DeviceManagerUtils.getInstance()
.notifyPrintStatusChangeCallBack(printDeviceStatus)
}
//打印机关闭
fun printCloseNotify(msg: String) {
//回调通知
val printDeviceStatus = PrintDeviceStatus(
PrintDeviceStatus.PRINT_CLOSED,
this, Log.getStackTraceString(Throwable()),
"错误原因: $msg"
)
DeviceManagerUtils.getInstance()
.notifyPrintStatusChangeCallBack(printDeviceStatus)
}
fun printWriteNotify(msg: String?) {
//回调通知
val printDeviceStatus = PrintDeviceStatus(
PrintDeviceStatus.PRINT_WRITE,
this, Log.getStackTraceString(Throwable()),
"msg: $msg"
)
DeviceManagerUtils.getInstance()
.notifyPrintStatusChangeCallBack(printDeviceStatus)
}
} }
\ No newline at end of file
...@@ -3,9 +3,15 @@ package zs.qimai.com.printer2.manager ...@@ -3,9 +3,15 @@ package zs.qimai.com.printer2.manager
import android.bluetooth.BluetoothDevice import android.bluetooth.BluetoothDevice
import android.hardware.usb.UsbDevice import android.hardware.usb.UsbDevice
import android.util.Log import android.util.Log
import zs.qimai.com.printer2.PrintDeviceStatus
import zs.qimai.com.printer2.PrintDeviceStatus.Companion.CONNECT_FAILED
import zs.qimai.com.printer2.PrintDeviceStatus.Companion.PRINT_CLOSED
import zs.qimai.com.printer2.PrintDeviceStatus.Companion.PRINT_REPEAT_ADD
import zs.qimai.com.printer2.callback.PrintConnOrDisCallBack import zs.qimai.com.printer2.callback.PrintConnOrDisCallBack
import zs.qimai.com.printer2.callback.PrintStatusChangeCallBack
import zs.qimai.com.printer2.manager.DeviceManager.Companion.BT import zs.qimai.com.printer2.manager.DeviceManager.Companion.BT
import zs.qimai.com.printer2.manager.DeviceManager.Companion.USB import zs.qimai.com.printer2.manager.DeviceManager.Companion.USB
import zs.qimai.com.printer2.utils.PrintManagerUtils
import java.util.concurrent.CopyOnWriteArrayList import java.util.concurrent.CopyOnWriteArrayList
/***** /*****
...@@ -16,12 +22,24 @@ import java.util.concurrent.CopyOnWriteArrayList ...@@ -16,12 +22,24 @@ import java.util.concurrent.CopyOnWriteArrayList
class DeviceManagerUtils { class DeviceManagerUtils {
private val TAG = "DeviceManagerUtils" private val TAG = "DeviceManagerUtils"
private var mCallBacklist: CopyOnWriteArrayList<PrintConnOrDisCallBack> = CopyOnWriteArrayList() private var mCallBackList: CopyOnWriteArrayList<PrintConnOrDisCallBack> = CopyOnWriteArrayList()
var lists: CopyOnWriteArrayList<DeviceManager> = CopyOnWriteArrayList()
//打印机状态监听
private var mPrintStatusChangeCallBackList: CopyOnWriteArrayList<PrintStatusChangeCallBack> =
CopyOnWriteArrayList()
var lists: CopyOnWriteArrayList<DeviceManager> = CopyOnWriteArrayList()
internal fun addDevice(deviceManager: DeviceManager?) { internal fun addDevice(deviceManager: DeviceManager?) {
deviceManager?.let { deviceManager?.let {
//避免重复添加
if (lists.contains(it)) {
val printDeviceStatus = PrintDeviceStatus(
PRINT_REPEAT_ADD, it, Log.getStackTraceString(
Throwable()
)
)
DeviceManagerUtils.getInstance().notifyPrintStatusChangeCallBack(printDeviceStatus)
return
}
lists.add(deviceManager) lists.add(deviceManager)
notifyaddObserver(it) notifyaddObserver(it)
} }
...@@ -45,11 +63,18 @@ class DeviceManagerUtils { ...@@ -45,11 +63,18 @@ class DeviceManagerUtils {
// lists.remove(deviceManager) // lists.remove(deviceManager)
} }
internal fun removeUsbDevice(device: UsbDevice) { private fun removeUsbDevice(device: UsbDevice) {
lists.forEach { lists.forEach {
if (it is UsbDeviceManager) { if (it is UsbDeviceManager) {
if (it.usbDevice == device) { if (it.usbDevice == device) {
//回调通知
val printDeviceStatus = PrintDeviceStatus(
PRINT_CLOSED, it,
Log.getStackTraceString(Throwable())
)
getInstance().notifyPrintStatusChangeCallBack(printDeviceStatus)
//closePort()会清除所有配置项,并调用removeDevice() //closePort()会清除所有配置项,并调用removeDevice()
it.closePort() it.closePort()
return@forEach return@forEach
...@@ -58,7 +83,6 @@ class DeviceManagerUtils { ...@@ -58,7 +83,6 @@ class DeviceManagerUtils {
} }
} }
} }
} }
...@@ -99,7 +123,7 @@ class DeviceManagerUtils { ...@@ -99,7 +123,7 @@ class DeviceManagerUtils {
} }
internal fun removeUsbDevice(address: String) { internal fun removeUsbDevice(address: String) {
var device = getUsbDevice(address) val device = getUsbDevice(address)
device?.closePort() device?.closePort()
} }
...@@ -140,7 +164,7 @@ class DeviceManagerUtils { ...@@ -140,7 +164,7 @@ class DeviceManagerUtils {
* 添加状态监听 * 添加状态监听
* **/ * **/
fun addConnectStatusCallBack(callBack: PrintConnOrDisCallBack) { fun addConnectStatusCallBack(callBack: PrintConnOrDisCallBack) {
mCallBacklist.add(callBack) mCallBackList.add(callBack)
} }
/**** /****
...@@ -148,7 +172,37 @@ class DeviceManagerUtils { ...@@ -148,7 +172,37 @@ class DeviceManagerUtils {
* **/ * **/
fun removeConnectStatusCallBack(callBack: PrintConnOrDisCallBack) { fun removeConnectStatusCallBack(callBack: PrintConnOrDisCallBack) {
mCallBacklist.remove(callBack) mCallBackList.remove(callBack)
}
/***
* 添加状态监听
* **/
fun addPrintStatusCallBack(callBack: PrintStatusChangeCallBack) {
mPrintStatusChangeCallBackList.add(callBack)
}
/****
* 移除状态监听
* **/
fun removePrintStatusCallBack(callBack: PrintStatusChangeCallBack) {
mPrintStatusChangeCallBackList.remove(callBack)
}
fun notifyPrintDeviceStatusOnDisConnectObserver(
printDeviceStatus: PrintDeviceStatus
) {
mPrintStatusChangeCallBackList.forEach {
it.onChange(printDeviceStatus)
}
}
fun notifyPrintStatusChangeCallBack(printDeviceStatus: PrintDeviceStatus) {
mPrintStatusChangeCallBackList.forEach {
it.onChange(printDeviceStatus)
}
} }
...@@ -157,7 +211,7 @@ class DeviceManagerUtils { ...@@ -157,7 +211,7 @@ class DeviceManagerUtils {
* ***/ * ***/
private fun notifyRemoveObserver(deviceManager: DeviceManager) { private fun notifyRemoveObserver(deviceManager: DeviceManager) {
Log.d(TAG, "notifyRemoveObserver: ${Log.getStackTraceString(Throwable())}") Log.d(TAG, "notifyRemoveObserver: ${Log.getStackTraceString(Throwable())}")
mCallBacklist?.forEach { mCallBackList.forEach {
it.onDisPrint(deviceManager) it.onDisPrint(deviceManager)
} }
} }
...@@ -167,7 +221,7 @@ class DeviceManagerUtils { ...@@ -167,7 +221,7 @@ class DeviceManagerUtils {
* ***/ * ***/
private fun notifyaddObserver(deviceManager: DeviceManager) { private fun notifyaddObserver(deviceManager: DeviceManager) {
// Log.d(TAG, "notifyaddObserver: ${Log.getStackTraceString(Throwable())}") // Log.d(TAG, "notifyaddObserver: ${Log.getStackTraceString(Throwable())}")
mCallBacklist?.forEach { mCallBackList.forEach {
it.onConectPrint(deviceManager) it.onConectPrint(deviceManager)
} }
} }
...@@ -297,11 +351,30 @@ class DeviceManagerUtils { ...@@ -297,11 +351,30 @@ class DeviceManagerUtils {
return null return null
} }
val device = lists.find { it.deviceId == deviceId } val device = lists.find { it.deviceId == deviceId }
return if (device != null) (device as DeviceManager) else null return device
} }
/***
* 通过广播获取新的设备
*/
fun receiveNewUsbDevice(
address: String,
usbDevice: UsbDevice
) {
//如果是设置为自动连接 则自动去连接
if (PrintManagerUtils.getInstance().detachUsbDeviceAutoConn) {
PrintManagerUtils.getInstance().usbConnect(address, usbDevice)
} else {
//不做处理
}
}
/***
* 通过广播监听到usb设备移除
*/
fun receiveDeviceDetach(device: UsbDevice) {
removeUsbDevice(device)
}
} }
\ No newline at end of file
...@@ -2,6 +2,10 @@ package zs.qimai.com.printer2.manager ...@@ -2,6 +2,10 @@ package zs.qimai.com.printer2.manager
import android.hardware.usb.* import android.hardware.usb.*
import android.util.Log import android.util.Log
import zs.qimai.com.printer2.PrintDeviceStatus
import zs.qimai.com.printer2.PrintDeviceStatus.Companion.CONNECT_SUCCESS
import zs.qimai.com.printer2.PrintDeviceStatus.Companion.PRINT_CLOSED
import zs.qimai.com.printer2.PrintDeviceStatus.Companion.PRINT_ERROR
import zs.qimai.com.printer2.callback.UsbPrintConnCallBack import zs.qimai.com.printer2.callback.UsbPrintConnCallBack
import zs.qimai.com.printer2.printStatus.PrintStatusCallBack import zs.qimai.com.printer2.printStatus.PrintStatusCallBack
import zs.qimai.com.printer2.printStatus.PrinterStatusUtils import zs.qimai.com.printer2.printStatus.PrinterStatusUtils
...@@ -59,11 +63,16 @@ class UsbDeviceManager(override var mType: Int = USB) : DeviceManager() { ...@@ -59,11 +63,16 @@ class UsbDeviceManager(override var mType: Int = USB) : DeviceManager() {
} }
DeviceManagerUtils.getInstance().addDevice(this@UsbDeviceManager) DeviceManagerUtils.getInstance().addDevice(this@UsbDeviceManager)
mUsbPrintConnCallBack?.onConnSucess(this@UsbDeviceManager) mUsbPrintConnCallBack?.onConnSucess(this@UsbDeviceManager)
//回调通知
connectSuccessNotify()
} }
} }
}.queryStatus() }.queryStatus()
} else { } else {
connectFailedNotify("连接失败")
mUsbPrintConnCallBack?.onConnFailed(USB_CONN_FAILED, "连接失败") mUsbPrintConnCallBack?.onConnFailed(USB_CONN_FAILED, "连接失败")
} }
...@@ -72,6 +81,8 @@ class UsbDeviceManager(override var mType: Int = USB) : DeviceManager() { ...@@ -72,6 +81,8 @@ class UsbDeviceManager(override var mType: Int = USB) : DeviceManager() {
} }
override fun closePort() { override fun closePort() {
//回调通知
printCloseNotify("")
mStatus = false mStatus = false
mUsbPrintConnCallBack = null mUsbPrintConnCallBack = null
DeviceManagerUtils.getInstance().removeDevice(this@UsbDeviceManager) DeviceManagerUtils.getInstance().removeDevice(this@UsbDeviceManager)
...@@ -104,7 +115,7 @@ class UsbDeviceManager(override var mType: Int = USB) : DeviceManager() { ...@@ -104,7 +115,7 @@ class UsbDeviceManager(override var mType: Int = USB) : DeviceManager() {
for (i in data.indices) { for (i in data.indices) {
if (sendData.size >= 1024) { if (sendData.size >= 1024) {
Log.e( Log.e(
TAG, TAG,
"i = " + i + "\tsendData size -> " + sendData.size + "\tdata size -> " + data.size "i = " + i + "\tsendData size -> " + sendData.size + "\tdata size -> " + data.size
) )
result += mmConnection!!.bulkTransfer( result += mmConnection!!.bulkTransfer(
...@@ -131,7 +142,8 @@ class UsbDeviceManager(override var mType: Int = USB) : DeviceManager() { ...@@ -131,7 +142,8 @@ class UsbDeviceManager(override var mType: Int = USB) : DeviceManager() {
Log.d(TAG, "send success") Log.d(TAG, "send success")
} }
} catch (var7: Exception) { } catch (var7: Exception) {
Log.d(TAG, "Exception occured while sending data immediately: " + var7.message) //打印异常通知
printErrorNotify(var7.message)
} }
} }
...@@ -149,9 +161,9 @@ class UsbDeviceManager(override var mType: Int = USB) : DeviceManager() { ...@@ -149,9 +161,9 @@ class UsbDeviceManager(override var mType: Int = USB) : DeviceManager() {
return address?.hashCode() ?: 0 + usbDevice.hashCode() return address?.hashCode() ?: 0 + usbDevice.hashCode()
}*/ }*/
override fun toString(): String { /* override fun toString(): String {
return "UsbDeviceManager(mType=$mType, TAG='$TAG', usbDevice=$usbDevice, usbManager=$usbManager, mmConnection=$mmConnection, mmIntf=$mmIntf, mmEndIn=$mmEndIn, mmEndOut=$mmEndOut, mUsbPrintConnCallBack=$mUsbPrintConnCallBack)" return "usb打印机: UsbDeviceManager(mType=$mType, TAG='$TAG', usbDevice=$usbDevice, usbManager=$usbManager, mmConnection=$mmConnection, mmIntf=$mmIntf, mmEndIn=$mmEndIn, mmEndOut=$mmEndOut, mUsbPrintConnCallBack=$mUsbPrintConnCallBack)" + super.toString()
} }*/
override fun equals(other: Any?): Boolean { override fun equals(other: Any?): Boolean {
if (this === other) return true if (this === other) return true
...@@ -165,6 +177,7 @@ class UsbDeviceManager(override var mType: Int = USB) : DeviceManager() { ...@@ -165,6 +177,7 @@ class UsbDeviceManager(override var mType: Int = USB) : DeviceManager() {
override fun hashCode(): Int { override fun hashCode(): Int {
return deviceId.hashCode() return deviceId.hashCode()
} }
protected fun convertVectorByteToBytes(data: Vector<Byte>): ByteArray? { protected fun convertVectorByteToBytes(data: Vector<Byte>): ByteArray? {
val sendData = ByteArray(data.size) val sendData = ByteArray(data.size)
if (data.size > 0) { if (data.size > 0) {
......
...@@ -49,7 +49,10 @@ class WifiDeviceManager(var ip: String, var port: Int = 9100) : DeviceManager() ...@@ -49,7 +49,10 @@ class WifiDeviceManager(var ip: String, var port: Int = 9100) : DeviceManager()
DeviceManagerUtils.getInstance().addDevice(this@WifiDeviceManager) DeviceManagerUtils.getInstance().addDevice(this@WifiDeviceManager)
Log.d(TAG, "searchResult: WifiDeviceManager success") Log.d(TAG, "searchResult: WifiDeviceManager success")
mConnectCallBack?.onConnectSuccess(this@WifiDeviceManager) mConnectCallBack?.onConnectSuccess(this@WifiDeviceManager)
timeDetachStatus() //ip连接的很奇怪,长时间不操作,会自动化断开 https://www.jianshu.com/p/0f44f9e3f604 按这个教程,每次打印完断开端口连接,打印的时候重新连接在打印
mPort.closePort()
// timeDetachStatus()
connectSuccessNotify()
} }
} }
}.queryStatus() }.queryStatus()
...@@ -58,10 +61,10 @@ class WifiDeviceManager(var ip: String, var port: Int = 9100) : DeviceManager() ...@@ -58,10 +61,10 @@ class WifiDeviceManager(var ip: String, var port: Int = 9100) : DeviceManager()
DeviceManagerUtils.getInstance().removeDevice(this@WifiDeviceManager) DeviceManagerUtils.getInstance().removeDevice(this@WifiDeviceManager)
mConnectCallBack?.onConnectError(0, "连接失败") mConnectCallBack?.onConnectError(0, "连接失败")
} }
connectFailedNotify("连接失败")
} }
} }
} }
override fun closePort() { override fun closePort() {
...@@ -85,11 +88,23 @@ class WifiDeviceManager(var ip: String, var port: Int = 9100) : DeviceManager() ...@@ -85,11 +88,23 @@ class WifiDeviceManager(var ip: String, var port: Int = 9100) : DeviceManager()
} }
override fun writeData(bytes: ByteArray) { override fun writeData(bytes: ByteArray) {
//每次打印先连接端口,打印失败后在断开
try { try {
mOutPutStream?.write(bytes) val isOpenPort = mPort.openPort()
if (isOpenPort) {
mOutPutStream = mPort.outputStream
mInPutStream = mPort.inputStream
this@WifiDeviceManager.deviceId = ip
mOutPutStream?.write(bytes)
mOutPutStream?.flush()
mPort.closePort()
}
} catch (e: IOException) { } catch (e: IOException) {
printErrorNotify(e.message)
Log.d(TAG, "writeData: e=$e") Log.d(TAG, "writeData: e=$e")
} catch (e: SocketException) { } catch (e: SocketException) {
printErrorNotify(e.message)
Log.d(TAG, "writeData: e = $e") Log.d(TAG, "writeData: e = $e")
} }
} }
......
...@@ -7,6 +7,9 @@ import android.content.BroadcastReceiver ...@@ -7,6 +7,9 @@ import android.content.BroadcastReceiver
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.util.Log import android.util.Log
import zs.qimai.com.printer2.PrintDeviceStatus
import zs.qimai.com.printer2.PrintDeviceStatus.Companion.BLUETOOTH_CLOSED_RECEIVER
import zs.qimai.com.printer2.manager.BlueDeviceManager
import zs.qimai.com.printer2.manager.DeviceManagerUtils import zs.qimai.com.printer2.manager.DeviceManagerUtils
class BlueToothStatusReceiver : BroadcastReceiver() { class BlueToothStatusReceiver : BroadcastReceiver() {
...@@ -20,10 +23,26 @@ class BlueToothStatusReceiver : BroadcastReceiver() { ...@@ -20,10 +23,26 @@ class BlueToothStatusReceiver : BroadcastReceiver() {
} }
BluetoothDevice.ACTION_ACL_DISCONNECTED -> { BluetoothDevice.ACTION_ACL_DISCONNECTED -> {
var bluetoothDevice = intent.getParcelableExtra<BluetoothDevice?>(EXTRA_DEVICE) val bluetoothDevice = intent.getParcelableExtra<BluetoothDevice?>(EXTRA_DEVICE)
bluetoothDevice?.let { bluetoothDevice?.let { bluetoothDevice ->
//如果检测到设备已经断开连接,就从保存的集合中删除 //如果检测到设备已经断开连接,就从保存的集合中删除
DeviceManagerUtils.getInstance().removeBtDevice(it) var deviceManager = DeviceManagerUtils.getInstance()
.getDevice { it.deviceId == bluetoothDevice.address }
if (deviceManager == null) {
deviceManager = BlueDeviceManager().apply {
address = bluetoothDevice.address
name = bluetoothDevice.name
}
}
val printDeviceStatus = PrintDeviceStatus(
BLUETOOTH_CLOSED_RECEIVER, deviceManager, Log.getStackTraceString(
Throwable()
)
)
DeviceManagerUtils.getInstance()
.notifyPrintStatusChangeCallBack(printDeviceStatus)
DeviceManagerUtils.getInstance().removeBtDevice(bluetoothDevice)
} }
} }
BluetoothAdapter.ACTION_STATE_CHANGED -> { BluetoothAdapter.ACTION_STATE_CHANGED -> {
......
...@@ -9,6 +9,10 @@ import android.util.Log ...@@ -9,6 +9,10 @@ import android.util.Log
import zs.qimai.com.printer2.utils.PrintManagerUtils import zs.qimai.com.printer2.utils.PrintManagerUtils
import zs.qimai.com.printer2.manager.DeviceManagerUtils import zs.qimai.com.printer2.manager.DeviceManagerUtils
/***
* usb探测监听广播
*/
class UsbDetachReceiver : BroadcastReceiver() { class UsbDetachReceiver : BroadcastReceiver() {
private val TAG = "UsbDetachReceiver" private val TAG = "UsbDetachReceiver"
override fun onReceive(context: Context, intent: Intent) { override fun onReceive(context: Context, intent: Intent) {
...@@ -17,11 +21,12 @@ class UsbDetachReceiver : BroadcastReceiver() { ...@@ -17,11 +21,12 @@ class UsbDetachReceiver : BroadcastReceiver() {
//如果存在移除usb设备,则需要去连接设备管理中移除 //如果存在移除usb设备,则需要去连接设备管理中移除
if (intent.action == UsbManager.ACTION_USB_DEVICE_DETACHED) { if (intent.action == UsbManager.ACTION_USB_DEVICE_DETACHED) {
val mUsbDevice = intent.getParcelableExtra<UsbDevice?>(UsbManager.EXTRA_DEVICE) val mUsbDevice = intent.getParcelableExtra<UsbDevice?>(UsbManager.EXTRA_DEVICE)
Log.d(TAG, "onReceive: ACTION_USB_DEVICE_DETACHED mUsbDevice = ${mUsbDevice}") Log.d(TAG, "onReceive: ACTION_USB_DEVICE_DETACHED mUsbDevice = $mUsbDevice")
//判断是打印机类型 //判断是打印机类型
mUsbDevice?.let { mUsbDevice?.let {
if (mUsbDevice?.interfaceCount != 0 && mUsbDevice?.getInterface(0)?.interfaceClass == 7) { if (mUsbDevice.interfaceCount != 0 && mUsbDevice.getInterface(0).interfaceClass == 7) {
DeviceManagerUtils.getInstance().removeUsbDevice(it) //DeviceManagerUtils.getInstance().removeUsbDevice(it)
DeviceManagerUtils.getInstance().receiveDeviceDetach(it)
} }
} }
...@@ -29,9 +34,12 @@ class UsbDetachReceiver : BroadcastReceiver() { ...@@ -29,9 +34,12 @@ class UsbDetachReceiver : BroadcastReceiver() {
if (intent.action == UsbManager.ACTION_USB_DEVICE_ATTACHED) { if (intent.action == UsbManager.ACTION_USB_DEVICE_ATTACHED) {
val mUsbDevice = intent.getParcelableExtra<UsbDevice?>(UsbManager.EXTRA_DEVICE) val mUsbDevice = intent.getParcelableExtra<UsbDevice?>(UsbManager.EXTRA_DEVICE)
//判断是打印机类型 //判断是打印机类型
if (mUsbDevice?.interfaceCount != 0 && mUsbDevice?.getInterface(0)?.interfaceClass == 7 && PrintManagerUtils.getInstance().detachUsbDeviceAutoConn) { if (mUsbDevice?.interfaceCount != 0 && mUsbDevice?.getInterface(0)?.interfaceClass == 7) {
PrintManagerUtils.getInstance().usbConnect(mUsbDevice?.deviceName!!, mUsbDevice) DeviceManagerUtils.getInstance().receiveNewUsbDevice(mUsbDevice.deviceName, mUsbDevice)
} }
/* if (mUsbDevice?.interfaceCount != 0 && mUsbDevice?.getInterface(0)?.interfaceClass == 7 && PrintManagerUtils.getInstance().detachUsbDeviceAutoConn) {
PrintManagerUtils.getInstance().usbConnect(mUsbDevice.deviceName, mUsbDevice)
}*/
} }
} }
} }
...@@ -11,6 +11,7 @@ import androidx.fragment.app.Fragment ...@@ -11,6 +11,7 @@ import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity import androidx.fragment.app.FragmentActivity
import androidx.lifecycle.Lifecycle import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleObserver import androidx.lifecycle.LifecycleObserver
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.OnLifecycleEvent import androidx.lifecycle.OnLifecycleEvent
import zs.qimai.com.printer2.callback.BlueToothSearchCallBack import zs.qimai.com.printer2.callback.BlueToothSearchCallBack
...@@ -22,6 +23,7 @@ class RequestBlueToothListReceiverManager : LifecycleObserver { ...@@ -22,6 +23,7 @@ class RequestBlueToothListReceiverManager : LifecycleObserver {
var activity: FragmentActivity? = null var activity: FragmentActivity? = null
var receiver = Receiver() var receiver = Receiver()
var blueToothSearchCallBack: BlueToothSearchCallBack? = null var blueToothSearchCallBack: BlueToothSearchCallBack? = null
private var mOuterLifecycleOwner: LifecycleOwner? = null
var intentFilter = IntentFilter(BluetoothDevice.ACTION_FOUND) var intentFilter = IntentFilter(BluetoothDevice.ACTION_FOUND)
var bluetoothAdapter: BluetoothAdapter? = null var bluetoothAdapter: BluetoothAdapter? = null
...@@ -29,9 +31,13 @@ class RequestBlueToothListReceiverManager : LifecycleObserver { ...@@ -29,9 +31,13 @@ class RequestBlueToothListReceiverManager : LifecycleObserver {
@OnLifecycleEvent(Lifecycle.Event.ON_DESTROY) @OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
fun onDestory() { fun onDestory() {
Log.d(TAG, "onDestory: ") Log.d(TAG, "onDestory: ")
cancelSearch()
}
fun cancelSearch() {
blueToothSearchCallBack = null
this.activity?.unregisterReceiver(receiver) this.activity?.unregisterReceiver(receiver)
this.activity = null this.activity = null
blueToothSearchCallBack = null
bluetoothAdapter?.cancelDiscovery() bluetoothAdapter?.cancelDiscovery()
bluetoothAdapter = null bluetoothAdapter = null
} }
...@@ -42,19 +48,24 @@ class RequestBlueToothListReceiverManager : LifecycleObserver { ...@@ -42,19 +48,24 @@ class RequestBlueToothListReceiverManager : LifecycleObserver {
init() init()
} }
constructor(fragment: Fragment) { constructor(outerLifecycleOwner: LifecycleOwner) {
this.activity = fragment.activity this.mOuterLifecycleOwner = outerLifecycleOwner
if (mOuterLifecycleOwner is FragmentActivity) {
this.activity = mOuterLifecycleOwner as FragmentActivity
} else if (mOuterLifecycleOwner is Fragment) {
this.activity = (mOuterLifecycleOwner as Fragment).requireActivity()
}
Log.d(TAG, "activity2= :$activity ") Log.d(TAG, "activity2= :$activity ")
init() init()
} }
private fun init() { private fun init() {
this.activity?.lifecycle?.addObserver(this) this.mOuterLifecycleOwner?.lifecycle?.addObserver(this)
intentFilter.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED) intentFilter.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED)
intentFilter.addAction(BluetoothAdapter.ACTION_DISCOVERY_STARTED) intentFilter.addAction(BluetoothAdapter.ACTION_DISCOVERY_STARTED)
intentFilter.addAction(BluetoothAdapter.ACTION_DISCOVERY_FINISHED) intentFilter.addAction(BluetoothAdapter.ACTION_DISCOVERY_FINISHED)
this?.activity?.registerReceiver(receiver, intentFilter) this.activity?.registerReceiver(receiver, intentFilter)
bluetoothAdapter = BluetoothAdapter.getDefaultAdapter() bluetoothAdapter = BluetoothAdapter.getDefaultAdapter()
bluetoothAdapter?.cancelDiscovery() bluetoothAdapter?.cancelDiscovery()
bluetoothAdapter?.startDiscovery() bluetoothAdapter?.startDiscovery()
...@@ -69,7 +80,13 @@ class RequestBlueToothListReceiverManager : LifecycleObserver { ...@@ -69,7 +80,13 @@ class RequestBlueToothListReceiverManager : LifecycleObserver {
intent?.getParcelableExtra<BluetoothDevice>(BluetoothDevice.EXTRA_DEVICE) intent?.getParcelableExtra<BluetoothDevice>(BluetoothDevice.EXTRA_DEVICE)
bluetoothDevice?.let { bluetoothDevice?.let {
if (!it.name.isNullOrEmpty()) { if (!it.name.isNullOrEmpty()) {
blueToothSearchCallBack?.onBlueToothFound(bluetoothDevice) Log.d(
TAG,
"onReceive:bluetoothDevice.address= ${bluetoothDevice.address} name= ${bluetoothDevice.name}"
)
if (BluetoothAdapter.checkBluetoothAddress(bluetoothDevice.address)) {
blueToothSearchCallBack?.onBlueToothFound(bluetoothDevice)
}
/*Log.d( /*Log.d(
TAG, TAG,
"onReceive: bluetoothDevice name= " + bluetoothDevice!!.name + " address= " "onReceive: bluetoothDevice name= " + bluetoothDevice!!.name + " address= "
...@@ -95,4 +112,6 @@ class RequestBlueToothListReceiverManager : LifecycleObserver { ...@@ -95,4 +112,6 @@ class RequestBlueToothListReceiverManager : LifecycleObserver {
} }
} }
} }
\ No newline at end of file
...@@ -10,6 +10,8 @@ import android.hardware.usb.UsbManager ...@@ -10,6 +10,8 @@ import android.hardware.usb.UsbManager
import android.os.Build import android.os.Build
import android.util.Log import android.util.Log
import androidx.annotation.RequiresApi import androidx.annotation.RequiresApi
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity
import zs.qimai.com.printer2.callback.* import zs.qimai.com.printer2.callback.*
import zs.qimai.com.printer2.fragment.BlueToothLists import zs.qimai.com.printer2.fragment.BlueToothLists
import zs.qimai.com.printer2.lifecycle.ActivityManagers import zs.qimai.com.printer2.lifecycle.ActivityManagers
...@@ -19,6 +21,7 @@ import zs.qimai.com.printer2.manager.DeviceManagerUtils ...@@ -19,6 +21,7 @@ import zs.qimai.com.printer2.manager.DeviceManagerUtils
import zs.qimai.com.printer2.manager.UsbDeviceManager import zs.qimai.com.printer2.manager.UsbDeviceManager
import zs.qimai.com.printer2.manager.WifiDeviceManager import zs.qimai.com.printer2.manager.WifiDeviceManager
import zs.qimai.com.printer2.receiverManager.UsbRqPermissionReceiverManager import zs.qimai.com.printer2.receiverManager.UsbRqPermissionReceiverManager
import androidx.core.content.ContextCompat.getSystemService
/**** /****
...@@ -47,13 +50,31 @@ class PrintManagerUtils { ...@@ -47,13 +50,31 @@ class PrintManagerUtils {
throw RuntimeException("请在Application 先调用init()") throw RuntimeException("请在Application 先调用init()")
} }
} }
//获取蓝牙列表
fun getSearchBlueToothList(
fragmentActivity: FragmentActivity,
callBack: BlueToothSearchCallBack? = null
) {
checkIsInit()
BlueToothLists(fragmentActivity).getBlueToothList(callBack)
?: callBack?.searchFailed(ACTIVITY_NOT_FOUND, "获取不到Activity")
}
fun getSearchBlueToothList(callBack: BlueToothSearchCallBack? = null) { //获取蓝牙列表
fun getSearchBlueToothList(fragment: Fragment, callBack: BlueToothSearchCallBack? = null) {
checkIsInit() checkIsInit()
var topActivity = ActivityManagers.getInstance().getTopActivity() BlueToothLists(fragment).getBlueToothList(callBack)
topActivity?.let { ?: callBack?.searchFailed(ACTIVITY_NOT_FOUND, "获取不到Activity")
BlueToothLists(it).getBlueToothList(callBack) }
} ?: callBack?.searchFailed(ACTIVITY_NOT_FOUND, "获取不到Activity")
//取消蓝牙搜索
fun cancelSearchToothList(fragmentActivity: FragmentActivity) {
BlueToothLists(fragmentActivity).cancelSearchBlueToothList()
}
//取消蓝牙搜索
fun cancelSearchToothList(fragment: Fragment) {
BlueToothLists(fragment).cancelSearchBlueToothList()
} }
//蓝牙连接 //蓝牙连接
...@@ -63,6 +84,8 @@ class PrintManagerUtils { ...@@ -63,6 +84,8 @@ class PrintManagerUtils {
address: String, address: String,
mOnBtConnectCallBack: OnBtConnectCallBack? = null mOnBtConnectCallBack: OnBtConnectCallBack? = null
) { ) {
Log.d(TAG, "btConnect: ip= $address ${Log.getStackTraceString(Throwable())}")
checkIsInit() checkIsInit()
val bluetoothAdapter = BluetoothAdapter.getDefaultAdapter() val bluetoothAdapter = BluetoothAdapter.getDefaultAdapter()
bluetoothAdapter?.cancelDiscovery() bluetoothAdapter?.cancelDiscovery()
...@@ -110,12 +133,31 @@ class PrintManagerUtils { ...@@ -110,12 +133,31 @@ class PrintManagerUtils {
} }
} }
//usb根据地址进行连接
fun usbConnect(address: String, mUsbPrintConnCallBack: UsbPrintConnCallBack? = null) {
//生成usbDevice
ActivityManagers.getInstance().getTopActivity()?.apply {
val manager = this.getSystemService(Context.USB_SERVICE) as UsbManager
val list = manager.deviceList
val device: UsbDevice? = list[address]
if (device != null) {
usbConnect(address, device, mUsbPrintConnCallBack)
} else {
mUsbPrintConnCallBack?.onConnFailed(USB_DEVICE_INFO_NOT_FOUND, "获取不到该设备")
}
}
}
@JvmOverloads @JvmOverloads
fun usbConnect( fun usbConnect(
address: String, address: String,
usbDevice: UsbDevice, usbDevice: UsbDevice,
mUsbPrintConnCallBack: UsbPrintConnCallBack? = null mUsbPrintConnCallBack: UsbPrintConnCallBack? = null
) { ) {
Log.d(TAG, "usbConnect: ip= ${address} ${Log.getStackTraceString(Throwable())}")
checkIsInit() checkIsInit()
//连接开始 //连接开始
mUsbPrintConnCallBack?.onConnStart() mUsbPrintConnCallBack?.onConnStart()
...@@ -250,6 +292,7 @@ class PrintManagerUtils { ...@@ -250,6 +292,7 @@ class PrintManagerUtils {
//网口连接 //网口连接
fun wifiConnect(ip: String, callback: OnConnectCallBack? = null, port: Int = 9100) { fun wifiConnect(ip: String, callback: OnConnectCallBack? = null, port: Int = 9100) {
Log.d(TAG, "wifiConnect: ip= ${ip} ${Log.getStackTraceString(Throwable())}")
val status = DeviceManagerUtils.getInstance().isContainerDevice { val status = DeviceManagerUtils.getInstance().isContainerDevice {
it is WifiDeviceManager && it.ip == ip it is WifiDeviceManager && it.ip == ip
} }
...@@ -281,4 +324,24 @@ class PrintManagerUtils { ...@@ -281,4 +324,24 @@ class PrintManagerUtils {
btConnect("00:11:22:33:44:55", onBtConnectCallBack) btConnect("00:11:22:33:44:55", onBtConnectCallBack)
} }
} }
/**
* 获取已经插入的usb打印机设备
*/
fun getAccessUsbList(): Map<String, UsbDevice>? {
val manager = application!!.getSystemService(Context.USB_SERVICE) as UsbManager?
val deviceMap = manager?.deviceList
if (!deviceMap.isNullOrEmpty()) {
val deviceList = HashMap<String, UsbDevice>()
deviceMap.forEach {
if (it.value.interfaceCount != 0 && it.value.getInterface(0).interfaceClass == 7) {
deviceList[it.key] = it.value
}
}
return deviceList
}
return null
}
} }
\ No newline at end of file
function print1(){
android.printString('hello orld');
var html = `<p>储值编号:{{orderNo}}</p><p>储值时间:{{createTime}}</p><p>操作人员:{{operator}}</p><hr /> <p>活动名称{{ruleName}}</p> <p><span style="text-align:left;width:50%;">支付方式:</span> <span style="text-align:right;width:50%;">{{payDesc}}</span></p> <p> <span style="text-align:left;width:50%;">储值金额:</span><span style="text-align:right;width:50%;">{{rechargeAmount}}</span></p> <p> <span style="text-align:left;font-size:24px;width:50%;">账户余额: </span><span style="text-align:right;font-size:36px;width:50%;">{{balance}} </span></p> <hr /> <p>会员:{{buyerName}}</p> <p>电话:{{mobile}}</p><br /><p style="text-align:center">扫码关注店铺公众号</p><p style="text-align:center">本次储值赠送白金卡,5张10元优惠券</p>`
// let encoding = require('./encoding')
// let TextEncoder = encoding.TextEncoder
// let template = require('./art-template')
var render = template.render(html, {
orderNo: 'SDJL1293780123',
createTime: '2020-10-09 23:23:23',
operator: '王雷',
ruleName: '限时折扣活动',
payDesc: '微信',
rechargeAmount: '¥12.22',
balance: '¥100.22',
buyerName: '小雷',
mobile: '19899999999'
});
// var htmlparser2 = require('./htmlparser2')
var byteArray = [27, 64, 10]
var uint8array = new TextEncoder('gbk', { NONSTANDARD_allowLegacyEncoding: true })
var handler = new htmlparser2.DomHandler(function(error, dom) {
dom.forEach(item => {
if (item.name) {
var children = item.children
if (children.length == 1 && children[0].type == 'text') {
// console.log(uint8array.encode('值编号:SDJL1293780123') + '')
byteArray = byteArray.concat(Array.from(uint8array.encode(children[0].data)))
}
}
})
android.print12(byteArray)
// console.log(byteArray + '')
})
var parser = new htmlparser2.Parser(handler)
// var parser = new htmlparser2.Parser({
// onopentag(name, attributes) {
// console.log(name)
// console.log(attributes)
// // html = html + '<' + name + '>'
// // if (name === 'span') {
// // name = 'p'
// // }
// },
// // ontext(text) {
// // console.log("-->", text);
// // // html = html + '{{value}}'
// // },
// // onclosetag(tagname) {
// // // if (tagname === "script") {
// // // console.log("That's it?!");
// // // }
// // // html = html + '</' + tagname + '>'
// // console.log(tagname, '结尾')
// // },
// });
parser.write(render);
parser.end();
// function transform() {
// return render
// }
// console.log(transform())
// let encoding = require('./encoding')
// let TextEncoder = encoding.TextEncoder
// console.log(TextEncoder)
var uint8array = new TextEncoder(
'gbk', { NONSTANDARD_allowLegacyEncoding: true }).encode('值编号:SDJL1293780123');
// function encodeArrayOfStrings(strings) {
// var encoder, encoded, len, i, bytes, view, offset;
// encoder = new TextEncoder();
// encoded = [];
// len = Uint16Array.BYTES_PER_ELEMENT;
// for (i = 0; i < strings.length; i += 1) {
// len += Uint16Array.BYTES_PER_ELEMENT;
// encoded[i] = new TextEncoder().encode(strings[i]);
// len += encoded[i].byteLength;
// }
// bytes = new Uint16Array(len);
// view = new DataView(bytes.buffer);
// offset = 0;
// view.setUint32(offset, strings.length);
// offset += Uint16Array.BYTES_PER_ELEMENT;
// for (i = 0; i < encoded.length; i += 1) {
// len = encoded[i].byteLength;
// view.setUint32(offset, len);
// offset += Uint16Array.BYTES_PER_ELEMENT;
// bytes.set(encoded[i], offset);
// offset += len;
// }
// return bytes;
// }
// var aa = encodeArrayOfStrings('hello world')
}
\ No newline at end of file
...@@ -22,7 +22,7 @@ android { ...@@ -22,7 +22,7 @@ android {
} }
group 'com.qmai.android.sdk' group 'com.qmai.android.sdk'
version '2.1.3-SNAPSHOT' version '2.1.5-SNAPSHOT'
gradlePublish { gradlePublish {
......
...@@ -19,7 +19,7 @@ ...@@ -19,7 +19,7 @@
For example: For example:
<version value="1" /> <version value="1" />
--> -->
<version value="2" /> <version value="3" />
<!-- <!--
Define your models in the list with mapping tag, LitePal will Define your models in the list with mapping tag, LitePal will
...@@ -34,6 +34,7 @@ ...@@ -34,6 +34,7 @@
<list> <list>
<mapping class="com.zhimai.websocket.bean.MessageSqlBean" /> <mapping class="com.zhimai.websocket.bean.MessageSqlBean" />
<mapping class="com.zhimai.websocket.bean.SocketOrderInfoBean"/> <mapping class="com.zhimai.websocket.bean.SocketOrderInfoBean"/>
<mapping class="com.zhimai.websocket.bean.SocketCheckMessage"/>
</list> </list>
<!-- <!--
......
package com.zhimai.websocket.bean
import org.litepal.crud.LitePalSupport
/**
* @author: 高英祥
* @github:
* @time: 2021/9/13 15:02
* @desc:
* @doc:
*/
class SocketCheckMessage : LitePalSupport() {
//消息唯一id
var messageId:String? = null
//订单号
var orderNo:String? = null
//设备id
var deviceId:String? = null
//设备的tag
var tag:String? = null
//门店id
var mutableId:String? = null
//门店名称
var mutableName:String? = null
//店铺名称:
var shopName:String? = null
//店铺id
var shopId:String? = null
//当前版本
var version:String? = null
//订单类容
var orderDetails:String? = null
//订单时间
var orderInTime:String? = null
//打印机状态
var printerState:String? = null
//是否打印
var isPrint:String? = null
//是否播报
var isPlay:String? = null
}
\ No newline at end of file
package com.zhimai.websocket.listener; package com.zhimai.websocket.listener;
/** /**
* @author: 高英祥 * @author: 高英祥
* @github: * @github:
* @time: 2021/7/9 15:12 * @time: 2021/7/9 15:12
* @desc: * @desc:
* @doc: * @doc:
*/ */
public interface SocketTimerMessageListener { public interface SocketTimerMessageListener {
void timerMessage(); void timerMessage();
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment