| @@ -0,0 +1,20 @@ | |||
| gen/ | |||
| bin/ | |||
| Thumbs.db | |||
| proguard/dump.txt | |||
| proguard/mapping.txt | |||
| proguard/seeds.txt | |||
| proguard/usage.txt | |||
| log.txt | |||
| .idea/ | |||
| *.iml | |||
| .gradle/ | |||
| build/ | |||
| proguard/ | |||
| local.properties | |||
| gradle.properties | |||
| gradle/ | |||
| gradlew | |||
| gradlew.bat | |||
| @@ -0,0 +1 @@ | |||
| /build | |||
| @@ -0,0 +1,58 @@ | |||
| apply plugin: 'com.jfrog.bintray' | |||
| version = libraryVersion | |||
| if (project.hasProperty("android")) { // Android libraries | |||
| task sourcesJar(type: Jar) { | |||
| classifier = 'sources' | |||
| from android.sourceSets.main.java.srcDirs | |||
| } | |||
| task javadoc(type: Javadoc) { | |||
| source = android.sourceSets.main.java.srcDirs | |||
| classpath += project.files(android.getBootClasspath().join(File.pathSeparator)) | |||
| } | |||
| } else { // Java libraries | |||
| task sourcesJar(type: Jar, dependsOn: classes) { | |||
| classifier = 'sources' | |||
| from sourceSets.main.allSource | |||
| } | |||
| } | |||
| task javadocJar(type: Jar, dependsOn: javadoc) { | |||
| classifier = 'javadoc' | |||
| from javadoc.destinationDir | |||
| } | |||
| artifacts { | |||
| archives javadocJar | |||
| archives sourcesJar | |||
| } | |||
| // Bintray | |||
| Properties properties = new Properties() | |||
| properties.load(project.rootProject.file('gradle.properties').newDataInputStream()) | |||
| bintray { | |||
| user = properties.getProperty("bintray.user") | |||
| key = properties.getProperty("bintray.apikey") | |||
| configurations = ['archives'] | |||
| pkg { | |||
| repo = bintrayRepo | |||
| name = bintrayName | |||
| desc = libraryDescription | |||
| websiteUrl = siteUrl | |||
| vcsUrl = gitUrl | |||
| licenses = allLicenses | |||
| publish = true | |||
| publicDownloadNumbers = true | |||
| version { | |||
| desc = libraryDescription | |||
| gpg { | |||
| sign = true //Determines whether to GPG sign the files. The default is false | |||
| passphrase = properties.getProperty("bintray.gpg.password") //Optional. The passphrase for GPG signing' | |||
| } | |||
| } | |||
| } | |||
| } | |||
| @@ -0,0 +1,29 @@ | |||
| apply plugin: 'com.android.library' | |||
| android { | |||
| compileSdkVersion versions.compileSdk | |||
| defaultConfig { | |||
| minSdkVersion versions.minSdk | |||
| targetSdkVersion versions.targetSdk | |||
| versionCode 1 | |||
| versionName "1.0" | |||
| } | |||
| buildTypes { | |||
| release { | |||
| minifyEnabled false | |||
| consumerProguardFiles 'proguard-rules.pro' | |||
| } | |||
| } | |||
| } | |||
| dependencies { | |||
| compileOnly deps.rx.java | |||
| compileOnly deps.rx.java2 | |||
| } | |||
| apply from: 'properties.gradle' | |||
| apply from: 'install.gradle' | |||
| apply from: 'bintray.gradle' | |||
| @@ -0,0 +1,44 @@ | |||
| apply plugin: 'com.github.dcendents.android-maven' | |||
| // Maven Group ID for the artifact | |||
| group = publishedGroupId | |||
| install { | |||
| repositories.mavenInstaller { | |||
| // This generates POM.xml with proper parameters | |||
| pom { | |||
| project { | |||
| packaging 'aar' | |||
| groupId publishedGroupId | |||
| artifactId artifact | |||
| // Add your description here | |||
| name libraryName | |||
| description libraryDescription | |||
| url siteUrl | |||
| // Set your license | |||
| licenses { | |||
| license { | |||
| name licenseName | |||
| url licenseUrl | |||
| } | |||
| } | |||
| developers { | |||
| developer { | |||
| id developerId | |||
| name developerName | |||
| email developerEmail | |||
| } | |||
| } | |||
| scm { | |||
| connection gitUrl | |||
| developerConnection gitUrl | |||
| url siteUrl | |||
| } | |||
| } | |||
| } | |||
| } | |||
| } | |||
| @@ -0,0 +1,29 @@ | |||
| # Add project specific ProGuard rules here. | |||
| # You can control the set of applied configuration files using the | |||
| # proguardFiles setting in build.gradle. | |||
| # | |||
| # For more details, see | |||
| # http://developer.android.com/guide/developing/tools/proguard.html | |||
| # If your project uses WebView with JS, uncomment the following | |||
| # and specify the fully qualified class name to the JavaScript interface | |||
| # class: | |||
| #-keepclassmembers class fqcn.of.javascript.interface.for.webview { | |||
| # public *; | |||
| #} | |||
| # Uncomment this to preserve the line number information for | |||
| # debugging stack traces. | |||
| #-keepattributes SourceFile,LineNumberTable | |||
| # If you keep the line number information, uncomment this to | |||
| # hide the original source file name. | |||
| #-renamesourcefileattribute SourceFile | |||
| -dontwarn com.codezjx.andlinker.adapter.** | |||
| -keep class * implements com.codezjx.andlinker.SuperParcelable { | |||
| public void readFromParcel(android.os.Parcel); | |||
| } | |||
| -keep @com.codezjx.andlinker.annotation.RemoteInterface class * { | |||
| <methods>; | |||
| } | |||
| @@ -0,0 +1,23 @@ | |||
| ext { | |||
| bintrayRepo = 'maven' | |||
| bintrayName = 'and-linker' | |||
| publishedGroupId = 'com.codezjx.library' | |||
| libraryName = 'AndLinker' | |||
| artifact = 'andlinker' | |||
| libraryDescription = 'AndLinker is a IPC library for Android, which combines the features of AIDL and Retrofit. Allows IPC call seamlessly compose with rxjava and rxjava2 call adapters.' | |||
| siteUrl = 'https://github.com/codezjx/AndLinker' | |||
| gitUrl = 'https://github.com/codezjx/AndLinker.git' | |||
| libraryVersion = '0.7.1' | |||
| developerId = 'codezjx' | |||
| developerName = 'codezjx' | |||
| developerEmail = 'code.zjx@gmail.com' | |||
| licenseName = 'The Apache Software License, Version 2.0' | |||
| licenseUrl = 'http://www.apache.org/licenses/LICENSE-2.0.txt' | |||
| allLicenses = ["Apache-2.0"] | |||
| } | |||
| @@ -0,0 +1,2 @@ | |||
| <manifest xmlns:android="http://schemas.android.com/apk/res/android" | |||
| package="com.codezjx.andlinker"/> | |||
| @@ -0,0 +1,295 @@ | |||
| package com.codezjx.andlinker; | |||
| import android.content.ComponentName; | |||
| import android.content.Context; | |||
| import android.content.Intent; | |||
| import android.content.ServiceConnection; | |||
| import android.os.IBinder; | |||
| import android.os.RemoteException; | |||
| import com.codezjx.andlinker.adapter.DefaultCallAdapterFactory; | |||
| import java.lang.annotation.Annotation; | |||
| import java.lang.reflect.InvocationHandler; | |||
| import java.lang.reflect.Method; | |||
| import java.lang.reflect.Proxy; | |||
| import java.lang.reflect.Type; | |||
| import java.util.ArrayList; | |||
| import java.util.List; | |||
| import java.util.Map; | |||
| import java.util.concurrent.ConcurrentHashMap; | |||
| import static com.codezjx.andlinker.Utils.checkNotNull; | |||
| /** | |||
| * AndLinker adapts a Java interface to IPC calls by using annotations on the declared methods to | |||
| * define how requests are made. Create instances using {@linkplain Builder | |||
| * the builder} and pass your interface to {@link #create} to generate an implementation. | |||
| */ | |||
| public final class AndLinker { | |||
| private static final String TAG = "AndLinker"; | |||
| private final Map<Method, ServiceMethod> serviceMethodCache = new ConcurrentHashMap<>(); | |||
| private ServiceConnection mServiceConnection; | |||
| private Invoker mInvoker; | |||
| private Context mContext; | |||
| private String mPackageName; | |||
| private String mAction; | |||
| private String mClassName; | |||
| private List<CallAdapter.Factory> mAdapterFactories; | |||
| private Dispatcher mDispatcher; | |||
| private ITransfer mTransferService; | |||
| private ICallback mCallback; | |||
| private BindCallback mBindCallback; | |||
| private AndLinker(Context context, String packageName, String action, String className, List<CallAdapter.Factory> adapterFactories) { | |||
| mContext = context; | |||
| mPackageName = packageName; | |||
| mAction = action; | |||
| mClassName = className; | |||
| mInvoker = new Invoker(); | |||
| mAdapterFactories = adapterFactories; | |||
| mDispatcher = new Dispatcher(); | |||
| mServiceConnection = createServiceConnection(); | |||
| mCallback = createCallback(); | |||
| } | |||
| /** | |||
| * Create an implementation defined by the remote service interface. | |||
| */ | |||
| @SuppressWarnings("unchecked") // Single-interface proxy creation guarded by parameter safety. | |||
| public <T> T create(final Class<T> service) { | |||
| Utils.validateServiceInterface(service); | |||
| return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class<?>[] { service }, | |||
| new InvocationHandler() { | |||
| @Override | |||
| public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { | |||
| // If the method is a method from Object then defer to normal invocation. | |||
| if (method.getDeclaringClass() == Object.class) { | |||
| return method.invoke(this, args); | |||
| } | |||
| ServiceMethod serviceMethod = loadServiceMethod(method); | |||
| RemoteCall remoteCall = new RemoteCall(mTransferService, serviceMethod, args, mDispatcher); | |||
| return serviceMethod.getCallAdapter().adapt(remoteCall); | |||
| } | |||
| }); | |||
| } | |||
| /** | |||
| * Connect to the remote service. | |||
| */ | |||
| public void bind() { | |||
| Intent intent = new Intent(); | |||
| if (!Utils.isStringBlank(mAction)) { | |||
| intent.setAction(mAction); | |||
| } else if (!Utils.isStringBlank(mClassName)) { | |||
| intent.setClassName(mPackageName, mClassName); | |||
| } | |||
| // After android 5.0+, service Intent must be explicit. | |||
| intent.setPackage(mPackageName); | |||
| mContext.bindService(intent, mServiceConnection, Context.BIND_AUTO_CREATE); | |||
| } | |||
| /** | |||
| * Disconnect from the remote service. | |||
| */ | |||
| public void unbind() { | |||
| mContext.unbindService(mServiceConnection); | |||
| } | |||
| /** | |||
| * Register client interface implementation called by remote service. | |||
| */ | |||
| public void registerObject(Object target) { | |||
| mInvoker.registerObject(target); | |||
| } | |||
| /** | |||
| * Unregister client interface implementation. | |||
| */ | |||
| public void unRegisterObject(Object target) { | |||
| mInvoker.unRegisterObject(target); | |||
| } | |||
| /** | |||
| * Set callback to be invoked when linker is bind or unBind. | |||
| */ | |||
| public void setBindCallback(BindCallback bindCallback) { | |||
| mBindCallback = bindCallback; | |||
| } | |||
| /** | |||
| * Return the remote service bind state. | |||
| */ | |||
| public boolean isBind() { | |||
| return mTransferService != null; | |||
| } | |||
| CallAdapter<?, ?> findCallAdapter(Type returnType, Annotation[] annotations) { | |||
| checkNotNull(returnType, "returnType == null"); | |||
| checkNotNull(annotations, "annotations == null"); | |||
| for (int i = 0, count = mAdapterFactories.size(); i < count; i++) { | |||
| CallAdapter<?, ?> adapter = mAdapterFactories.get(i).get(returnType, annotations); | |||
| if (adapter != null) { | |||
| return adapter; | |||
| } | |||
| } | |||
| return DefaultCallAdapterFactory.INSTANCE.get(returnType, annotations); | |||
| } | |||
| private ServiceConnection createServiceConnection() { | |||
| return new ServiceConnection() { | |||
| @Override | |||
| public void onServiceConnected(ComponentName name, IBinder service) { | |||
| mTransferService = ITransfer.Stub.asInterface(service); | |||
| fireOnBind(); | |||
| try { | |||
| mTransferService.register(mCallback); | |||
| } catch (RemoteException e) { | |||
| e.printStackTrace(); | |||
| } | |||
| } | |||
| @Override | |||
| public void onServiceDisconnected(ComponentName name) { | |||
| if (mTransferService == null) { | |||
| Logger.e(TAG, "Error occur, TransferService was null when service disconnected."); | |||
| fireOnUnBind(); | |||
| return; | |||
| } | |||
| try { | |||
| mTransferService.unRegister(mCallback); | |||
| } catch (RemoteException e) { | |||
| e.printStackTrace(); | |||
| } | |||
| mTransferService = null; | |||
| fireOnUnBind(); | |||
| } | |||
| }; | |||
| } | |||
| private void fireOnBind() { | |||
| if (mBindCallback != null) { | |||
| mBindCallback.onBind(); | |||
| } | |||
| } | |||
| private void fireOnUnBind() { | |||
| if (mBindCallback != null) { | |||
| mBindCallback.onUnBind(); | |||
| } | |||
| } | |||
| private ICallback createCallback() { | |||
| return new ICallback.Stub() { | |||
| @Override | |||
| public Response callback(Request request) throws RemoteException { | |||
| Logger.d(TAG, "Receive callback in client:" + request.toString()); | |||
| return mInvoker.invoke(request); | |||
| } | |||
| }; | |||
| } | |||
| private ServiceMethod loadServiceMethod(Method method) { | |||
| ServiceMethod result = serviceMethodCache.get(method); | |||
| if (result != null) { | |||
| return result; | |||
| } | |||
| synchronized (serviceMethodCache) { | |||
| result = serviceMethodCache.get(method); | |||
| if (result == null) { | |||
| result = new ServiceMethod.Builder(this, method).build(); | |||
| serviceMethodCache.put(method, result); | |||
| } | |||
| } | |||
| return result; | |||
| } | |||
| /** | |||
| * Method to enable or disable internal logger | |||
| */ | |||
| public static void enableLogger(boolean enable) { | |||
| Logger.sEnable = enable; | |||
| } | |||
| /** | |||
| * Builder to create a new {@link AndLinker} instance. | |||
| */ | |||
| public static final class Builder { | |||
| private Context mContext; | |||
| private String mPackageName; | |||
| private String mAction; | |||
| private String mClassName; | |||
| private List<CallAdapter.Factory> mAdapterFactories = new ArrayList<>(); | |||
| public Builder(Context context) { | |||
| mContext = context; | |||
| } | |||
| /** | |||
| * Set the remote service package name. | |||
| */ | |||
| public Builder packageName(String packageName) { | |||
| mPackageName = packageName; | |||
| return this; | |||
| } | |||
| /** | |||
| * Set the action to bind the remote service. | |||
| */ | |||
| public Builder action(String action) { | |||
| mAction = action; | |||
| return this; | |||
| } | |||
| /** | |||
| * Set the class name of the remote service. | |||
| */ | |||
| public Builder className(String className) { | |||
| mClassName = className; | |||
| return this; | |||
| } | |||
| /** | |||
| * Add a call adapter factory for supporting service method return types other than {@link Call}. | |||
| */ | |||
| public Builder addCallAdapterFactory(CallAdapter.Factory factory) { | |||
| mAdapterFactories.add(checkNotNull(factory, "factory == null")); | |||
| return this; | |||
| } | |||
| /** | |||
| * Create the {@link AndLinker} instance using the configured values. | |||
| */ | |||
| public AndLinker build() { | |||
| if (Utils.isStringBlank(mPackageName)) { | |||
| throw new IllegalStateException("Package name required."); | |||
| } | |||
| if (Utils.isStringBlank(mAction) && Utils.isStringBlank(mClassName)) { | |||
| throw new IllegalStateException("You must set one of the action or className."); | |||
| } | |||
| return new AndLinker(mContext, mPackageName, mAction, mClassName, mAdapterFactories); | |||
| } | |||
| } | |||
| /** | |||
| * Interface definition for a callback to be invoked when linker is bind or unBind to the service. | |||
| */ | |||
| public interface BindCallback { | |||
| /** | |||
| * Called when a connection to the remote service has been established, now you can execute the remote call. | |||
| */ | |||
| void onBind(); | |||
| /** | |||
| * Called when a connection to the remote service has been lost, any remote call will not execute. | |||
| */ | |||
| void onUnBind(); | |||
| } | |||
| } | |||
| @@ -0,0 +1,38 @@ | |||
| package com.codezjx.andlinker; | |||
| import android.os.IBinder; | |||
| /** | |||
| * AndLinker {@link IBinder} object to return in {@link android.app.Service#onBind(android.content.Intent)} method. | |||
| */ | |||
| public interface AndLinkerBinder extends IBinder { | |||
| /** | |||
| * Register service interface implementation. | |||
| */ | |||
| void registerObject(Object target); | |||
| /** | |||
| * Unregister service interface implementation. | |||
| */ | |||
| void unRegisterObject(Object target); | |||
| /** | |||
| * {@link AndLinkerBinder} factory class. | |||
| */ | |||
| final class Factory { | |||
| private Factory() { | |||
| } | |||
| /** | |||
| * Factory method to create the {@link AndLinkerBinder} impl instance. | |||
| */ | |||
| public static AndLinkerBinder newBinder() { | |||
| // Return inner package access LinkerBinder, prevent exposed. | |||
| return new LinkerBinderImpl(); | |||
| } | |||
| } | |||
| } | |||
| @@ -0,0 +1,356 @@ | |||
| package com.codezjx.andlinker; | |||
| import android.os.Parcel; | |||
| import android.os.Parcelable; | |||
| import java.lang.reflect.Array; | |||
| /** | |||
| * Created by codezjx on 2017/11/28.<br/> | |||
| */ | |||
| interface ArrayType<T> extends OutType<T> { | |||
| T newInstance(int length); | |||
| final class ByteArrayType implements ArrayType<byte[]> { | |||
| @Override | |||
| public void writeToParcel(Parcel dest, int flags, byte[] val) { | |||
| dest.writeByteArray(val); | |||
| } | |||
| @Override | |||
| public byte[] createFromParcel(Parcel in) { | |||
| return in.createByteArray(); | |||
| } | |||
| @Override | |||
| public void readFromParcel(Parcel in, byte[] val) { | |||
| in.readByteArray(val); | |||
| } | |||
| @Override | |||
| public byte[] newInstance(int length) { | |||
| return new byte[length]; | |||
| } | |||
| } | |||
| final class ShortArrayType implements ArrayType<short[]> { | |||
| @Override | |||
| public void writeToParcel(Parcel dest, int flags, short[] val) { | |||
| if (val != null) { | |||
| int N = val.length; | |||
| dest.writeInt(N); | |||
| for (int i = 0; i < N; i++) { | |||
| dest.writeInt(val[i]); | |||
| } | |||
| } else { | |||
| dest.writeInt(-1); | |||
| } | |||
| } | |||
| @Override | |||
| public short[] createFromParcel(Parcel in) { | |||
| int N = in.readInt(); | |||
| if (N >= 0 && N <= (in.dataAvail() >> 2)) { | |||
| short[] val = new short[N]; | |||
| for (int i = 0; i < N; i++) { | |||
| val[i] = (short) in.readInt(); | |||
| } | |||
| return val; | |||
| } else { | |||
| return null; | |||
| } | |||
| } | |||
| @Override | |||
| public void readFromParcel(Parcel in, short[] val) { | |||
| int N = in.readInt(); | |||
| if (N == val.length) { | |||
| for (int i = 0; i < N; i++) { | |||
| val[i] = (short) in.readInt(); | |||
| } | |||
| } else { | |||
| throw new RuntimeException("bad array lengths"); | |||
| } | |||
| } | |||
| @Override | |||
| public short[] newInstance(int length) { | |||
| return new short[length]; | |||
| } | |||
| } | |||
| final class IntArrayType implements ArrayType<int[]> { | |||
| @Override | |||
| public void writeToParcel(Parcel dest, int flags, int[] val) { | |||
| dest.writeIntArray(val); | |||
| } | |||
| @Override | |||
| public int[] createFromParcel(Parcel in) { | |||
| return in.createIntArray(); | |||
| } | |||
| @Override | |||
| public void readFromParcel(Parcel in, int[] val) { | |||
| in.readIntArray(val); | |||
| } | |||
| @Override | |||
| public int[] newInstance(int length) { | |||
| return new int[length]; | |||
| } | |||
| } | |||
| final class LongArrayType implements ArrayType<long[]> { | |||
| @Override | |||
| public void writeToParcel(Parcel dest, int flags, long[] val) { | |||
| dest.writeLongArray(val); | |||
| } | |||
| @Override | |||
| public long[] createFromParcel(Parcel in) { | |||
| return in.createLongArray(); | |||
| } | |||
| @Override | |||
| public void readFromParcel(Parcel in, long[] val) { | |||
| in.readLongArray(val); | |||
| } | |||
| @Override | |||
| public long[] newInstance(int length) { | |||
| return new long[length]; | |||
| } | |||
| } | |||
| final class FloatArrayType implements ArrayType<float[]> { | |||
| @Override | |||
| public void writeToParcel(Parcel dest, int flags, float[] val) { | |||
| dest.writeFloatArray(val); | |||
| } | |||
| @Override | |||
| public float[] createFromParcel(Parcel in) { | |||
| return in.createFloatArray(); | |||
| } | |||
| @Override | |||
| public void readFromParcel(Parcel in, float[] val) { | |||
| in.readFloatArray(val); | |||
| } | |||
| @Override | |||
| public float[] newInstance(int length) { | |||
| return new float[length]; | |||
| } | |||
| } | |||
| final class DoubleArrayType implements ArrayType<double[]> { | |||
| @Override | |||
| public void writeToParcel(Parcel dest, int flags, double[] val) { | |||
| dest.writeDoubleArray(val); | |||
| } | |||
| @Override | |||
| public double[] createFromParcel(Parcel in) { | |||
| return in.createDoubleArray(); | |||
| } | |||
| @Override | |||
| public void readFromParcel(Parcel in, double[] val) { | |||
| in.readDoubleArray(val); | |||
| } | |||
| @Override | |||
| public double[] newInstance(int length) { | |||
| return new double[length]; | |||
| } | |||
| } | |||
| final class BooleanArrayType implements ArrayType<boolean[]> { | |||
| @Override | |||
| public void writeToParcel(Parcel dest, int flags, boolean[] val) { | |||
| dest.writeBooleanArray(val); | |||
| } | |||
| @Override | |||
| public boolean[] createFromParcel(Parcel in) { | |||
| return in.createBooleanArray(); | |||
| } | |||
| @Override | |||
| public void readFromParcel(Parcel in, boolean[] val) { | |||
| in.readBooleanArray(val); | |||
| } | |||
| @Override | |||
| public boolean[] newInstance(int length) { | |||
| return new boolean[length]; | |||
| } | |||
| } | |||
| final class CharArrayType implements ArrayType<char[]> { | |||
| @Override | |||
| public void writeToParcel(Parcel dest, int flags, char[] val) { | |||
| dest.writeCharArray(val); | |||
| } | |||
| @Override | |||
| public char[] createFromParcel(Parcel in) { | |||
| return in.createCharArray(); | |||
| } | |||
| @Override | |||
| public void readFromParcel(Parcel in, char[] val) { | |||
| in.readCharArray(val); | |||
| } | |||
| @Override | |||
| public char[] newInstance(int length) { | |||
| return new char[length]; | |||
| } | |||
| } | |||
| final class StringArrayType implements ArrayType<String[]> { | |||
| @Override | |||
| public void writeToParcel(Parcel dest, int flags, String[] val) { | |||
| dest.writeStringArray(val); | |||
| } | |||
| @Override | |||
| public String[] createFromParcel(Parcel in) { | |||
| return in.createStringArray(); | |||
| } | |||
| @Override | |||
| public void readFromParcel(Parcel in, String[] val) { | |||
| in.readStringArray(val); | |||
| } | |||
| @Override | |||
| public String[] newInstance(int length) { | |||
| return new String[length]; | |||
| } | |||
| } | |||
| final class CharSequenceArrayType implements ArrayType<CharSequence[]> { | |||
| @Override | |||
| public void writeToParcel(Parcel dest, int flags, CharSequence[] val) { | |||
| writeCharSequenceArray(dest, val); | |||
| } | |||
| @Override | |||
| public CharSequence[] createFromParcel(Parcel in) { | |||
| return readCharSequenceArray(in); | |||
| } | |||
| @Override | |||
| public void readFromParcel(Parcel in, CharSequence[] val) { | |||
| int N = in.readInt(); | |||
| if (N == val.length) { | |||
| for (int i = 0; i < N; i++) { | |||
| val[i] = Type.CharSequenceType.readCharSequence(in); | |||
| } | |||
| } else { | |||
| throw new RuntimeException("bad array lengths"); | |||
| } | |||
| } | |||
| @Override | |||
| public CharSequence[] newInstance(int length) { | |||
| return new CharSequence[length]; | |||
| } | |||
| private void writeCharSequenceArray(Parcel dest, CharSequence[] val) { | |||
| if (val != null) { | |||
| int N = val.length; | |||
| dest.writeInt(N); | |||
| for (int i = 0; i < N; i++) { | |||
| Type.CharSequenceType.writeCharSequence(dest, val[i]); | |||
| } | |||
| } else { | |||
| dest.writeInt(-1); | |||
| } | |||
| } | |||
| private CharSequence[] readCharSequenceArray(Parcel in) { | |||
| CharSequence[] array = null; | |||
| int length = in.readInt(); | |||
| if (length >= 0) { | |||
| array = new CharSequence[length]; | |||
| for (int i = 0 ; i < length ; i++) { | |||
| array[i] = Type.CharSequenceType.readCharSequence(in); | |||
| } | |||
| } | |||
| return array; | |||
| } | |||
| } | |||
| final class ParcelableArrayType implements ArrayType<Parcelable[]> { | |||
| @Override | |||
| public void writeToParcel(Parcel dest, int flags, Parcelable[] val) { | |||
| dest.writeString(val.getClass().getComponentType().getName()); | |||
| dest.writeParcelableArray(val, flags); | |||
| } | |||
| @Override | |||
| public Parcelable[] createFromParcel(Parcel in) { | |||
| String componentType = in.readString(); | |||
| Object obj = null; | |||
| try { | |||
| Class cls = Class.forName(componentType); | |||
| obj = createTypedArray(in, cls); | |||
| } catch (ClassNotFoundException e) { | |||
| e.printStackTrace(); | |||
| } | |||
| return (Parcelable[]) obj; | |||
| } | |||
| @Override | |||
| public void readFromParcel(Parcel in, Parcelable[] val) { | |||
| // Just read and do nothing, because we don't need component type here | |||
| in.readString(); | |||
| int N = in.readInt(); | |||
| if (N == val.length) { | |||
| for (int i = 0; i < N; i++) { | |||
| val[i] = in.readParcelable(getClass().getClassLoader()); | |||
| } | |||
| } else { | |||
| throw new RuntimeException("bad array lengths"); | |||
| } | |||
| } | |||
| @Override | |||
| public Parcelable[] newInstance(int length) { | |||
| return new Parcelable[length]; | |||
| } | |||
| private <T> T[] createTypedArray(Parcel in, Class<T> cls) { | |||
| int N = in.readInt(); | |||
| if (N < 0) { | |||
| return null; | |||
| } | |||
| T[] arr = (T[]) Array.newInstance(cls, N); | |||
| for (int i = 0; i < N; i++) { | |||
| arr[i] = in.readParcelable(cls.getClassLoader()); | |||
| } | |||
| return arr; | |||
| } | |||
| } | |||
| } | |||
| @@ -0,0 +1,44 @@ | |||
| package com.codezjx.andlinker; | |||
| /** | |||
| * Created by codezjx on 2017/11/19.<br/> | |||
| */ | |||
| interface BaseTypeWrapper extends SuperParcelable { | |||
| int TYPE_EMPTY = 0; | |||
| // Primitives | |||
| int TYPE_BYTE = 1; | |||
| int TYPE_SHORT = 2; | |||
| int TYPE_INT = 3; | |||
| int TYPE_LONG = 4; | |||
| int TYPE_FLOAT = 5; | |||
| int TYPE_DOUBLE = 6; | |||
| int TYPE_BOOLEAN = 7; | |||
| int TYPE_CHAR = 8; | |||
| // Primitive Arrays | |||
| int TYPE_BYTEARRAY = 9; | |||
| int TYPE_SHORTARRAY = 10; | |||
| int TYPE_INTARRAY = 11; | |||
| int TYPE_LONGARRAY = 12; | |||
| int TYPE_FLOATARRAY = 13; | |||
| int TYPE_DOUBLEARRAY = 14; | |||
| int TYPE_BOOLEANARRAY = 15; | |||
| int TYPE_CHARARRAY = 16; | |||
| // Other | |||
| int TYPE_STRING = 17; | |||
| int TYPE_STRINGARRAY = 18; | |||
| int TYPE_CHARSEQUENCE = 19; | |||
| int TYPE_CHARSEQUENCEARRAY = 20; | |||
| int TYPE_PARCELABLE = 21; | |||
| int TYPE_PARCELABLEARRAY = 22; | |||
| int TYPE_LIST = 23; | |||
| int TYPE_MAP = 24; | |||
| int TYPE_CALLBACK = 25; | |||
| int getType(); | |||
| Object getParam(); | |||
| } | |||
| @@ -0,0 +1,34 @@ | |||
| package com.codezjx.andlinker; | |||
| /** | |||
| * An invocation of a remote method that sends a request to host and returns a response. | |||
| */ | |||
| public interface Call<T> { | |||
| /** | |||
| * Synchronously send the request and return its response. | |||
| */ | |||
| T execute(); | |||
| /** | |||
| * Asynchronously send the request and notify when response return. | |||
| * @param callback The callback to notify. | |||
| */ | |||
| void enqueue(Callback<T> callback); | |||
| /** | |||
| * Returns true if this call has been either {@linkplain #execute() executed} or {@linkplain #enqueue(Callback) enqueued}. | |||
| */ | |||
| boolean isExecuted(); | |||
| /** | |||
| * Cancel this call. If the call has not yet been executed it never will be. | |||
| */ | |||
| void cancel(); | |||
| /** | |||
| * True if {@link #cancel()} was called. | |||
| */ | |||
| boolean isCanceled(); | |||
| } | |||
| @@ -0,0 +1,36 @@ | |||
| package com.codezjx.andlinker; | |||
| import java.lang.annotation.Annotation; | |||
| import java.lang.reflect.Type; | |||
| /** | |||
| * Adapts a {@link Call} with response type {@code R} into the type of {@code T}. Instances are | |||
| * created by {@linkplain Factory factory} which is {@linkplain AndLinker.Builder#addCallAdapterFactory(Factory) installed} | |||
| * into the {@link AndLinker} instance. | |||
| */ | |||
| public interface CallAdapter<R, T> { | |||
| /** | |||
| * Returns an instance of {@code T} which delegates to {@code call}. | |||
| */ | |||
| T adapt(Call<R> call); | |||
| /** | |||
| * Creates {@link CallAdapter} instances based on the return type of {@linkplain | |||
| * AndLinker#create(Class) the service interface} methods. | |||
| */ | |||
| abstract class Factory { | |||
| /** | |||
| * Returns a call adapter for interface methods that return {@code returnType}, or null if it | |||
| * cannot be handled by this factory. | |||
| */ | |||
| public abstract CallAdapter<?, ?> get(Type returnType, Annotation[] annotations); | |||
| protected static Class<?> getRawType(Type type) { | |||
| return Utils.getRawType(type); | |||
| } | |||
| } | |||
| } | |||
| @@ -0,0 +1,19 @@ | |||
| package com.codezjx.andlinker; | |||
| /** | |||
| * Interface definition for a callback to be invoked when execute {@linkplain Call#enqueue(Callback) asynchronously call}. | |||
| * By default callbacks are executed on the application's main (UI) thread. | |||
| */ | |||
| public interface Callback<T> { | |||
| /** | |||
| * Invoked for a received response. | |||
| */ | |||
| void onResponse(Call<T> call, T response); | |||
| /** | |||
| * Invoked when a remote exception occurred. | |||
| */ | |||
| void onFailure(Call<T> call, Throwable t); | |||
| } | |||
| @@ -0,0 +1,60 @@ | |||
| package com.codezjx.andlinker; | |||
| import android.os.Parcel; | |||
| /** | |||
| * Created by codezjx on 2017/11/18.<br/> | |||
| */ | |||
| final class CallbackTypeWrapper implements BaseTypeWrapper { | |||
| private String mClassName; | |||
| CallbackTypeWrapper(String className) { | |||
| mClassName = className; | |||
| } | |||
| String getClassName() { | |||
| return mClassName; | |||
| } | |||
| @Override | |||
| public int getType() { | |||
| return TYPE_CALLBACK; | |||
| } | |||
| @Override | |||
| public Object getParam() { | |||
| return null; | |||
| } | |||
| @Override | |||
| public int describeContents() { | |||
| return 0; | |||
| } | |||
| @Override | |||
| public void writeToParcel(Parcel dest, int flags) { | |||
| dest.writeString(mClassName); | |||
| } | |||
| @Override | |||
| public void readFromParcel(Parcel in) { | |||
| mClassName = in.readString(); | |||
| } | |||
| private CallbackTypeWrapper(Parcel in) { | |||
| readFromParcel(in); | |||
| } | |||
| public static final Creator<CallbackTypeWrapper> CREATOR = new Creator<CallbackTypeWrapper>() { | |||
| @Override | |||
| public CallbackTypeWrapper createFromParcel(Parcel source) { | |||
| return new CallbackTypeWrapper(source); | |||
| } | |||
| @Override | |||
| public CallbackTypeWrapper[] newArray(int size) { | |||
| return new CallbackTypeWrapper[size]; | |||
| } | |||
| }; | |||
| } | |||
| @@ -0,0 +1,49 @@ | |||
| package com.codezjx.andlinker; | |||
| import java.util.concurrent.ExecutorService; | |||
| import java.util.concurrent.SynchronousQueue; | |||
| import java.util.concurrent.ThreadFactory; | |||
| import java.util.concurrent.ThreadPoolExecutor; | |||
| import java.util.concurrent.TimeUnit; | |||
| import java.util.concurrent.atomic.AtomicInteger; | |||
| /** | |||
| * Created by codezjx on 2018/1/10.<br/> | |||
| */ | |||
| final class Dispatcher { | |||
| private static final String THREAD_NAME = "Dispatcher Thread #"; | |||
| private static final int KEEP_ALIVE_TIME_SECONDS = 60; | |||
| private ExecutorService mExecutorService; | |||
| Dispatcher() { | |||
| } | |||
| Dispatcher(ExecutorService executorService) { | |||
| mExecutorService = executorService; | |||
| } | |||
| synchronized void enqueue(Runnable task) { | |||
| if (mExecutorService == null) { | |||
| mExecutorService = new ThreadPoolExecutor(0, Integer.MAX_VALUE, | |||
| KEEP_ALIVE_TIME_SECONDS, TimeUnit.SECONDS, | |||
| new SynchronousQueue<Runnable>(), createFactory()); | |||
| } | |||
| mExecutorService.execute(task); | |||
| } | |||
| private ThreadFactory createFactory() { | |||
| return new ThreadFactory() { | |||
| private final AtomicInteger mCount = new AtomicInteger(1); | |||
| @Override | |||
| public Thread newThread(Runnable runnable) { | |||
| Thread thread = new Thread(runnable, THREAD_NAME + mCount.getAndIncrement()); | |||
| thread.setDaemon(false); | |||
| return thread; | |||
| } | |||
| }; | |||
| } | |||
| } | |||
| @@ -0,0 +1,114 @@ | |||
| package com.codezjx.andlinker; | |||
| interface ICallback extends android.os.IInterface { | |||
| /** | |||
| * Local-side IPC implementation stub class. | |||
| */ | |||
| abstract class Stub extends android.os.Binder implements ICallback { | |||
| private static final String DESCRIPTOR = "com.codezjx.alinker.ICallback"; | |||
| /** | |||
| * Construct the stub at attach it to the interface. | |||
| */ | |||
| Stub() { | |||
| this.attachInterface(this, DESCRIPTOR); | |||
| } | |||
| /** | |||
| * Cast an IBinder object into an com.codezjx.alinker.ICallback interface, | |||
| * generating a proxy if needed. | |||
| */ | |||
| static ICallback asInterface(android.os.IBinder obj) { | |||
| if ((obj == null)) { | |||
| return null; | |||
| } | |||
| android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR); | |||
| if (((iin != null) && (iin instanceof ICallback))) { | |||
| return ((ICallback) iin); | |||
| } | |||
| return new Proxy(obj); | |||
| } | |||
| @Override | |||
| public android.os.IBinder asBinder() { | |||
| return this; | |||
| } | |||
| @Override | |||
| public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException { | |||
| switch (code) { | |||
| case INTERFACE_TRANSACTION: { | |||
| reply.writeString(DESCRIPTOR); | |||
| return true; | |||
| } | |||
| case TRANSACTION_callback: { | |||
| data.enforceInterface(DESCRIPTOR); | |||
| Request _arg0; | |||
| if ((0 != data.readInt())) { | |||
| _arg0 = Request.CREATOR.createFromParcel(data); | |||
| } else { | |||
| _arg0 = null; | |||
| } | |||
| Response _result = this.callback(_arg0); | |||
| reply.writeNoException(); | |||
| if ((_result != null)) { | |||
| reply.writeInt(1); | |||
| _result.writeToParcel(reply, android.os.Parcelable.PARCELABLE_WRITE_RETURN_VALUE); | |||
| } else { | |||
| reply.writeInt(0); | |||
| } | |||
| return true; | |||
| } | |||
| } | |||
| return super.onTransact(code, data, reply, flags); | |||
| } | |||
| private static class Proxy implements ICallback { | |||
| private android.os.IBinder mRemote; | |||
| Proxy(android.os.IBinder remote) { | |||
| mRemote = remote; | |||
| } | |||
| @Override | |||
| public android.os.IBinder asBinder() { | |||
| return mRemote; | |||
| } | |||
| public String getInterfaceDescriptor() { | |||
| return DESCRIPTOR; | |||
| } | |||
| @Override | |||
| public Response callback(Request request) throws android.os.RemoteException { | |||
| android.os.Parcel _data = android.os.Parcel.obtain(); | |||
| android.os.Parcel _reply = android.os.Parcel.obtain(); | |||
| Response _result; | |||
| try { | |||
| _data.writeInterfaceToken(DESCRIPTOR); | |||
| if ((request != null)) { | |||
| _data.writeInt(1); | |||
| request.writeToParcel(_data, 0); | |||
| } else { | |||
| _data.writeInt(0); | |||
| } | |||
| mRemote.transact(Stub.TRANSACTION_callback, _data, _reply, 0); | |||
| _reply.readException(); | |||
| if ((0 != _reply.readInt())) { | |||
| _result = Response.CREATOR.createFromParcel(_reply); | |||
| } else { | |||
| _result = null; | |||
| } | |||
| } finally { | |||
| _reply.recycle(); | |||
| _data.recycle(); | |||
| } | |||
| return _result; | |||
| } | |||
| } | |||
| static final int TRANSACTION_callback = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0); | |||
| } | |||
| Response callback(Request request) throws android.os.RemoteException; | |||
| } | |||
| @@ -0,0 +1,184 @@ | |||
| package com.codezjx.andlinker; | |||
| interface ITransfer extends android.os.IInterface { | |||
| /** | |||
| * Local-side IPC implementation stub class. | |||
| */ | |||
| abstract class Stub extends android.os.Binder implements ITransfer { | |||
| private static final String DESCRIPTOR = "com.codezjx.alinker.ITransfer"; | |||
| /** | |||
| * Construct the stub at attach it to the interface. | |||
| */ | |||
| Stub() { | |||
| this.attachInterface(this, DESCRIPTOR); | |||
| } | |||
| /** | |||
| * Cast an IBinder object into an com.codezjx.alinker.ITransfer interface, | |||
| * generating a proxy if needed. | |||
| */ | |||
| static ITransfer asInterface(android.os.IBinder obj) { | |||
| if ((obj == null)) { | |||
| return null; | |||
| } | |||
| android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR); | |||
| if (((iin != null) && (iin instanceof ITransfer))) { | |||
| return ((ITransfer) iin); | |||
| } | |||
| return new Proxy(obj); | |||
| } | |||
| @Override | |||
| public android.os.IBinder asBinder() { | |||
| return this; | |||
| } | |||
| @Override | |||
| public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException { | |||
| switch (code) { | |||
| case INTERFACE_TRANSACTION: { | |||
| reply.writeString(DESCRIPTOR); | |||
| return true; | |||
| } | |||
| case TRANSACTION_execute: { | |||
| data.enforceInterface(DESCRIPTOR); | |||
| Request _arg0; | |||
| if ((0 != data.readInt())) { | |||
| _arg0 = Request.CREATOR.createFromParcel(data); | |||
| } else { | |||
| _arg0 = null; | |||
| } | |||
| Response _result = this.execute(_arg0); | |||
| if ((flags & android.os.IBinder.FLAG_ONEWAY) != 0) { | |||
| // One-way mode just execute and return directly. | |||
| return true; | |||
| } | |||
| reply.writeNoException(); | |||
| if ((_result != null)) { | |||
| reply.writeInt(1); | |||
| _result.writeToParcel(reply, android.os.Parcelable.PARCELABLE_WRITE_RETURN_VALUE); | |||
| } else { | |||
| reply.writeInt(0); | |||
| } | |||
| if ((_arg0 != null)) { | |||
| reply.writeInt(1); | |||
| _arg0.writeToParcel(reply, android.os.Parcelable.PARCELABLE_WRITE_RETURN_VALUE); | |||
| } else { | |||
| reply.writeInt(0); | |||
| } | |||
| return true; | |||
| } | |||
| case TRANSACTION_register: { | |||
| data.enforceInterface(DESCRIPTOR); | |||
| ICallback _arg0; | |||
| _arg0 = ICallback.Stub.asInterface(data.readStrongBinder()); | |||
| this.register(_arg0); | |||
| reply.writeNoException(); | |||
| return true; | |||
| } | |||
| case TRANSACTION_unRegister: { | |||
| data.enforceInterface(DESCRIPTOR); | |||
| ICallback _arg0; | |||
| _arg0 = ICallback.Stub.asInterface(data.readStrongBinder()); | |||
| this.unRegister(_arg0); | |||
| reply.writeNoException(); | |||
| return true; | |||
| } | |||
| } | |||
| return super.onTransact(code, data, reply, flags); | |||
| } | |||
| private static class Proxy implements ITransfer { | |||
| private android.os.IBinder mRemote; | |||
| Proxy(android.os.IBinder remote) { | |||
| mRemote = remote; | |||
| } | |||
| @Override | |||
| public android.os.IBinder asBinder() { | |||
| return mRemote; | |||
| } | |||
| public String getInterfaceDescriptor() { | |||
| return DESCRIPTOR; | |||
| } | |||
| @Override | |||
| public Response execute(Request request) throws android.os.RemoteException { | |||
| android.os.Parcel _data = android.os.Parcel.obtain(); | |||
| android.os.Parcel _reply = android.os.Parcel.obtain(); | |||
| Response _result; | |||
| try { | |||
| _data.writeInterfaceToken(DESCRIPTOR); | |||
| if ((request != null)) { | |||
| _data.writeInt(1); | |||
| request.writeToParcel(_data, 0); | |||
| } else { | |||
| _data.writeInt(0); | |||
| } | |||
| // One-way mode just transact and return directly. | |||
| if (request != null && request.isOneWay()) { | |||
| mRemote.transact(Stub.TRANSACTION_execute, _data, null, android.os.IBinder.FLAG_ONEWAY); | |||
| return null; | |||
| } | |||
| mRemote.transact(Stub.TRANSACTION_execute, _data, _reply, 0); | |||
| _reply.readException(); | |||
| if ((0 != _reply.readInt())) { | |||
| _result = Response.CREATOR.createFromParcel(_reply); | |||
| } else { | |||
| _result = null; | |||
| } | |||
| if ((0 != _reply.readInt())) { | |||
| request.readFromParcel(_reply); | |||
| } | |||
| } finally { | |||
| _reply.recycle(); | |||
| _data.recycle(); | |||
| } | |||
| return _result; | |||
| } | |||
| @Override | |||
| public void register(ICallback callback) throws android.os.RemoteException { | |||
| android.os.Parcel _data = android.os.Parcel.obtain(); | |||
| android.os.Parcel _reply = android.os.Parcel.obtain(); | |||
| try { | |||
| _data.writeInterfaceToken(DESCRIPTOR); | |||
| _data.writeStrongBinder((((callback != null)) ? (callback.asBinder()) : (null))); | |||
| mRemote.transact(Stub.TRANSACTION_register, _data, _reply, 0); | |||
| _reply.readException(); | |||
| } finally { | |||
| _reply.recycle(); | |||
| _data.recycle(); | |||
| } | |||
| } | |||
| @Override | |||
| public void unRegister(ICallback callback) throws android.os.RemoteException { | |||
| android.os.Parcel _data = android.os.Parcel.obtain(); | |||
| android.os.Parcel _reply = android.os.Parcel.obtain(); | |||
| try { | |||
| _data.writeInterfaceToken(DESCRIPTOR); | |||
| _data.writeStrongBinder((((callback != null)) ? (callback.asBinder()) : (null))); | |||
| mRemote.transact(Stub.TRANSACTION_unRegister, _data, _reply, 0); | |||
| _reply.readException(); | |||
| } finally { | |||
| _reply.recycle(); | |||
| _data.recycle(); | |||
| } | |||
| } | |||
| } | |||
| static final int TRANSACTION_execute = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0); | |||
| static final int TRANSACTION_register = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1); | |||
| static final int TRANSACTION_unRegister = (android.os.IBinder.FIRST_CALL_TRANSACTION + 2); | |||
| } | |||
| Response execute(Request request) throws android.os.RemoteException; | |||
| void register(ICallback callback) throws android.os.RemoteException; | |||
| void unRegister(ICallback callback) throws android.os.RemoteException; | |||
| } | |||
| @@ -0,0 +1,65 @@ | |||
| package com.codezjx.andlinker; | |||
| import android.os.Parcel; | |||
| /** | |||
| * Created by codezjx on 2017/11/30.<br/> | |||
| */ | |||
| final class InOutTypeWrapper implements BaseTypeWrapper { | |||
| private int mType; | |||
| private Object mParam; | |||
| InOutTypeWrapper(Object param, Class<?> mParamType) { | |||
| mType = Utils.getTypeByClass(mParamType); | |||
| mParam = param; | |||
| } | |||
| @Override | |||
| public int getType() { | |||
| return mType; | |||
| } | |||
| @Override | |||
| public Object getParam() { | |||
| return mParam; | |||
| } | |||
| @Override | |||
| public int describeContents() { | |||
| return 0; | |||
| } | |||
| @Override | |||
| public void writeToParcel(Parcel dest, int flags) { | |||
| dest.writeInt(mType); | |||
| Type type = TypeFactory.getType(mType); | |||
| type.writeToParcel(dest, flags, mParam); | |||
| } | |||
| @Override | |||
| public void readFromParcel(Parcel in) { | |||
| mType = in.readInt(); | |||
| OutType type = (OutType) TypeFactory.getType(mType); | |||
| type.readFromParcel(in, mParam); | |||
| } | |||
| private InOutTypeWrapper(Parcel in) { | |||
| mType = in.readInt(); | |||
| Type type = TypeFactory.getType(mType); | |||
| mParam = type.createFromParcel(in); | |||
| } | |||
| public static final Creator<InOutTypeWrapper> CREATOR = new Creator<InOutTypeWrapper>() { | |||
| @Override | |||
| public InOutTypeWrapper createFromParcel(Parcel source) { | |||
| return new InOutTypeWrapper(source); | |||
| } | |||
| @Override | |||
| public InOutTypeWrapper[] newArray(int size) { | |||
| return new InOutTypeWrapper[size]; | |||
| } | |||
| }; | |||
| } | |||
| @@ -0,0 +1,67 @@ | |||
| package com.codezjx.andlinker; | |||
| import android.os.Parcel; | |||
| /** | |||
| * Created by codezjx on 2017/11/19.<br/> | |||
| */ | |||
| final class InTypeWrapper implements BaseTypeWrapper { | |||
| private int mType; | |||
| private Object mParam; | |||
| InTypeWrapper(Object param, Class<?> mParamType) { | |||
| mType = Utils.getTypeByClass(mParamType); | |||
| mParam = param; | |||
| } | |||
| @Override | |||
| public int getType() { | |||
| return mType; | |||
| } | |||
| @Override | |||
| public Object getParam() { | |||
| return mParam; | |||
| } | |||
| @Override | |||
| public int describeContents() { | |||
| return 0; | |||
| } | |||
| @Override | |||
| public void writeToParcel(Parcel dest, int flags) { | |||
| // Return if write return value, because in type won't readFromParcel | |||
| if (flags == PARCELABLE_WRITE_RETURN_VALUE) { | |||
| return; | |||
| } | |||
| dest.writeInt(mType); | |||
| Type type = TypeFactory.getType(mType); | |||
| type.writeToParcel(dest, flags, mParam); | |||
| } | |||
| @Override | |||
| public void readFromParcel(Parcel in) { | |||
| // Nothing to do with in type | |||
| } | |||
| private InTypeWrapper(Parcel in) { | |||
| mType = in.readInt(); | |||
| Type type = TypeFactory.getType(mType); | |||
| mParam = type.createFromParcel(in); | |||
| } | |||
| public static final Creator<InTypeWrapper> CREATOR = new Creator<InTypeWrapper>() { | |||
| @Override | |||
| public InTypeWrapper createFromParcel(Parcel source) { | |||
| return new InTypeWrapper(source); | |||
| } | |||
| @Override | |||
| public InTypeWrapper[] newArray(int size) { | |||
| return new InTypeWrapper[size]; | |||
| } | |||
| }; | |||
| } | |||
| @@ -0,0 +1,197 @@ | |||
| package com.codezjx.andlinker; | |||
| import android.os.Binder; | |||
| import android.os.RemoteCallbackList; | |||
| import android.os.RemoteException; | |||
| import com.codezjx.andlinker.annotation.RemoteInterface; | |||
| import java.lang.annotation.Annotation; | |||
| import java.lang.reflect.InvocationHandler; | |||
| import java.lang.reflect.Method; | |||
| import java.lang.reflect.Proxy; | |||
| import java.util.concurrent.ConcurrentHashMap; | |||
| /** | |||
| * Created by codezjx on 2017/10/3.<br/> | |||
| */ | |||
| final class Invoker { | |||
| private static final String TAG = "Invoker"; | |||
| private final ConcurrentHashMap<String, Class<?>> mCallbackClassTypes; | |||
| private final ConcurrentHashMap<String, MethodExecutor> mMethodExecutors; | |||
| private final RemoteCallbackList<ICallback> mCallbackList; | |||
| Invoker() { | |||
| mCallbackClassTypes = new ConcurrentHashMap<String, Class<?>>(); | |||
| mMethodExecutors = new ConcurrentHashMap<String, MethodExecutor>(); | |||
| mCallbackList = new RemoteCallbackList<ICallback>(); | |||
| } | |||
| private void handleCallbackClass(Class<?> clazz, boolean isRegister) { | |||
| if (!clazz.isAnnotationPresent(RemoteInterface.class)) { | |||
| throw new IllegalArgumentException("Callback interface doesn't has @RemoteInterface annotation."); | |||
| } | |||
| String className = clazz.getSimpleName(); | |||
| if (isRegister) { | |||
| mCallbackClassTypes.putIfAbsent(className, clazz); | |||
| } else { | |||
| mCallbackClassTypes.remove(className); | |||
| } | |||
| } | |||
| private void handleObject(Object target, boolean isRegister) { | |||
| if (target == null) { | |||
| throw new NullPointerException("Object to (un)register must not be null."); | |||
| } | |||
| Class<?>[] interfaces = target.getClass().getInterfaces(); | |||
| if (interfaces.length != 1) { | |||
| throw new IllegalArgumentException("Remote object must extend just one interface."); | |||
| } | |||
| Class<?> clazz = interfaces[0]; | |||
| if (!clazz.isAnnotationPresent(RemoteInterface.class)) { | |||
| throw new IllegalArgumentException("Interface doesn't has @RemoteInterface annotation."); | |||
| } | |||
| // Cache all annotation method | |||
| String clsName = clazz.getSimpleName(); | |||
| Method[] methods = clazz.getDeclaredMethods(); | |||
| for (Method method : methods) { | |||
| // The compiler sometimes creates synthetic bridge methods as part of the | |||
| // type erasure process. As of JDK8 these methods now include the same | |||
| // annotations as the original declarations. They should be ignored for | |||
| // subscribe/produce. | |||
| if (method.isBridge()) { | |||
| continue; | |||
| } | |||
| String methodName = method.getName(); | |||
| String key = createMethodExecutorKey(clsName, methodName); | |||
| if (isRegister) { | |||
| MethodExecutor executor = new MethodExecutor(target, method); | |||
| MethodExecutor preExecutor = mMethodExecutors.putIfAbsent(key, executor); | |||
| if (preExecutor != null) { | |||
| throw new IllegalStateException("Key conflict with class:" + clsName + " method:" + methodName | |||
| + ". Please try another class/method name."); | |||
| } | |||
| } else { | |||
| mMethodExecutors.remove(key); | |||
| } | |||
| // Cache callback class if exist | |||
| Class<?>[] paramCls = method.getParameterTypes(); | |||
| Annotation[][] paramAnnotations = method.getParameterAnnotations(); | |||
| for (int i = 0; i < paramCls.length; i++) { | |||
| Class<?> cls = paramCls[i]; | |||
| Annotation[] annotations = paramAnnotations[i]; | |||
| if (!containCallbackAnnotation(annotations)) { | |||
| continue; | |||
| } | |||
| handleCallbackClass(cls, isRegister); | |||
| } | |||
| } | |||
| } | |||
| private boolean containCallbackAnnotation(Annotation[] annotations) { | |||
| if (annotations == null) { | |||
| return false; | |||
| } | |||
| for (Annotation annotation : annotations) { | |||
| if (annotation instanceof com.codezjx.andlinker.annotation.Callback) { | |||
| return true; | |||
| } | |||
| } | |||
| return false; | |||
| } | |||
| void registerObject(Object target) { | |||
| handleObject(target, true); | |||
| } | |||
| void unRegisterObject(Object target) { | |||
| handleObject(target, false); | |||
| } | |||
| Response invoke(Request request) { | |||
| BaseTypeWrapper[] wrappers = request.getArgsWrapper(); | |||
| Object[] args = new Object[wrappers.length]; | |||
| for (int i = 0; i < wrappers.length; i++) { | |||
| // Assign the origin args parameter | |||
| args[i] = wrappers[i].getParam(); | |||
| if (wrappers[i].getType() == BaseTypeWrapper.TYPE_CALLBACK) { | |||
| int pid = Binder.getCallingPid(); | |||
| String clazzName = ((CallbackTypeWrapper) wrappers[i]).getClassName(); | |||
| Class<?> clazz = getCallbackClass(clazzName); | |||
| if (clazz == null) { | |||
| throw new IllegalStateException("Can't find callback class: " + clazzName); | |||
| } | |||
| args[i] = getCallbackProxy(clazz, pid); | |||
| } | |||
| } | |||
| MethodExecutor executor = getMethodExecutor(request); | |||
| if (executor == null) { | |||
| String errMsg = String.format("The method '%s' you call was not exist!", request.getMethodName()); | |||
| return new Response(Response.STATUS_CODE_NOT_FOUND, errMsg, null); | |||
| } | |||
| return executor.execute(args); | |||
| } | |||
| RemoteCallbackList<ICallback> getCallbackList() { | |||
| return mCallbackList; | |||
| } | |||
| @SuppressWarnings("unchecked") // Single-interface proxy creation guarded by parameter safety. | |||
| private <T> T getCallbackProxy(final Class<T> service, final int pid) { | |||
| Utils.validateServiceInterface(service); | |||
| return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class<?>[] { service }, | |||
| new InvocationHandler() { | |||
| @Override | |||
| public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { | |||
| Object result = null; | |||
| final int len = mCallbackList.beginBroadcast(); | |||
| for (int i = 0; i < len; i++) { | |||
| int cookiePid = (int) mCallbackList.getBroadcastCookie(i); | |||
| if (cookiePid == pid) { | |||
| try { | |||
| Request request = createCallbackRequest(service.getSimpleName(), method.getName(), args); | |||
| Response response = mCallbackList.getBroadcastItem(i).callback(request); | |||
| result = response.getResult(); | |||
| if (response.getStatusCode() != Response.STATUS_CODE_SUCCESS) { | |||
| Logger.e(TAG, "Execute remote callback fail: " + response.toString()); | |||
| } | |||
| } catch (RemoteException e) { | |||
| Logger.e(TAG, "Error when execute callback!", e); | |||
| } | |||
| break; | |||
| } | |||
| } | |||
| mCallbackList.finishBroadcast(); | |||
| return result; | |||
| } | |||
| }); | |||
| } | |||
| private Request createCallbackRequest(String targetClass, String methodName, Object[] args) { | |||
| BaseTypeWrapper[] wrappers = new BaseTypeWrapper[args.length]; | |||
| for (int i = 0; i < args.length; i++) { | |||
| wrappers[i] = new InTypeWrapper(args[i], args[i].getClass()); | |||
| } | |||
| return new Request(targetClass, methodName, wrappers); | |||
| } | |||
| private Class<?> getCallbackClass(String className) { | |||
| return mCallbackClassTypes.get(className); | |||
| } | |||
| private String createMethodExecutorKey(String clsName, String methodName) { | |||
| StringBuilder sb = new StringBuilder(); | |||
| sb.append(clsName) | |||
| .append('-') | |||
| .append(methodName); | |||
| return sb.toString(); | |||
| } | |||
| private MethodExecutor getMethodExecutor(Request request) { | |||
| String key = createMethodExecutorKey(request.getTargetClass(), request.getMethodName()); | |||
| return mMethodExecutors.get(key); | |||
| } | |||
| } | |||
| @@ -0,0 +1,58 @@ | |||
| package com.codezjx.andlinker; | |||
| import android.os.Binder; | |||
| import android.os.RemoteCallbackList; | |||
| import android.os.RemoteException; | |||
| /** | |||
| * Created by codezjx on 2017/9/13.<br/> | |||
| */ | |||
| final class LinkerBinderImpl extends ITransfer.Stub implements AndLinkerBinder { | |||
| private static final String TAG = "LinkerBinder"; | |||
| private RemoteCallbackList<ICallback> mCallbackList; | |||
| private Invoker mInvoker; | |||
| LinkerBinderImpl() { | |||
| mInvoker = new Invoker(); | |||
| mCallbackList = mInvoker.getCallbackList(); | |||
| } | |||
| @Override | |||
| public void registerObject(Object target) { | |||
| mInvoker.registerObject(target); | |||
| } | |||
| @Override | |||
| public void unRegisterObject(Object target) { | |||
| mInvoker.unRegisterObject(target); | |||
| } | |||
| @Override | |||
| public Response execute(Request request) throws RemoteException { | |||
| for (BaseTypeWrapper wrapper : request.getArgsWrapper()) { | |||
| Logger.d(TAG, "Receive param, value:" + wrapper.getParam() | |||
| + " type:" + (wrapper.getParam() != null ? wrapper.getParam().getClass() : "null")); | |||
| } | |||
| Logger.d(TAG, "Receive request:" + request.getMethodName()); | |||
| return mInvoker.invoke(request); | |||
| } | |||
| @Override | |||
| public void register(ICallback callback) throws RemoteException { | |||
| int pid = Binder.getCallingPid(); | |||
| Logger.d(TAG, "register callback:" + callback + " pid:" + pid); | |||
| if (callback != null) { | |||
| mCallbackList.register(callback, pid); | |||
| } | |||
| } | |||
| @Override | |||
| public void unRegister(ICallback callback) throws RemoteException { | |||
| int pid = Binder.getCallingPid(); | |||
| Logger.d(TAG, "unRegister callback:" + callback + " pid:" + pid); | |||
| if (callback != null) { | |||
| mCallbackList.unregister(callback); | |||
| } | |||
| } | |||
| } | |||
| @@ -0,0 +1,127 @@ | |||
| package com.codezjx.andlinker; | |||
| import android.os.Build; | |||
| import android.util.Log; | |||
| import java.io.PrintWriter; | |||
| import java.io.StringWriter; | |||
| import java.util.regex.Matcher; | |||
| import java.util.regex.Pattern; | |||
| /** | |||
| * Created by codezjx on 2018/1/23.<br/> | |||
| */ | |||
| final class Logger { | |||
| private Logger() { | |||
| // private constructor | |||
| } | |||
| private static final String DEFAULT_LOG_TAG = "Logger"; | |||
| private static final int MAX_TAG_LENGTH = 23; | |||
| // 3 method calls inside Logger | |||
| private static final int CALL_STACK_INDEX = 3; | |||
| private static final Pattern ANONYMOUS_CLASS = Pattern.compile("(\\$\\d+)+$"); | |||
| static boolean sEnable = false; | |||
| static void v(String msg) { | |||
| log(Log.VERBOSE, null, msg, null); | |||
| } | |||
| static void v(String tag, String msg) { | |||
| log(Log.VERBOSE, tag, msg, null); | |||
| } | |||
| static void d(String msg) { | |||
| log(Log.DEBUG, null, msg, null); | |||
| } | |||
| static void d(String tag, String msg) { | |||
| log(Log.DEBUG, tag, msg, null); | |||
| } | |||
| static void i(String msg) { | |||
| log(Log.INFO, null, msg, null); | |||
| } | |||
| static void i(String tag, String msg) { | |||
| log(Log.INFO, tag, msg, null); | |||
| } | |||
| static void w(String msg) { | |||
| log(Log.WARN, null, msg, null); | |||
| } | |||
| static void w(String tag, String msg) { | |||
| log(Log.WARN, tag, msg, null); | |||
| } | |||
| static void w(String msg, Throwable t) { | |||
| log(Log.WARN, null, msg, t); | |||
| } | |||
| static void w(String tag, String msg, Throwable t) { | |||
| log(Log.WARN, tag, msg, t); | |||
| } | |||
| static void e(String msg) { | |||
| log(Log.ERROR, null, msg, null); | |||
| } | |||
| static void e(String tag, String msg) { | |||
| log(Log.ERROR, tag, msg, null); | |||
| } | |||
| static void e(String msg, Throwable t) { | |||
| log(Log.ERROR, null, msg, t); | |||
| } | |||
| static void e(String tag, String msg, Throwable t) { | |||
| log(Log.ERROR, tag, msg, t); | |||
| } | |||
| private static void log(int priority, String tag, String msg, Throwable t) { | |||
| if (!sEnable) { | |||
| return; | |||
| } | |||
| String curTag = (tag != null && tag.trim().length() > 0) ? tag : getTag(); | |||
| String curMsg = (t != null) ? (msg + '\n' + getStackTraceString(t)) : msg; | |||
| Log.println(priority, curTag, curMsg); | |||
| } | |||
| private static String getStackTraceString(Throwable t) { | |||
| if (t == null) { | |||
| return ""; | |||
| } | |||
| // Don't replace this with Log.getStackTraceString() - it hides | |||
| // UnknownHostException, which is not what we want. | |||
| StringWriter sw = new StringWriter(256); | |||
| PrintWriter pw = new PrintWriter(sw, false); | |||
| t.printStackTrace(pw); | |||
| pw.flush(); | |||
| return sw.toString(); | |||
| } | |||
| private static String getTag() { | |||
| StackTraceElement[] stackTrace = new Throwable().getStackTrace(); | |||
| if (stackTrace.length <= CALL_STACK_INDEX) { | |||
| Log.e(DEFAULT_LOG_TAG, "Synthetic stacktrace didn't have enough elements, use the default logger tag, are you using proguard?"); | |||
| return DEFAULT_LOG_TAG; | |||
| } | |||
| return createStackElementTag(stackTrace[CALL_STACK_INDEX]); | |||
| } | |||
| private static String createStackElementTag(StackTraceElement element) { | |||
| String tag = element.getClassName(); | |||
| Matcher m = ANONYMOUS_CLASS.matcher(tag); | |||
| if (m.find()) { | |||
| tag = m.replaceAll(""); | |||
| } | |||
| tag = tag.substring(tag.lastIndexOf('.') + 1); | |||
| // Tag length limit was removed in API 24. | |||
| if (tag.length() <= MAX_TAG_LENGTH || Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { | |||
| return tag; | |||
| } | |||
| return tag.substring(0, MAX_TAG_LENGTH); | |||
| } | |||
| } | |||
| @@ -0,0 +1,50 @@ | |||
| package com.codezjx.andlinker; | |||
| import java.lang.reflect.InvocationTargetException; | |||
| import java.lang.reflect.Method; | |||
| /** | |||
| * Created by codezjx on 2017/10/22.<br/> | |||
| */ | |||
| final class MethodExecutor { | |||
| /** Target object to invoke. */ | |||
| private final Object mTarget; | |||
| /** Method to execute. */ | |||
| private final Method mMethod; | |||
| MethodExecutor(Object target, Method method) { | |||
| if (target == null) { | |||
| throw new NullPointerException("Target cannot be null."); | |||
| } | |||
| if (method == null) { | |||
| throw new NullPointerException("Method cannot be null."); | |||
| } | |||
| mTarget = target; | |||
| mMethod = method; | |||
| method.setAccessible(true); | |||
| } | |||
| Response execute(Object[] args) { | |||
| Object result = null; | |||
| int statusCode = Response.STATUS_CODE_SUCCESS; | |||
| String resultMsg = String.format("Call method '%s' successfully!", mMethod.getName()); | |||
| Throwable throwable = null; | |||
| try { | |||
| result = mMethod.invoke(mTarget, args); | |||
| } catch (IllegalAccessException e) { | |||
| statusCode = Response.STATUS_CODE_ILLEGAL_ACCESS; | |||
| throwable = e; | |||
| } catch (InvocationTargetException e) { | |||
| statusCode = Response.STATUS_CODE_INVOCATION_FAIL; | |||
| throwable = e; | |||
| } | |||
| if (throwable != null) { | |||
| resultMsg = "Exception occur when execute method:" + mMethod.getName() + '\n' + throwable.getMessage(); | |||
| } | |||
| return new Response(statusCode, resultMsg, result); | |||
| } | |||
| } | |||
| @@ -0,0 +1,109 @@ | |||
| package com.codezjx.andlinker; | |||
| import android.os.Parcel; | |||
| import android.os.Parcelable; | |||
| import java.lang.reflect.InvocationTargetException; | |||
| import java.lang.reflect.Method; | |||
| import java.util.List; | |||
| import java.util.Map; | |||
| /** | |||
| * Created by codezjx on 2017/11/30.<br/> | |||
| */ | |||
| interface OutType<T> extends Type<T> { | |||
| void readFromParcel(Parcel in, T val); | |||
| final class ParcelableType implements OutType<Parcelable> { | |||
| private static final String TAG = "ParcelableType"; | |||
| @Override | |||
| public void writeToParcel(Parcel dest, int flags, Parcelable val) { | |||
| if (flags == Parcelable.PARCELABLE_WRITE_RETURN_VALUE) { | |||
| val.writeToParcel(dest, flags); | |||
| } else { | |||
| dest.writeParcelable(val, flags); | |||
| } | |||
| } | |||
| @Override | |||
| public Parcelable createFromParcel(Parcel in) { | |||
| return in.readParcelable(getClass().getClassLoader()); | |||
| } | |||
| @Override | |||
| public void readFromParcel(Parcel in, Parcelable val) { | |||
| if (val instanceof SuperParcelable) { | |||
| ((SuperParcelable) val).readFromParcel(in); | |||
| return; | |||
| } | |||
| Method method = Utils.getMethodReadFromParcel(val.getClass()); | |||
| if (method == null) { | |||
| throw new IllegalArgumentException("Parcelable parameter with @Out or @Inout annotation must " + | |||
| "declare the public readFromParcel() method or implements interface SuperParcelable. " + | |||
| "Error parameter type: " + val.getClass().getName()); | |||
| } | |||
| try { | |||
| method.invoke(val, in); | |||
| } catch (IllegalAccessException e) { | |||
| Logger.e(TAG, "Can't access method readFromParcel().", e); | |||
| } catch (InvocationTargetException e) { | |||
| Logger.e(TAG, "Method readFromParcel() throws an exception.", e); | |||
| } | |||
| } | |||
| } | |||
| final class ListType implements OutType<List> { | |||
| @Override | |||
| public void writeToParcel(Parcel dest, int flags, List val) { | |||
| dest.writeList(val); | |||
| } | |||
| @Override | |||
| public List createFromParcel(Parcel in) { | |||
| return in.readArrayList(getClass().getClassLoader()); | |||
| } | |||
| @Override | |||
| public void readFromParcel(Parcel in, List val) { | |||
| // Clear the list before read list | |||
| val.clear(); | |||
| int N = in.readInt(); | |||
| while (N > 0) { | |||
| Object value = in.readValue(getClass().getClassLoader()); | |||
| val.add(value); | |||
| N--; | |||
| } | |||
| } | |||
| } | |||
| final class MapType implements OutType<Map> { | |||
| @Override | |||
| public void writeToParcel(Parcel dest, int flags, Map val) { | |||
| dest.writeMap(val); | |||
| } | |||
| @Override | |||
| public Map createFromParcel(Parcel in) { | |||
| return in.readHashMap(getClass().getClassLoader()); | |||
| } | |||
| @Override | |||
| public void readFromParcel(Parcel in, Map val) { | |||
| ClassLoader loader = getClass().getClassLoader(); | |||
| // Clear the map before read map | |||
| val.clear(); | |||
| int N = in.readInt(); | |||
| while (N > 0) { | |||
| Object key = in.readValue(loader); | |||
| Object value = in.readValue(loader); | |||
| val.put(key, value); | |||
| N--; | |||
| } | |||
| } | |||
| } | |||
| } | |||
| @@ -0,0 +1,95 @@ | |||
| package com.codezjx.andlinker; | |||
| import android.os.Parcel; | |||
| import java.lang.reflect.Array; | |||
| import java.util.ArrayList; | |||
| import java.util.HashMap; | |||
| /** | |||
| * Created by codezjx on 2017/11/30.<br/> | |||
| */ | |||
| final class OutTypeWrapper implements BaseTypeWrapper { | |||
| private int mType; | |||
| private Object mParam; | |||
| OutTypeWrapper(Object param, Class<?> mParamType) { | |||
| mType = Utils.getTypeByClass(mParamType); | |||
| mParam = param; | |||
| } | |||
| @Override | |||
| public int getType() { | |||
| return mType; | |||
| } | |||
| @Override | |||
| public Object getParam() { | |||
| return mParam; | |||
| } | |||
| @Override | |||
| public int describeContents() { | |||
| return 0; | |||
| } | |||
| @Override | |||
| public void writeToParcel(Parcel dest, int flags) { | |||
| dest.writeInt(mType); | |||
| Type type = TypeFactory.getType(mType); | |||
| if (flags == PARCELABLE_WRITE_RETURN_VALUE) { | |||
| type.writeToParcel(dest, flags, mParam); | |||
| } else { | |||
| if (mType == BaseTypeWrapper.TYPE_PARCELABLE) { | |||
| dest.writeString(mParam.getClass().getName()); | |||
| } else if (mType == BaseTypeWrapper.TYPE_PARCELABLEARRAY) { | |||
| dest.writeInt(Array.getLength(mParam)); | |||
| dest.writeString(mParam.getClass().getComponentType().getName()); | |||
| } else if (Utils.isArrayType(mType)) { | |||
| // array type just write length | |||
| dest.writeInt(Array.getLength(mParam)); | |||
| } | |||
| } | |||
| } | |||
| @Override | |||
| public void readFromParcel(Parcel in) { | |||
| mType = in.readInt(); | |||
| OutType type = (OutType) TypeFactory.getType(mType); | |||
| type.readFromParcel(in, mParam); | |||
| } | |||
| private OutTypeWrapper(Parcel in) { | |||
| mType = in.readInt(); | |||
| if (mType == BaseTypeWrapper.TYPE_PARCELABLE) { | |||
| String clsName = in.readString(); | |||
| mParam = Utils.createObjFromClassName(clsName); | |||
| } else if (mType == BaseTypeWrapper.TYPE_PARCELABLEARRAY) { | |||
| int length = in.readInt(); | |||
| String componentType = in.readString(); | |||
| mParam = Utils.createArrayFromComponentType(componentType, length); | |||
| } else if (mType == BaseTypeWrapper.TYPE_LIST) { | |||
| mParam = new ArrayList(); | |||
| } else if (mType == BaseTypeWrapper.TYPE_MAP) { | |||
| mParam = new HashMap(); | |||
| } else if (Utils.isArrayType(mType)) { | |||
| int length = in.readInt(); | |||
| ArrayType type = (ArrayType) TypeFactory.getType(mType); | |||
| mParam = type.newInstance(length); | |||
| } | |||
| } | |||
| public static final Creator<OutTypeWrapper> CREATOR = new Creator<OutTypeWrapper>() { | |||
| @Override | |||
| public OutTypeWrapper createFromParcel(Parcel source) { | |||
| return new OutTypeWrapper(source); | |||
| } | |||
| @Override | |||
| public OutTypeWrapper[] newArray(int size) { | |||
| return new OutTypeWrapper[size]; | |||
| } | |||
| }; | |||
| } | |||
| @@ -0,0 +1,80 @@ | |||
| package com.codezjx.andlinker; | |||
| import com.codezjx.andlinker.annotation.In; | |||
| import com.codezjx.andlinker.annotation.Inout; | |||
| import com.codezjx.andlinker.annotation.Out; | |||
| import java.lang.annotation.Annotation; | |||
| /** | |||
| * Created by codezjx on 2017/9/17.<br/> | |||
| */ | |||
| interface ParameterHandler<T> { | |||
| void apply(RequestBuilder builder, T value, int index); | |||
| final class CallbackHandler<T> implements ParameterHandler<T> { | |||
| Class<?> mParamType; | |||
| CallbackHandler(Class<?> paramType) { | |||
| mParamType = paramType; | |||
| } | |||
| @Override | |||
| public void apply(RequestBuilder builder, T value, int index) { | |||
| Logger.d("CallbackHandler", "ParameterHandler mParamType:" + mParamType + " value:" + value); | |||
| String className = mParamType.getSimpleName(); | |||
| CallbackTypeWrapper wrapper = new CallbackTypeWrapper(className); | |||
| builder.applyWrapper(index, wrapper); | |||
| } | |||
| } | |||
| final class ParamDirectionHandler<T> implements ParameterHandler<T> { | |||
| Annotation mAnnotation; | |||
| Class<?> mParamType; | |||
| ParamDirectionHandler(Annotation annotation, Class<?> paramType) { | |||
| mAnnotation = annotation; | |||
| mParamType = paramType; | |||
| } | |||
| @Override | |||
| public void apply(RequestBuilder builder, T value, int index) { | |||
| Logger.d("ParamDirectionHandler", " mParamType:" + mParamType + " value:" + value + " index:" + index); | |||
| if (Utils.canOnlyBeInType(mParamType) && !(mAnnotation instanceof In)) { | |||
| throw new IllegalArgumentException("Primitives are in by default, and cannot be otherwise."); | |||
| } | |||
| BaseTypeWrapper wrapper = null; | |||
| if (mAnnotation instanceof In) { | |||
| wrapper = new InTypeWrapper(value, mParamType); | |||
| } else if (mAnnotation instanceof Out) { | |||
| wrapper = new OutTypeWrapper(value, mParamType); | |||
| } else if (mAnnotation instanceof Inout) { | |||
| wrapper = new InOutTypeWrapper(value, mParamType); | |||
| } | |||
| builder.applyWrapper(index, wrapper); | |||
| } | |||
| } | |||
| final class DefaultParameterHandler<T> implements ParameterHandler<T> { | |||
| Class<?> mParamType; | |||
| DefaultParameterHandler(Class<?> paramType) { | |||
| mParamType = paramType; | |||
| } | |||
| @Override | |||
| public void apply(RequestBuilder builder, T value, int index) { | |||
| if (Utils.canOnlyBeInType(mParamType)) { | |||
| InTypeWrapper wrapper = new InTypeWrapper(value, mParamType); | |||
| builder.applyWrapper(index, wrapper); | |||
| } else { | |||
| throw new IllegalArgumentException("Parameter type '" + mParamType.getSimpleName() + "' can be an out type, so you must declare it as @In, @Out or @Inout."); | |||
| } | |||
| } | |||
| } | |||
| } | |||
| @@ -0,0 +1,131 @@ | |||
| package com.codezjx.andlinker; | |||
| import android.os.RemoteException; | |||
| import static com.codezjx.andlinker.Utils.checkNotNull; | |||
| /** | |||
| * Created by codezjx on 2017/10/14.<br/> | |||
| */ | |||
| final class RemoteCall implements Call<Object> { | |||
| private static final String TAG = "RemoteCall"; | |||
| private final ITransfer mTransferService; | |||
| private final ServiceMethod mServiceMethod; | |||
| private final Object[] mArgs; | |||
| private final Dispatcher mDispatcher; | |||
| private volatile boolean mExecuted; | |||
| private volatile boolean mCanceled; | |||
| RemoteCall(ITransfer transferService, ServiceMethod serviceMethod, Object[] args, Dispatcher dispatcher) { | |||
| mTransferService = transferService; | |||
| mServiceMethod = serviceMethod; | |||
| mArgs = args; | |||
| mDispatcher = dispatcher; | |||
| } | |||
| private Object executeInternal() throws RemoteException { | |||
| if (mTransferService == null) { | |||
| Logger.e(TAG, "TransferService null when execute, this may occur if the client is not already bind to the service."); | |||
| return null; | |||
| } | |||
| @SuppressWarnings("unchecked") | |||
| ParameterHandler<Object>[] handlers = (ParameterHandler<Object>[]) mServiceMethod.getParameterHandlers(); | |||
| int argumentCount = mArgs != null ? mArgs.length : 0; | |||
| if (argumentCount != handlers.length) { | |||
| throw new IllegalArgumentException("Argument count (" + argumentCount | |||
| + ") doesn't match expected count (" + handlers.length + ")"); | |||
| } | |||
| RequestBuilder requestBuilder = new RequestBuilder(mServiceMethod.getClassName(), mServiceMethod.getMethodName(), argumentCount, mServiceMethod.isOneWay()); | |||
| for (int p = 0; p < argumentCount; p++) { | |||
| handlers[p].apply(requestBuilder, mArgs[p], p); | |||
| } | |||
| Response response = mTransferService.execute(requestBuilder.build()); | |||
| // May return null in one-way mode. | |||
| if (response == null) { | |||
| return null; | |||
| } | |||
| if (response.getStatusCode() != Response.STATUS_CODE_SUCCESS) { | |||
| Logger.e(TAG, "Execute remote call fail: " + response.toString()); | |||
| } | |||
| return response.getResult(); | |||
| } | |||
| @Override | |||
| public Object execute() { | |||
| checkExecuted(); | |||
| if (mCanceled) { | |||
| Logger.w(TAG, "Already canceled"); | |||
| return null; | |||
| } | |||
| Object result = null; | |||
| try { | |||
| result = executeInternal(); | |||
| } catch (RemoteException e) { | |||
| e.printStackTrace(); | |||
| } | |||
| return result; | |||
| } | |||
| @Override | |||
| public void enqueue(Callback<Object> callback) { | |||
| checkNotNull(callback, "callback == null"); | |||
| checkExecuted(); | |||
| if (mCanceled) { | |||
| Logger.w(TAG, "Already canceled"); | |||
| return; | |||
| } | |||
| mDispatcher.enqueue(new AsyncCall(callback)); | |||
| } | |||
| private void checkExecuted() { | |||
| synchronized (this) { | |||
| if (mExecuted) { | |||
| throw new IllegalStateException("Already executed."); | |||
| } | |||
| mExecuted = true; | |||
| } | |||
| } | |||
| final class AsyncCall implements Runnable { | |||
| private Callback<Object> mCallback; | |||
| AsyncCall(Callback<Object> callback) { | |||
| mCallback = callback; | |||
| } | |||
| @Override | |||
| public void run() { | |||
| try { | |||
| Object result = executeInternal(); | |||
| mCallback.onResponse(RemoteCall.this, result); | |||
| } catch (RemoteException e) { | |||
| e.printStackTrace(); | |||
| mCallback.onFailure(RemoteCall.this, e); | |||
| } | |||
| } | |||
| } | |||
| @Override | |||
| public synchronized boolean isExecuted() { | |||
| return mExecuted; | |||
| } | |||
| @Override | |||
| public void cancel() { | |||
| mCanceled = true; | |||
| } | |||
| @Override | |||
| public boolean isCanceled() { | |||
| return mCanceled; | |||
| } | |||
| } | |||
| @@ -0,0 +1,134 @@ | |||
| package com.codezjx.andlinker; | |||
| import android.os.Parcel; | |||
| import android.os.Parcelable; | |||
| import java.lang.reflect.Array; | |||
| import java.util.Arrays; | |||
| /** | |||
| * Created by codezjx on 2017/9/13.<br/> | |||
| */ | |||
| final class Request implements SuperParcelable { | |||
| private String mTargetClass; | |||
| private String mMethodName; | |||
| private BaseTypeWrapper[] mArgsWrapper; | |||
| // This field use for client slide only | |||
| private boolean mOneWay = false; | |||
| Request(String targetClass, String methodName, BaseTypeWrapper[] argsWrapper) { | |||
| this(targetClass, methodName, argsWrapper, false); | |||
| } | |||
| Request(String targetClass, String methodName, BaseTypeWrapper[] argsWrapper, boolean oneWay) { | |||
| mTargetClass = targetClass; | |||
| mMethodName = methodName; | |||
| mArgsWrapper = argsWrapper; | |||
| mOneWay = oneWay; | |||
| } | |||
| @Override | |||
| public int describeContents() { | |||
| return 0; | |||
| } | |||
| @Override | |||
| public void writeToParcel(Parcel dest, int flags) { | |||
| dest.writeString(mTargetClass); | |||
| dest.writeString(mMethodName); | |||
| if (flags == PARCELABLE_WRITE_RETURN_VALUE) { | |||
| writeParcelableArrayToParcel(dest, mArgsWrapper, flags); | |||
| } else { | |||
| dest.writeParcelableArray(mArgsWrapper, flags); | |||
| } | |||
| } | |||
| @Override | |||
| public void readFromParcel(Parcel in) { | |||
| mTargetClass = in.readString(); | |||
| mMethodName = in.readString(); | |||
| readParcelableArrayFromParcel(in, mArgsWrapper); | |||
| } | |||
| private Request(Parcel in) { | |||
| mTargetClass = in.readString(); | |||
| mMethodName = in.readString(); | |||
| mArgsWrapper = readParcelableArray(getClass().getClassLoader(), BaseTypeWrapper.class, in); | |||
| } | |||
| private <T extends BaseTypeWrapper> void writeParcelableArrayToParcel(Parcel dest, T[] value, int parcelableFlags) { | |||
| if (value != null) { | |||
| int N = value.length; | |||
| dest.writeInt(N); | |||
| for (int i = 0; i < N; i++) { | |||
| value[i].writeToParcel(dest, parcelableFlags); | |||
| } | |||
| } else { | |||
| dest.writeInt(-1); | |||
| } | |||
| } | |||
| private <T extends BaseTypeWrapper> void readParcelableArrayFromParcel(Parcel in, T[] value) { | |||
| int N = in.readInt(); | |||
| if (N < 0) { | |||
| return; | |||
| } | |||
| for (int i = 0; i < N; i++) { | |||
| value[i].readFromParcel(in); | |||
| } | |||
| } | |||
| /** | |||
| * Code from {@link Parcel}.readParcelableArray(ClassLoader loader, Class<T> clazz), it's a hide method. | |||
| */ | |||
| @SuppressWarnings("unchecked") | |||
| private <T extends Parcelable> T[] readParcelableArray(ClassLoader loader, Class<T> clazz, Parcel in) { | |||
| int N = in.readInt(); | |||
| if (N < 0) { | |||
| return null; | |||
| } | |||
| T[] p = (T[]) Array.newInstance(clazz, N); | |||
| for (int i = 0; i < N; i++) { | |||
| p[i] = in.readParcelable(loader); | |||
| } | |||
| return p; | |||
| } | |||
| public static final Creator<Request> CREATOR = new Creator<Request>() { | |||
| @Override | |||
| public Request createFromParcel(Parcel source) { | |||
| return new Request(source); | |||
| } | |||
| @Override | |||
| public Request[] newArray(int size) { | |||
| return new Request[size]; | |||
| } | |||
| }; | |||
| String getTargetClass() { | |||
| return mTargetClass; | |||
| } | |||
| String getMethodName() { | |||
| return mMethodName; | |||
| } | |||
| boolean isOneWay() { | |||
| return mOneWay; | |||
| } | |||
| BaseTypeWrapper[] getArgsWrapper() { | |||
| return mArgsWrapper; | |||
| } | |||
| @Override | |||
| public String toString() { | |||
| return "Request{" + | |||
| "mTargetClass='" + mTargetClass + '\'' + | |||
| ", mMethodName='" + mMethodName + '\'' + | |||
| ", mArgsWrapper=" + Arrays.toString(mArgsWrapper) + | |||
| '}'; | |||
| } | |||
| } | |||
| @@ -0,0 +1,31 @@ | |||
| package com.codezjx.andlinker; | |||
| /** | |||
| * Created by codezjx on 2017/11/5.<br/> | |||
| */ | |||
| final class RequestBuilder { | |||
| private String mTargetClass; | |||
| private String mMethodName; | |||
| private BaseTypeWrapper[] mParameterWrappers; | |||
| private boolean mOneWay; | |||
| RequestBuilder(String targetClass, String methodName, int argumentCount, boolean oneWay) { | |||
| mTargetClass = targetClass; | |||
| mMethodName = methodName; | |||
| mParameterWrappers = new BaseTypeWrapper[argumentCount]; | |||
| mOneWay = oneWay; | |||
| } | |||
| void applyWrapper(int index, BaseTypeWrapper wrapper) { | |||
| if (index < 0 || index >= mParameterWrappers.length) { | |||
| throw new IllegalArgumentException("Index out of range."); | |||
| } | |||
| mParameterWrappers[index] = wrapper; | |||
| } | |||
| Request build() { | |||
| return new Request(mTargetClass, mMethodName, mParameterWrappers, mOneWay); | |||
| } | |||
| } | |||
| @@ -0,0 +1,80 @@ | |||
| package com.codezjx.andlinker; | |||
| import android.os.Parcel; | |||
| /** | |||
| * Created by codezjx on 2017/9/13.<br/> | |||
| */ | |||
| final class Response implements SuperParcelable { | |||
| static final int STATUS_CODE_SUCCESS = 200; | |||
| static final int STATUS_CODE_ILLEGAL_ACCESS = 400; | |||
| static final int STATUS_CODE_INVOCATION_FAIL = 401; | |||
| static final int STATUS_CODE_NOT_FOUND = 404; | |||
| private int mStatusCode; | |||
| private String mStatusMessage; | |||
| private Object mResult; | |||
| Response(int statusCode, String statusMessage, Object result) { | |||
| mStatusCode = statusCode; | |||
| mStatusMessage = statusMessage; | |||
| mResult = result; | |||
| } | |||
| @Override | |||
| public int describeContents() { | |||
| return 0; | |||
| } | |||
| @Override | |||
| public void writeToParcel(Parcel dest, int flags) { | |||
| dest.writeInt(mStatusCode); | |||
| dest.writeString(mStatusMessage); | |||
| dest.writeValue(mResult); | |||
| } | |||
| @Override | |||
| public void readFromParcel(Parcel in) { | |||
| mStatusCode = in.readInt(); | |||
| mStatusMessage = in.readString(); | |||
| mResult = in.readValue(getClass().getClassLoader()); | |||
| } | |||
| private Response(Parcel in) { | |||
| readFromParcel(in); | |||
| } | |||
| public static final Creator<Response> CREATOR = new Creator<Response>() { | |||
| @Override | |||
| public Response createFromParcel(Parcel source) { | |||
| return new Response(source); | |||
| } | |||
| @Override | |||
| public Response[] newArray(int size) { | |||
| return new Response[size]; | |||
| } | |||
| }; | |||
| int getStatusCode() { | |||
| return mStatusCode; | |||
| } | |||
| String getStatusMessage() { | |||
| return mStatusMessage; | |||
| } | |||
| Object getResult() { | |||
| return mResult; | |||
| } | |||
| @Override | |||
| public String toString() { | |||
| return "Response{" + | |||
| "mStatusCode=" + mStatusCode + | |||
| ", mStatusMessage='" + mStatusMessage + '\'' + | |||
| ", mResult=" + mResult + | |||
| '}'; | |||
| } | |||
| } | |||
| @@ -0,0 +1,159 @@ | |||
| package com.codezjx.andlinker; | |||
| import com.codezjx.andlinker.annotation.Callback; | |||
| import com.codezjx.andlinker.annotation.In; | |||
| import com.codezjx.andlinker.annotation.Inout; | |||
| import com.codezjx.andlinker.annotation.OneWay; | |||
| import com.codezjx.andlinker.annotation.Out; | |||
| import java.lang.annotation.Annotation; | |||
| import java.lang.reflect.Method; | |||
| import java.lang.reflect.Type; | |||
| /** | |||
| * Created by codezjx on 2017/9/14.<br/> | |||
| * Adapts an invocation of an interface method into an AIDL call. | |||
| */ | |||
| final class ServiceMethod { | |||
| private static final String TAG = "ServiceMethod"; | |||
| private CallAdapter mCallAdapter; | |||
| private String mClassName; | |||
| private String mMethodName; | |||
| private boolean mOneWay; | |||
| private ParameterHandler<?>[] mParameterHandlers; | |||
| ServiceMethod(Builder builder) { | |||
| mCallAdapter = builder.mCallAdapter; | |||
| mClassName = builder.mClassName; | |||
| mMethodName = builder.mMethodName; | |||
| mOneWay = builder.mOneWay; | |||
| mParameterHandlers = builder.mParameterHandlers; | |||
| } | |||
| CallAdapter getCallAdapter() { | |||
| return mCallAdapter; | |||
| } | |||
| String getClassName() { | |||
| return mClassName; | |||
| } | |||
| String getMethodName() { | |||
| return mMethodName; | |||
| } | |||
| boolean isOneWay() { | |||
| return mOneWay; | |||
| } | |||
| ParameterHandler<?>[] getParameterHandlers() { | |||
| return mParameterHandlers; | |||
| } | |||
| static final class Builder { | |||
| private AndLinker mLinker; | |||
| private Method mMethod; | |||
| private Annotation[] mMethodAnnotations; | |||
| private Annotation[][] mParameterAnnotationsArray; | |||
| private Type[] mParameterTypes; | |||
| private CallAdapter mCallAdapter; | |||
| private String mClassName = ""; | |||
| private String mMethodName = ""; | |||
| private boolean mOneWay = false; | |||
| private ParameterHandler<?>[] mParameterHandlers; | |||
| Builder(AndLinker linker, Method method) { | |||
| mLinker = linker; | |||
| mMethod = method; | |||
| mMethodAnnotations = method.getAnnotations(); | |||
| mParameterAnnotationsArray = method.getParameterAnnotations(); | |||
| mParameterTypes = method.getGenericParameterTypes(); | |||
| } | |||
| ServiceMethod build() { | |||
| mCallAdapter = createCallAdapter(); | |||
| mClassName = mMethod.getDeclaringClass().getSimpleName(); | |||
| mMethodName = mMethod.getName(); | |||
| for (Annotation annotation : mMethodAnnotations) { | |||
| if (annotation instanceof OneWay) { | |||
| mOneWay = true; | |||
| break; | |||
| } | |||
| } | |||
| int parameterCount = mParameterAnnotationsArray.length; | |||
| mParameterHandlers = new ParameterHandler<?>[parameterCount]; | |||
| for (int p = 0; p < parameterCount; p++) { | |||
| Type parameterType = mParameterTypes[p]; | |||
| if (Utils.hasUnresolvableType(parameterType)) { | |||
| throw parameterError(p, "Parameter type must not include a type variable or wildcard: %s", | |||
| parameterType); | |||
| } | |||
| Annotation[] parameterAnnotations = mParameterAnnotationsArray[p]; | |||
| mParameterHandlers[p] = parseParameter(p, parameterType, parameterAnnotations); | |||
| } | |||
| return new ServiceMethod(this); | |||
| } | |||
| private CallAdapter createCallAdapter() { | |||
| Type returnType = mMethod.getGenericReturnType(); | |||
| if (Utils.hasUnresolvableType(returnType)) { | |||
| throw methodError( | |||
| "Method return type must not include a type variable or wildcard: %s", returnType); | |||
| } | |||
| Annotation[] annotations = mMethod.getAnnotations(); | |||
| try { | |||
| //noinspection unchecked | |||
| return mLinker.findCallAdapter(returnType, annotations); | |||
| } catch (RuntimeException e) { // Wide exception range because factories are user code. | |||
| throw methodError(e, "Unable to create call adapter for %s", returnType); | |||
| } | |||
| } | |||
| private ParameterHandler<?> parseParameter(int p, Type parameterType, Annotation[] annotations) { | |||
| Class<?> rawParameterType = Utils.getRawType(parameterType); | |||
| if (annotations == null || annotations.length == 0) { | |||
| return new ParameterHandler.DefaultParameterHandler<>(rawParameterType); | |||
| } | |||
| for (Annotation annotation : annotations) { | |||
| if (annotation instanceof Callback) { | |||
| return new ParameterHandler.CallbackHandler<>(rawParameterType); | |||
| } else if (annotation instanceof In || annotation instanceof Out || annotation instanceof Inout) { | |||
| return new ParameterHandler.ParamDirectionHandler<>(annotation, rawParameterType); | |||
| } | |||
| } | |||
| throw parameterError(p, "No support annotation found."); | |||
| } | |||
| private RuntimeException methodError(String message, Object... args) { | |||
| return methodError(null, message, args); | |||
| } | |||
| private RuntimeException methodError(Throwable cause, String message, Object... args) { | |||
| message = String.format(message, args); | |||
| return new IllegalArgumentException(message | |||
| + "\n for method " | |||
| + mMethod.getDeclaringClass().getSimpleName() | |||
| + "." | |||
| + mMethod.getName(), cause); | |||
| } | |||
| private RuntimeException parameterError( | |||
| Throwable cause, int p, String message, Object... args) { | |||
| return methodError(cause, message + " (parameter #" + (p + 1) + ")", args); | |||
| } | |||
| private RuntimeException parameterError(int p, String message, Object... args) { | |||
| return methodError(message + " (parameter #" + (p + 1) + ")", args); | |||
| } | |||
| } | |||
| } | |||
| @@ -0,0 +1,17 @@ | |||
| package com.codezjx.andlinker; | |||
| import android.os.Parcel; | |||
| import android.os.Parcelable; | |||
| /** | |||
| * Parcelable parameter classes with @Out or @Inout annotation must inherit from this class. | |||
| */ | |||
| public interface SuperParcelable extends Parcelable { | |||
| /** | |||
| * Reads the parcel contents into this object, so we can restore it. | |||
| * @param in The parcel to overwrite this object from. | |||
| */ | |||
| void readFromParcel(Parcel in); | |||
| } | |||
| @@ -0,0 +1,166 @@ | |||
| package com.codezjx.andlinker; | |||
| import android.os.Parcel; | |||
| import android.text.TextUtils; | |||
| /** | |||
| * Created by codezjx on 2017/11/28.<br/> | |||
| */ | |||
| interface Type<T> { | |||
| void writeToParcel(Parcel dest, int flags, T val); | |||
| T createFromParcel(Parcel in); | |||
| final class EmptyType implements Type<Object> { | |||
| @Override | |||
| public void writeToParcel(Parcel dest, int flags, Object val) { | |||
| // Nothing to do | |||
| } | |||
| @Override | |||
| public Object createFromParcel(Parcel in) { | |||
| return null; | |||
| } | |||
| } | |||
| final class ByteType implements Type<Byte> { | |||
| @Override | |||
| public void writeToParcel(Parcel dest, int flags, Byte val) { | |||
| dest.writeByte(val); | |||
| } | |||
| @Override | |||
| public Byte createFromParcel(Parcel in) { | |||
| return in.readByte(); | |||
| } | |||
| } | |||
| final class ShortType implements Type<Short> { | |||
| @Override | |||
| public void writeToParcel(Parcel dest, int flags, Short val) { | |||
| dest.writeInt(val.intValue()); | |||
| } | |||
| @Override | |||
| public Short createFromParcel(Parcel in) { | |||
| return (short) in.readInt(); | |||
| } | |||
| } | |||
| final class IntType implements Type<Integer> { | |||
| @Override | |||
| public void writeToParcel(Parcel dest, int flags, Integer val) { | |||
| dest.writeInt(val); | |||
| } | |||
| @Override | |||
| public Integer createFromParcel(Parcel in) { | |||
| return in.readInt(); | |||
| } | |||
| } | |||
| final class LongType implements Type<Long> { | |||
| @Override | |||
| public void writeToParcel(Parcel dest, int flags, Long val) { | |||
| dest.writeLong(val); | |||
| } | |||
| @Override | |||
| public Long createFromParcel(Parcel in) { | |||
| return in.readLong(); | |||
| } | |||
| } | |||
| final class FloatType implements Type<Float> { | |||
| @Override | |||
| public void writeToParcel(Parcel dest, int flags, Float val) { | |||
| dest.writeFloat(val); | |||
| } | |||
| @Override | |||
| public Float createFromParcel(Parcel in) { | |||
| return in.readFloat(); | |||
| } | |||
| } | |||
| final class DoubleType implements Type<Double> { | |||
| @Override | |||
| public void writeToParcel(Parcel dest, int flags, Double val) { | |||
| dest.writeDouble(val); | |||
| } | |||
| @Override | |||
| public Double createFromParcel(Parcel in) { | |||
| return in.readDouble(); | |||
| } | |||
| } | |||
| final class BooleanType implements Type<Boolean> { | |||
| @Override | |||
| public void writeToParcel(Parcel dest, int flags, Boolean val) { | |||
| dest.writeInt(val ? 1 : 0); | |||
| } | |||
| @Override | |||
| public Boolean createFromParcel(Parcel in) { | |||
| return in.readInt() == 1; | |||
| } | |||
| } | |||
| final class CharType implements Type<Character> { | |||
| @Override | |||
| public void writeToParcel(Parcel dest, int flags, Character val) { | |||
| dest.writeInt(((int) val)); | |||
| } | |||
| @Override | |||
| public Character createFromParcel(Parcel in) { | |||
| return (char) in.readInt(); | |||
| } | |||
| } | |||
| final class StringType implements Type<String> { | |||
| @Override | |||
| public void writeToParcel(Parcel dest, int flags, String val) { | |||
| dest.writeString(val); | |||
| } | |||
| @Override | |||
| public String createFromParcel(Parcel in) { | |||
| return in.readString(); | |||
| } | |||
| } | |||
| final class CharSequenceType implements Type<CharSequence> { | |||
| @Override | |||
| public void writeToParcel(Parcel dest, int flags, CharSequence val) { | |||
| writeCharSequence(dest, val); | |||
| } | |||
| @Override | |||
| public CharSequence createFromParcel(Parcel in) { | |||
| return readCharSequence(in); | |||
| } | |||
| static void writeCharSequence(Parcel dest, CharSequence val) { | |||
| TextUtils.writeToParcel(val, dest, 0); | |||
| } | |||
| static CharSequence readCharSequence(Parcel in) { | |||
| return TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in); | |||
| } | |||
| } | |||
| } | |||
| @@ -0,0 +1,30 @@ | |||
| package com.codezjx.andlinker; | |||
| /** | |||
| * Created by codezjx on 2017/11/28.<br/> | |||
| */ | |||
| final class TypeFactory { | |||
| private TypeFactory() { | |||
| } | |||
| private static Type[] sTypeArr = { | |||
| new Type.EmptyType(), | |||
| new Type.ByteType(), new Type.ShortType(), new Type.IntType(), new Type.LongType(), | |||
| new Type.FloatType(), new Type.DoubleType(), new Type.BooleanType(), new Type.CharType(), | |||
| new ArrayType.ByteArrayType(), new ArrayType.ShortArrayType(), new ArrayType.IntArrayType(), new ArrayType.LongArrayType(), | |||
| new ArrayType.FloatArrayType(), new ArrayType.DoubleArrayType(), new ArrayType.BooleanArrayType(), new ArrayType.CharArrayType(), | |||
| new Type.StringType(), new ArrayType.StringArrayType(), new Type.CharSequenceType(), new ArrayType.CharSequenceArrayType(), | |||
| new OutType.ParcelableType(), new ArrayType.ParcelableArrayType(), new OutType.ListType(), new OutType.MapType() | |||
| }; | |||
| static Type getType(int type) { | |||
| return sTypeArr[type]; | |||
| } | |||
| static Type getType(Class<?> classType) { | |||
| return sTypeArr[Utils.getTypeByClass(classType)]; | |||
| } | |||
| } | |||
| @@ -0,0 +1,227 @@ | |||
| package com.codezjx.andlinker; | |||
| import android.os.Parcel; | |||
| import android.os.Parcelable; | |||
| import java.lang.reflect.Array; | |||
| import java.lang.reflect.GenericArrayType; | |||
| import java.lang.reflect.Method; | |||
| import java.lang.reflect.ParameterizedType; | |||
| import java.lang.reflect.Type; | |||
| import java.lang.reflect.TypeVariable; | |||
| import java.lang.reflect.WildcardType; | |||
| import java.util.List; | |||
| import java.util.Map; | |||
| /** | |||
| * Created by codezjx on 2017/9/14.<br/> | |||
| */ | |||
| final class Utils { | |||
| private static final String TAG = "Utils"; | |||
| private Utils() { | |||
| // private constructor | |||
| } | |||
| static <T> void validateServiceInterface(Class<T> service) { | |||
| if (!service.isInterface()) { | |||
| throw new IllegalArgumentException("API declarations must be interfaces."); | |||
| } | |||
| // Prevent API interfaces from extending other interfaces. This not only avoids a bug in | |||
| // Android (http://b.android.com/58753) but it forces composition of API declarations which is | |||
| // the recommended pattern. | |||
| if (service.getInterfaces().length > 0) { | |||
| throw new IllegalArgumentException("API interfaces must not extend other interfaces."); | |||
| } | |||
| } | |||
| static boolean hasUnresolvableType(Type type) { | |||
| if (type instanceof Class<?>) { | |||
| return false; | |||
| } | |||
| if (type instanceof ParameterizedType) { | |||
| ParameterizedType parameterizedType = (ParameterizedType) type; | |||
| for (Type typeArgument : parameterizedType.getActualTypeArguments()) { | |||
| if (hasUnresolvableType(typeArgument)) { | |||
| return true; | |||
| } | |||
| } | |||
| return false; | |||
| } | |||
| if (type instanceof GenericArrayType) { | |||
| return hasUnresolvableType(((GenericArrayType) type).getGenericComponentType()); | |||
| } | |||
| if (type instanceof TypeVariable) { | |||
| return true; | |||
| } | |||
| if (type instanceof WildcardType) { | |||
| return true; | |||
| } | |||
| String className = type == null ? "null" : type.getClass().getName(); | |||
| throw new IllegalArgumentException("Expected a Class, ParameterizedType, or " | |||
| + "GenericArrayType, but <" + type + "> is of type " + className); | |||
| } | |||
| static Class<?> getRawType(Type type) { | |||
| checkNotNull(type, "type == null"); | |||
| if (type instanceof Class<?>) { | |||
| // Type is a normal class. | |||
| return (Class<?>) type; | |||
| } | |||
| if (type instanceof ParameterizedType) { | |||
| ParameterizedType parameterizedType = (ParameterizedType) type; | |||
| // I'm not exactly sure why getRawType() returns Type instead of Class. Neal isn't either but | |||
| // suspects some pathological case related to nested classes exists. | |||
| Type rawType = parameterizedType.getRawType(); | |||
| if (!(rawType instanceof Class)) throw new IllegalArgumentException(); | |||
| return (Class<?>) rawType; | |||
| } | |||
| if (type instanceof GenericArrayType) { | |||
| Type componentType = ((GenericArrayType) type).getGenericComponentType(); | |||
| return Array.newInstance(getRawType(componentType), 0).getClass(); | |||
| } | |||
| if (type instanceof TypeVariable) { | |||
| // We could use the variable's bounds, but that won't work if there are multiple. Having a raw | |||
| // type that's more general than necessary is okay. | |||
| return Object.class; | |||
| } | |||
| if (type instanceof WildcardType) { | |||
| return getRawType(((WildcardType) type).getUpperBounds()[0]); | |||
| } | |||
| throw new IllegalArgumentException("Expected a Class, ParameterizedType, or " | |||
| + "GenericArrayType, but <" + type + "> is of type " + type.getClass().getName()); | |||
| } | |||
| static <T> T checkNotNull(T object, String message) { | |||
| if (object == null) { | |||
| throw new NullPointerException(message); | |||
| } | |||
| return object; | |||
| } | |||
| static boolean canOnlyBeInType(Class<?> classType) { | |||
| return isPrimitiveType(classType) || classType == String.class || classType == CharSequence.class; | |||
| } | |||
| static boolean isArrayType(int type) { | |||
| return type == BaseTypeWrapper.TYPE_BYTEARRAY || type == BaseTypeWrapper.TYPE_SHORTARRAY || type == BaseTypeWrapper.TYPE_INTARRAY | |||
| || type == BaseTypeWrapper.TYPE_LONGARRAY || type == BaseTypeWrapper.TYPE_FLOATARRAY || type == BaseTypeWrapper.TYPE_DOUBLEARRAY | |||
| || type == BaseTypeWrapper.TYPE_BOOLEANARRAY || type == BaseTypeWrapper.TYPE_CHARARRAY || type == BaseTypeWrapper.TYPE_STRINGARRAY | |||
| || type == BaseTypeWrapper.TYPE_CHARSEQUENCEARRAY || type == BaseTypeWrapper.TYPE_PARCELABLEARRAY; | |||
| } | |||
| static boolean isPrimitiveType(Class<?> classType) { | |||
| return classType.isPrimitive() || classType == Byte.class || classType == Short.class | |||
| || classType == Integer.class || classType == Long.class || classType == Float.class | |||
| || classType == Double.class || classType == Boolean.class || classType == Character.class; | |||
| } | |||
| static boolean isArrayType(Class<?> classType) { | |||
| return classType == byte[].class || classType == short[].class || classType == int[].class || classType == long[].class | |||
| || classType == float[].class || classType == double[].class || classType == boolean[].class || classType == char[].class | |||
| || classType == String[].class || classType == CharSequence[].class || classType == Parcelable[].class; | |||
| } | |||
| static int getTypeByClass(Class<?> classType) { | |||
| int type; | |||
| if (classType == byte.class || classType == Byte.class) { | |||
| type = BaseTypeWrapper.TYPE_BYTE; | |||
| } else if (classType == short.class || classType == Short.class) { | |||
| type = BaseTypeWrapper.TYPE_SHORT; | |||
| } else if (classType == int.class || classType == Integer.class) { | |||
| type = BaseTypeWrapper.TYPE_INT; | |||
| } else if (classType == long.class || classType == Long.class) { | |||
| type = BaseTypeWrapper.TYPE_LONG; | |||
| } else if (classType == float.class || classType == Float.class) { | |||
| type = BaseTypeWrapper.TYPE_FLOAT; | |||
| } else if (classType == double.class || classType == Double.class) { | |||
| type = BaseTypeWrapper.TYPE_DOUBLE; | |||
| } else if (classType == boolean.class || classType == Boolean.class) { | |||
| type = BaseTypeWrapper.TYPE_BOOLEAN; | |||
| } else if (classType == char.class || classType == Character.class) { | |||
| type = BaseTypeWrapper.TYPE_CHAR; | |||
| } else if (classType == byte[].class) { | |||
| type = BaseTypeWrapper.TYPE_BYTEARRAY; | |||
| } else if (classType == short[].class) { | |||
| type = BaseTypeWrapper.TYPE_SHORTARRAY; | |||
| } else if (classType == int[].class) { | |||
| type = BaseTypeWrapper.TYPE_INTARRAY; | |||
| } else if (classType == long[].class) { | |||
| type = BaseTypeWrapper.TYPE_LONGARRAY; | |||
| } else if (classType == float[].class) { | |||
| type = BaseTypeWrapper.TYPE_FLOATARRAY; | |||
| } else if (classType == double[].class) { | |||
| type = BaseTypeWrapper.TYPE_DOUBLEARRAY; | |||
| } else if (classType == boolean[].class) { | |||
| type = BaseTypeWrapper.TYPE_BOOLEANARRAY; | |||
| } else if (classType == char[].class) { | |||
| type = BaseTypeWrapper.TYPE_CHARARRAY; | |||
| } else if (classType == String.class) { | |||
| type = BaseTypeWrapper.TYPE_STRING; | |||
| } else if (classType == String[].class) { | |||
| type = BaseTypeWrapper.TYPE_STRINGARRAY; | |||
| } else if (classType == CharSequence.class) { | |||
| type = BaseTypeWrapper.TYPE_CHARSEQUENCE; | |||
| } else if (classType == CharSequence[].class) { | |||
| type = BaseTypeWrapper.TYPE_CHARSEQUENCEARRAY; | |||
| } else if (Parcelable.class.isAssignableFrom(classType)) { | |||
| type = BaseTypeWrapper.TYPE_PARCELABLE; | |||
| } else if (Parcelable[].class.isAssignableFrom(classType)) { | |||
| type = BaseTypeWrapper.TYPE_PARCELABLEARRAY; | |||
| } else if (List.class.isAssignableFrom(classType)) { | |||
| type = BaseTypeWrapper.TYPE_LIST; | |||
| } else if (Map.class.isAssignableFrom(classType)) { | |||
| type = BaseTypeWrapper.TYPE_MAP; | |||
| } else { | |||
| type = BaseTypeWrapper.TYPE_EMPTY; | |||
| } | |||
| return type; | |||
| } | |||
| static Object createObjFromClassName(String clsName) { | |||
| Object obj = null; | |||
| try { | |||
| obj = Class.forName(clsName).newInstance(); | |||
| } catch (InstantiationException e) { | |||
| e.printStackTrace(); | |||
| } catch (IllegalAccessException e) { | |||
| e.printStackTrace(); | |||
| } catch (ClassNotFoundException e) { | |||
| e.printStackTrace(); | |||
| } | |||
| return obj; | |||
| } | |||
| static Object createArrayFromComponentType(String componentType, int length) { | |||
| Object obj = null; | |||
| try { | |||
| Class clsType = Class.forName(componentType); | |||
| obj = Array.newInstance(clsType, length); | |||
| } catch (ClassNotFoundException e) { | |||
| e.printStackTrace(); | |||
| } | |||
| return obj; | |||
| } | |||
| static boolean isStringBlank(String str) { | |||
| return str == null || str.trim().length() == 0; | |||
| } | |||
| static Method getMethodReadFromParcel(Class<?> cls) { | |||
| checkNotNull(cls, "Class must not be null."); | |||
| Method method = null; | |||
| try { | |||
| method = cls.getMethod("readFromParcel", Parcel.class); | |||
| } catch (NoSuchMethodException e) { | |||
| Logger.e(TAG, "No public readFromParcel() method in class:" + cls.getName()); | |||
| } catch (SecurityException e) { | |||
| Logger.e(TAG, "SecurityException when get readFromParcel() method in class:" + cls.getName()); | |||
| } | |||
| return method; | |||
| } | |||
| } | |||
| @@ -0,0 +1,27 @@ | |||
| package com.codezjx.andlinker.adapter; | |||
| import com.codezjx.andlinker.Call; | |||
| import com.codezjx.andlinker.CallAdapter; | |||
| import java.lang.annotation.Annotation; | |||
| import java.lang.reflect.Type; | |||
| /** | |||
| * Default {@linkplain CallAdapter.Factory call adapter} which adapt {@link Call} to the execute result. | |||
| */ | |||
| public class DefaultCallAdapterFactory extends CallAdapter.Factory { | |||
| public static final CallAdapter.Factory INSTANCE = new DefaultCallAdapterFactory(); | |||
| @Override | |||
| public CallAdapter<?, ?> get(Type returnType, Annotation[] annotations) { | |||
| return new CallAdapter<Object, Object>() { | |||
| @Override | |||
| public Object adapt(Call<Object> call) { | |||
| // Return the result | |||
| return call.execute(); | |||
| } | |||
| }; | |||
| } | |||
| } | |||
| @@ -0,0 +1,125 @@ | |||
| package com.codezjx.andlinker.adapter; | |||
| import android.os.Handler; | |||
| import android.os.Looper; | |||
| import com.codezjx.andlinker.Call; | |||
| import com.codezjx.andlinker.CallAdapter; | |||
| import com.codezjx.andlinker.Callback; | |||
| import java.lang.annotation.Annotation; | |||
| import java.lang.reflect.Type; | |||
| import java.util.concurrent.Executor; | |||
| /** | |||
| * A {@linkplain CallAdapter.Factory call adapter} which uses the original {@link Call}, just return as is. | |||
| */ | |||
| public class OriginalCallAdapterFactory extends CallAdapter.Factory { | |||
| private Executor mCallbackExecutor; | |||
| private OriginalCallAdapterFactory(Executor callbackExecutor) { | |||
| mCallbackExecutor = callbackExecutor; | |||
| } | |||
| /** | |||
| * Create {@link OriginalCallAdapterFactory} with default Android main thread executor. | |||
| */ | |||
| public static OriginalCallAdapterFactory create() { | |||
| return new OriginalCallAdapterFactory(new MainThreadExecutor()); | |||
| } | |||
| /** | |||
| * Create {@link OriginalCallAdapterFactory} with specify {@link Executor} | |||
| * @param callbackExecutor The executor on which {@link Callback} methods are invoked | |||
| * when returning {@link Call} from your service method. | |||
| */ | |||
| public static OriginalCallAdapterFactory create(Executor callbackExecutor) { | |||
| return new OriginalCallAdapterFactory(callbackExecutor); | |||
| } | |||
| @Override | |||
| public CallAdapter<?, ?> get(Type returnType, Annotation[] annotations) { | |||
| if (getRawType(returnType) != Call.class) { | |||
| return null; | |||
| } | |||
| return new CallAdapter<Object, Call<?>>() { | |||
| @Override public Call<Object> adapt(Call<Object> call) { | |||
| // Return executor wrapper call | |||
| return new ExecutorCallbackCall<>(mCallbackExecutor, call); | |||
| } | |||
| }; | |||
| } | |||
| static final class ExecutorCallbackCall<T> implements Call<T> { | |||
| final Executor mCallbackExecutor; | |||
| final Call<T> mDelegate; | |||
| ExecutorCallbackCall(Executor callbackExecutor, Call<T> delegate) { | |||
| mCallbackExecutor = callbackExecutor; | |||
| mDelegate = delegate; | |||
| } | |||
| @Override | |||
| public T execute() { | |||
| return mDelegate.execute(); | |||
| } | |||
| @Override | |||
| public void enqueue(final Callback<T> callback) { | |||
| if (callback == null) { | |||
| throw new NullPointerException("callback == null"); | |||
| } | |||
| mDelegate.enqueue(new Callback<T>() { | |||
| @Override | |||
| public void onResponse(Call<T> call, final T response) { | |||
| mCallbackExecutor.execute(new Runnable() { | |||
| @Override | |||
| public void run() { | |||
| if (mDelegate.isCanceled()) { | |||
| // Emulate behavior of throwing/delivering an IOException on cancellation. | |||
| callback.onFailure(ExecutorCallbackCall.this, new IllegalStateException("Already canceled")); | |||
| } else { | |||
| callback.onResponse(ExecutorCallbackCall.this, response); | |||
| } | |||
| } | |||
| }); | |||
| } | |||
| @Override | |||
| public void onFailure(Call<T> call, final Throwable t) { | |||
| mCallbackExecutor.execute(new Runnable() { | |||
| @Override | |||
| public void run() { | |||
| callback.onFailure(ExecutorCallbackCall.this, t); | |||
| } | |||
| }); | |||
| } | |||
| }); | |||
| } | |||
| @Override | |||
| public boolean isExecuted() { | |||
| return mDelegate.isExecuted(); | |||
| } | |||
| @Override | |||
| public void cancel() { | |||
| mDelegate.cancel(); | |||
| } | |||
| @Override | |||
| public boolean isCanceled() { | |||
| return mDelegate.isCanceled(); | |||
| } | |||
| } | |||
| private static class MainThreadExecutor implements Executor { | |||
| private final Handler handler = new Handler(Looper.getMainLooper()); | |||
| @Override public void execute(Runnable r) { | |||
| handler.post(r); | |||
| } | |||
| } | |||
| } | |||
| @@ -0,0 +1,163 @@ | |||
| package com.codezjx.andlinker.adapter.rxjava; | |||
| import com.codezjx.andlinker.Call; | |||
| import java.util.concurrent.atomic.AtomicInteger; | |||
| import rx.Producer; | |||
| import rx.Subscriber; | |||
| import rx.Subscription; | |||
| import rx.exceptions.CompositeException; | |||
| import rx.exceptions.Exceptions; | |||
| import rx.exceptions.OnCompletedFailedException; | |||
| import rx.exceptions.OnErrorFailedException; | |||
| import rx.exceptions.OnErrorNotImplementedException; | |||
| import rx.plugins.RxJavaPlugins; | |||
| /** | |||
| * Created by codezjx on 2017/10/16.<br/> | |||
| */ | |||
| final class CallArbiter<T> extends AtomicInteger implements Subscription, Producer { | |||
| private static final int STATE_WAITING = 0; | |||
| private static final int STATE_REQUESTED = 1; | |||
| private static final int STATE_HAS_RESPONSE = 2; | |||
| private static final int STATE_TERMINATED = 3; | |||
| private final Call<T> mCall; | |||
| private final Subscriber<? super T> mSubscriber; | |||
| private volatile T mResponse; | |||
| CallArbiter(Call<T> call, Subscriber<? super T> subscriber) { | |||
| super(STATE_WAITING); | |||
| mCall = call; | |||
| mSubscriber = subscriber; | |||
| } | |||
| @Override | |||
| public void unsubscribe() { | |||
| mCall.cancel(); | |||
| } | |||
| @Override | |||
| public boolean isUnsubscribed() { | |||
| return mCall.isCanceled(); | |||
| } | |||
| @Override | |||
| public void request(long amount) { | |||
| if (amount == 0) { | |||
| return; | |||
| } | |||
| while (true) { | |||
| int state = get(); | |||
| switch (state) { | |||
| case STATE_WAITING: | |||
| if (compareAndSet(STATE_WAITING, STATE_REQUESTED)) { | |||
| return; | |||
| } | |||
| break; // State transition failed. Try again. | |||
| case STATE_HAS_RESPONSE: | |||
| if (compareAndSet(STATE_HAS_RESPONSE, STATE_TERMINATED)) { | |||
| deliverResponse(mResponse); | |||
| return; | |||
| } | |||
| break; // State transition failed. Try again. | |||
| case STATE_REQUESTED: | |||
| case STATE_TERMINATED: | |||
| return; // Nothing to do. | |||
| default: | |||
| throw new IllegalStateException("Unknown state: " + state); | |||
| } | |||
| } | |||
| } | |||
| void emitResponse(T response) { | |||
| while (true) { | |||
| int state = get(); | |||
| switch (state) { | |||
| case STATE_WAITING: | |||
| this.mResponse = response; | |||
| if (compareAndSet(STATE_WAITING, STATE_HAS_RESPONSE)) { | |||
| return; | |||
| } | |||
| break; // State transition failed. Try again. | |||
| case STATE_REQUESTED: | |||
| if (compareAndSet(STATE_REQUESTED, STATE_TERMINATED)) { | |||
| deliverResponse(response); | |||
| return; | |||
| } | |||
| break; // State transition failed. Try again. | |||
| case STATE_HAS_RESPONSE: | |||
| case STATE_TERMINATED: | |||
| throw new AssertionError(); | |||
| default: | |||
| throw new IllegalStateException("Unknown state: " + state); | |||
| } | |||
| } | |||
| } | |||
| private void deliverResponse(T response) { | |||
| try { | |||
| if (!isUnsubscribed()) { | |||
| mSubscriber.onNext(response); | |||
| } | |||
| } catch (OnCompletedFailedException | |||
| | OnErrorFailedException | |||
| | OnErrorNotImplementedException e) { | |||
| RxJavaPlugins.getInstance().getErrorHandler().handleError(e); | |||
| return; | |||
| } catch (Throwable t) { | |||
| Exceptions.throwIfFatal(t); | |||
| try { | |||
| mSubscriber.onError(t); | |||
| } catch (OnCompletedFailedException | |||
| | OnErrorFailedException | |||
| | OnErrorNotImplementedException e) { | |||
| RxJavaPlugins.getInstance().getErrorHandler().handleError(e); | |||
| } catch (Throwable inner) { | |||
| Exceptions.throwIfFatal(inner); | |||
| CompositeException composite = new CompositeException(t, inner); | |||
| RxJavaPlugins.getInstance().getErrorHandler().handleError(composite); | |||
| } | |||
| return; | |||
| } | |||
| try { | |||
| if (!isUnsubscribed()) { | |||
| mSubscriber.onCompleted(); | |||
| } | |||
| } catch (OnCompletedFailedException | |||
| | OnErrorFailedException | |||
| | OnErrorNotImplementedException e) { | |||
| RxJavaPlugins.getInstance().getErrorHandler().handleError(e); | |||
| } catch (Throwable t) { | |||
| Exceptions.throwIfFatal(t); | |||
| RxJavaPlugins.getInstance().getErrorHandler().handleError(t); | |||
| } | |||
| } | |||
| void emitError(Throwable t) { | |||
| set(STATE_TERMINATED); | |||
| if (!isUnsubscribed()) { | |||
| try { | |||
| mSubscriber.onError(t); | |||
| } catch (OnCompletedFailedException | |||
| | OnErrorFailedException | |||
| | OnErrorNotImplementedException e) { | |||
| RxJavaPlugins.getInstance().getErrorHandler().handleError(e); | |||
| } catch (Throwable inner) { | |||
| Exceptions.throwIfFatal(inner); | |||
| CompositeException composite = new CompositeException(t, inner); | |||
| RxJavaPlugins.getInstance().getErrorHandler().handleError(composite); | |||
| } | |||
| } | |||
| } | |||
| } | |||
| @@ -0,0 +1,36 @@ | |||
| package com.codezjx.andlinker.adapter.rxjava; | |||
| import com.codezjx.andlinker.Call; | |||
| import rx.Observable.OnSubscribe; | |||
| import rx.Subscriber; | |||
| import rx.exceptions.Exceptions; | |||
| /** | |||
| * Created by codezjx on 2017/10/16.<br/> | |||
| */ | |||
| final class CallExecuteOnSubscribe<T> implements OnSubscribe<T> { | |||
| private final Call<T> mCall; | |||
| CallExecuteOnSubscribe(Call<T> call) { | |||
| mCall = call; | |||
| } | |||
| @Override | |||
| public void call(Subscriber<? super T> subscriber) { | |||
| CallArbiter<T> arbiter = new CallArbiter<>(mCall, subscriber); | |||
| subscriber.add(arbiter); | |||
| subscriber.setProducer(arbiter); | |||
| T response; | |||
| try { | |||
| response = mCall.execute(); | |||
| } catch (Throwable t) { | |||
| Exceptions.throwIfFatal(t); | |||
| arbiter.emitError(t); | |||
| return; | |||
| } | |||
| arbiter.emitResponse(response); | |||
| } | |||
| } | |||
| @@ -0,0 +1,35 @@ | |||
| package com.codezjx.andlinker.adapter.rxjava; | |||
| import com.codezjx.andlinker.Call; | |||
| import com.codezjx.andlinker.CallAdapter; | |||
| import rx.Observable; | |||
| import rx.Observable.OnSubscribe; | |||
| import rx.Scheduler; | |||
| /** | |||
| * Created by codezjx on 2017/10/16.<br/> | |||
| */ | |||
| final class RxJavaCallAdapter<R> implements CallAdapter<R, Observable<R>> { | |||
| private final Scheduler mScheduler; | |||
| RxJavaCallAdapter(Scheduler scheduler) { | |||
| mScheduler = scheduler; | |||
| } | |||
| @Override | |||
| public Observable<R> adapt(Call<R> call) { | |||
| OnSubscribe<R> subscribe = new CallExecuteOnSubscribe<>(call); | |||
| Observable<R> observable = Observable.create(subscribe); | |||
| if (mScheduler != null) { | |||
| observable = observable.subscribeOn(mScheduler); | |||
| } | |||
| return observable; | |||
| } | |||
| } | |||
| @@ -0,0 +1,47 @@ | |||
| package com.codezjx.andlinker.adapter.rxjava; | |||
| import com.codezjx.andlinker.CallAdapter; | |||
| import java.lang.annotation.Annotation; | |||
| import java.lang.reflect.ParameterizedType; | |||
| import java.lang.reflect.Type; | |||
| import rx.Observable; | |||
| import rx.Scheduler; | |||
| /** | |||
| * A {@linkplain CallAdapter.Factory call adapter} which uses RxJava for creating observables. | |||
| */ | |||
| public class RxJavaCallAdapterFactory extends CallAdapter.Factory { | |||
| private final Scheduler mScheduler; | |||
| private RxJavaCallAdapterFactory(Scheduler scheduler) { | |||
| mScheduler = scheduler; | |||
| } | |||
| public static RxJavaCallAdapterFactory create() { | |||
| return new RxJavaCallAdapterFactory(null); | |||
| } | |||
| public static RxJavaCallAdapterFactory createWithScheduler(Scheduler scheduler) { | |||
| if (scheduler == null) throw new NullPointerException("scheduler == null"); | |||
| return new RxJavaCallAdapterFactory(scheduler); | |||
| } | |||
| @Override | |||
| public CallAdapter<?, ?> get(Type returnType, Annotation[] annotations) { | |||
| Class<?> rawType = getRawType(returnType); | |||
| if (rawType != Observable.class) { | |||
| return null; | |||
| } | |||
| if (!(returnType instanceof ParameterizedType)) { | |||
| throw new IllegalStateException("Observable return type must be parameterized" | |||
| + " as Observable<Foo> or Observable<? extends Foo>"); | |||
| } | |||
| return new RxJavaCallAdapter(mScheduler); | |||
| } | |||
| } | |||
| @@ -0,0 +1,66 @@ | |||
| package com.codezjx.andlinker.adapter.rxjava2; | |||
| import com.codezjx.andlinker.Call; | |||
| import io.reactivex.Observable; | |||
| import io.reactivex.Observer; | |||
| import io.reactivex.disposables.Disposable; | |||
| import io.reactivex.exceptions.CompositeException; | |||
| import io.reactivex.exceptions.Exceptions; | |||
| import io.reactivex.plugins.RxJavaPlugins; | |||
| /** | |||
| * Created by codezjx on 2017/10/14.<br/> | |||
| */ | |||
| final class CallExecuteObservable<T> extends Observable<T> { | |||
| private final Call<T> mCall; | |||
| CallExecuteObservable(Call<T> call) { | |||
| mCall = call; | |||
| } | |||
| @Override | |||
| protected void subscribeActual(Observer<? super T> observer) { | |||
| observer.onSubscribe(new CallDisposable(mCall)); | |||
| boolean terminated = false; | |||
| try { | |||
| T response = mCall.execute(); | |||
| if (!mCall.isCanceled()) { | |||
| observer.onNext(response); | |||
| } | |||
| if (!mCall.isCanceled()) { | |||
| terminated = true; | |||
| observer.onComplete(); | |||
| } | |||
| } catch (Throwable t) { | |||
| Exceptions.throwIfFatal(t); | |||
| if (terminated) { | |||
| RxJavaPlugins.onError(t); | |||
| } else if (!mCall.isCanceled()) { | |||
| try { | |||
| observer.onError(t); | |||
| } catch (Throwable inner) { | |||
| Exceptions.throwIfFatal(inner); | |||
| RxJavaPlugins.onError(new CompositeException(t, inner)); | |||
| } | |||
| } | |||
| } | |||
| } | |||
| private static final class CallDisposable implements Disposable { | |||
| private final Call<?> mCall; | |||
| CallDisposable(Call<?> call) { | |||
| this.mCall = call; | |||
| } | |||
| @Override public void dispose() { | |||
| mCall.cancel(); | |||
| } | |||
| @Override public boolean isDisposed() { | |||
| return mCall.isCanceled(); | |||
| } | |||
| } | |||
| } | |||
| @@ -0,0 +1,38 @@ | |||
| package com.codezjx.andlinker.adapter.rxjava2; | |||
| import com.codezjx.andlinker.Call; | |||
| import com.codezjx.andlinker.CallAdapter; | |||
| import io.reactivex.BackpressureStrategy; | |||
| import io.reactivex.Observable; | |||
| import io.reactivex.Scheduler; | |||
| /** | |||
| * Created by codezjx on 2017/10/14.<br/> | |||
| */ | |||
| final class RxJava2CallAdapter<R> implements CallAdapter<R, Object> { | |||
| private final Scheduler mScheduler; | |||
| private final boolean mIsFlowable; | |||
| RxJava2CallAdapter(Scheduler scheduler, boolean isFlowable) { | |||
| mScheduler = scheduler; | |||
| mIsFlowable = isFlowable; | |||
| } | |||
| @Override | |||
| public Object adapt(Call<R> call) { | |||
| Observable<R> observable = new CallExecuteObservable<>(call); | |||
| if (mScheduler != null) { | |||
| observable = observable.subscribeOn(mScheduler); | |||
| } | |||
| if (mIsFlowable) { | |||
| return observable.toFlowable(BackpressureStrategy.LATEST); | |||
| } | |||
| return observable; | |||
| } | |||
| } | |||
| @@ -0,0 +1,50 @@ | |||
| package com.codezjx.andlinker.adapter.rxjava2; | |||
| import com.codezjx.andlinker.CallAdapter; | |||
| import java.lang.annotation.Annotation; | |||
| import java.lang.reflect.ParameterizedType; | |||
| import java.lang.reflect.Type; | |||
| import io.reactivex.Flowable; | |||
| import io.reactivex.Observable; | |||
| import io.reactivex.Scheduler; | |||
| /** | |||
| * A {@linkplain CallAdapter.Factory call adapter} which uses RxJava 2 for creating observables. | |||
| */ | |||
| public class RxJava2CallAdapterFactory extends CallAdapter.Factory { | |||
| private final Scheduler mScheduler; | |||
| private RxJava2CallAdapterFactory(Scheduler scheduler) { | |||
| mScheduler = scheduler; | |||
| } | |||
| public static RxJava2CallAdapterFactory create() { | |||
| return new RxJava2CallAdapterFactory(null); | |||
| } | |||
| public static RxJava2CallAdapterFactory createWithScheduler(Scheduler scheduler) { | |||
| if (scheduler == null) throw new NullPointerException("scheduler == null"); | |||
| return new RxJava2CallAdapterFactory(scheduler); | |||
| } | |||
| @Override | |||
| public CallAdapter<?, ?> get(Type returnType, Annotation[] annotations) { | |||
| Class<?> rawType = getRawType(returnType); | |||
| boolean isFlowable = (rawType == Flowable.class); | |||
| if (rawType != Observable.class && !isFlowable) { | |||
| return null; | |||
| } | |||
| if (!(returnType instanceof ParameterizedType)) { | |||
| String name = isFlowable ? "Flowable" : "Observable"; | |||
| throw new IllegalStateException(name + " return type must be parameterized" | |||
| + " as " + name + "<Foo> or " + name + "<? extends Foo>"); | |||
| } | |||
| return new RxJava2CallAdapter(mScheduler, isFlowable); | |||
| } | |||
| } | |||
| @@ -0,0 +1,16 @@ | |||
| package com.codezjx.andlinker.annotation; | |||
| import java.lang.annotation.Retention; | |||
| import java.lang.annotation.Target; | |||
| import static java.lang.annotation.ElementType.PARAMETER; | |||
| import static java.lang.annotation.RetentionPolicy.RUNTIME; | |||
| /** | |||
| * Indicate a parameter is a remote callback type. | |||
| */ | |||
| @Target(PARAMETER) | |||
| @Retention(RUNTIME) | |||
| public @interface Callback { | |||
| } | |||
| @@ -0,0 +1,16 @@ | |||
| package com.codezjx.andlinker.annotation; | |||
| import java.lang.annotation.Retention; | |||
| import java.lang.annotation.Target; | |||
| import static java.lang.annotation.ElementType.PARAMETER; | |||
| import static java.lang.annotation.RetentionPolicy.RUNTIME; | |||
| /** | |||
| * Directional tag indicating which way the data goes, same as "in" tag in AIDL. | |||
| */ | |||
| @Target(PARAMETER) | |||
| @Retention(RUNTIME) | |||
| public @interface In { | |||
| } | |||
| @@ -0,0 +1,16 @@ | |||
| package com.codezjx.andlinker.annotation; | |||
| import java.lang.annotation.Retention; | |||
| import java.lang.annotation.Target; | |||
| import static java.lang.annotation.ElementType.PARAMETER; | |||
| import static java.lang.annotation.RetentionPolicy.RUNTIME; | |||
| /** | |||
| * Directional tag indicating which way the data goes, same as "inout" tag in AIDL. | |||
| */ | |||
| @Target(PARAMETER) | |||
| @Retention(RUNTIME) | |||
| public @interface Inout { | |||
| } | |||
| @@ -0,0 +1,17 @@ | |||
| package com.codezjx.andlinker.annotation; | |||
| import java.lang.annotation.Retention; | |||
| import java.lang.annotation.Target; | |||
| import static java.lang.annotation.ElementType.METHOD; | |||
| import static java.lang.annotation.RetentionPolicy.RUNTIME; | |||
| /** | |||
| * Indicate a remote call does not block, it simply sends the transaction data and immediately | |||
| * returns, same as "oneway" tag in AIDL. | |||
| */ | |||
| @Target(METHOD) | |||
| @Retention(RUNTIME) | |||
| public @interface OneWay { | |||
| } | |||
| @@ -0,0 +1,16 @@ | |||
| package com.codezjx.andlinker.annotation; | |||
| import java.lang.annotation.Retention; | |||
| import java.lang.annotation.Target; | |||
| import static java.lang.annotation.ElementType.PARAMETER; | |||
| import static java.lang.annotation.RetentionPolicy.RUNTIME; | |||
| /** | |||
| * Directional tag indicating which way the data goes, same as "out" tag in AIDL. | |||
| */ | |||
| @Target(PARAMETER) | |||
| @Retention(RUNTIME) | |||
| public @interface Out { | |||
| } | |||
| @@ -0,0 +1,16 @@ | |||
| package com.codezjx.andlinker.annotation; | |||
| import java.lang.annotation.Retention; | |||
| import java.lang.annotation.Target; | |||
| import static java.lang.annotation.ElementType.TYPE; | |||
| import static java.lang.annotation.RetentionPolicy.RUNTIME; | |||
| /** | |||
| * Specify the interface as remote service interface. | |||
| */ | |||
| @Target(TYPE) | |||
| @Retention(RUNTIME) | |||
| public @interface RemoteInterface { | |||
| } | |||
| @@ -0,0 +1 @@ | |||
| /build | |||
| @@ -0,0 +1,80 @@ | |||
| apply plugin: 'com.android.application' | |||
| android { | |||
| compileSdkVersion 26 | |||
| buildToolsVersion '26.0.2' | |||
| defaultConfig { | |||
| applicationId "com.neusoft.possale" | |||
| minSdkVersion 21 | |||
| targetSdkVersion 23 | |||
| versionCode 7 | |||
| versionName "20201123163124" | |||
| // jackOptions.enabled = true | |||
| javaCompileOptions { | |||
| annotationProcessorOptions { | |||
| includeCompileClasspath = true | |||
| } | |||
| } | |||
| } | |||
| // compileOptions{ | |||
| // sourceCompatibility org.gradle.api.JavaVersion.VERSION_1_8 | |||
| // targetCompatibility org.gradle.api.JavaVersion.VERSION_1_8 | |||
| // } | |||
| packagingOptions { | |||
| exclude 'META-INF/rxjava.properties' | |||
| // ... and any other excludes go here | |||
| } | |||
| buildTypes { | |||
| release { | |||
| minifyEnabled false | |||
| proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' | |||
| } | |||
| // debug { | |||
| // applicationIdSuffix "debug" | |||
| // } | |||
| } | |||
| sourceSets { main { assets.srcDirs = ['src/main/assets', 'src/main/assets/'] } } | |||
| sourceSets { | |||
| main { | |||
| aidl.srcDirs = ['src/main/aidl/'] | |||
| java.srcDirs = ['src/main/java', 'src/main/aidl'] | |||
| resources.srcDirs = ['src/main/java', 'src/main/aidl'] | |||
| } | |||
| } | |||
| sourceSets { | |||
| main { | |||
| jniLibs.srcDirs = ['libs'] | |||
| } | |||
| } | |||
| } | |||
| dependencies { | |||
| compile fileTree(include: ['*.jar'], dir: 'libs') | |||
| testCompile 'junit:junit:4.12' | |||
| compile 'com.android.support:design:23.4.0' | |||
| //rxlifecycle | |||
| compile 'com.trello:rxlifecycle-components:1.0' | |||
| //rx | |||
| compile 'io.reactivex:rxandroid:1.2.1' | |||
| compile 'io.reactivex:rxjava:1.3.0' | |||
| //net | |||
| compile 'com.squareup.retrofit2:retrofit:2.4.0' | |||
| compile 'com.squareup.retrofit2:adapter-rxjava:2.4.0' | |||
| compile 'com.squareup.retrofit2:converter-gson:2.4.0' | |||
| compile 'com.squareup.okhttp3:logging-interceptor:3.10.0' | |||
| compile 'com.google.code.gson:gson:2.7' | |||
| compile 'com.github.bumptech.glide:glide:3.7.0' | |||
| compile project(path: ':cardreader') | |||
| compile project(path: ':barcodescaner') | |||
| compile files('libs/butterknife-7.0.1.jar') | |||
| compile project(path: ':lib') | |||
| compile 'com.ttsea.jrxbus2:jrxbus2:1.0.3' | |||
| compile project(path: ':extendMediaPlay') | |||
| provided files('libs_ext/androidsim.jar') | |||
| implementation 'com.codezjx.library:andlinker:0.7.1' | |||
| } | |||
| @@ -0,0 +1,17 @@ | |||
| # Add project specific ProGuard rules here. | |||
| # By default, the flags in this file are appended to flags specified | |||
| # in E:\angel\Cmread_android_studio_sdk\android-sdk-windows/tools/proguard/proguard-android.txt | |||
| # You can edit the include path and order by changing the proguardFiles | |||
| # directive in build.gradle. | |||
| # | |||
| # For more details, see | |||
| # http://developer.android.com/guide/developing/tools/proguard.html | |||
| # Add any project specific keep options here: | |||
| # If your project uses WebView with JS, uncomment the following | |||
| # and specify the fully qualified class name to the JavaScript interface | |||
| # class: | |||
| #-keepclassmembers class fqcn.of.javascript.interface.for.webview { | |||
| # public *; | |||
| #} | |||
| @@ -0,0 +1,13 @@ | |||
| package com.neusoft.possale; | |||
| import android.app.Application; | |||
| import android.test.ApplicationTestCase; | |||
| /** | |||
| * <a href="http://d.android.com/tools/testing/testing_android.html">Testing Fundamentals</a> | |||
| */ | |||
| public class ApplicationTest extends ApplicationTestCase<Application> { | |||
| public ApplicationTest() { | |||
| super(Application.class); | |||
| } | |||
| } | |||
| @@ -0,0 +1,266 @@ | |||
| <?xml version="1.0" encoding="utf-8"?> | |||
| <manifest xmlns:android="http://schemas.android.com/apk/res/android" | |||
| package="com.neusoft.possale"> | |||
| <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" /> | |||
| <uses-permission android:name="android.permission.CHANGE_WIFI_STATE" /> | |||
| <uses-permission android:name="android.permission.KOOLCLOUD_CONTACTLESS_CARD" /> | |||
| <uses-permission android:name="android.permission.INTERNET" /> | |||
| <uses-permission android:name="android.permission.ACCESS_SUPERUSER" /> | |||
| <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> | |||
| <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> | |||
| <uses-permission android:name="android.permission.WRITE_SETTINGS" /> | |||
| <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" /> | |||
| <uses-permission android:name="android.permission.KOOLCLOUD_PRINTER" /> | |||
| <uses-permission android:name="com.hidglobal.ia.omnikey.service.permission.SMARTCARDIO" /> | |||
| <!-- 外置存储存取权限 --> | |||
| <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> | |||
| <uses-feature android:name="android.hardware.screen.portrait" /> | |||
| <!-- 接收开机广播 --> | |||
| <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" /> | |||
| <!-- 交易权限 --> | |||
| <uses-permission android:name="com.icbc.smartpos.bankpay.advance.special" /> | |||
| <!-- 收银 --> | |||
| <uses-permission android:name="com.icbc.smartpos.bankpay.trans.multiPurchase" /> | |||
| <!-- 余额查询 --> | |||
| <uses-permission android:name="com.icbc.smartpos.bankpay.trans.inquery" /> | |||
| <!-- 积分查询 --> | |||
| <uses-permission android:name="com.icbc.smartpos.bankpay.trans.integralQuery" /> | |||
| <!-- 当日撤销 --> | |||
| <uses-permission android:name="com.icbc.smartpos.bankpay.trans.posVoid" /> | |||
| <!-- 隔日退货 --> | |||
| <uses-permission android:name="com.icbc.smartpos.bankpay.trans.refund" /> | |||
| <!-- 积分退货 --> | |||
| <uses-permission android:name="com.icbc.smartpos.bankpay.trans.integralRefund" /> | |||
| <!-- 二维码退货 --> | |||
| <uses-permission android:name="com.icbc.smartpos.bankpay.trans.qrRefund" /> | |||
| <!-- 预授权 --> | |||
| <uses-permission android:name="com.icbc.smartpos.bankpay.trans.preauth" /> | |||
| <!-- 追加预授权 --> | |||
| <uses-permission android:name="com.icbc.smartpos.bankpay.trans.addPreauth" /> | |||
| <!-- 预授权确认 --> | |||
| <uses-permission android:name="com.icbc.smartpos.bankpay.trans.preauthEnd" /> | |||
| <!-- 预授权撤销 --> | |||
| <uses-permission android:name="com.icbc.smartpos.bankpay.trans.preauthVoid" /> | |||
| <!-- 查本机交易记录 --> | |||
| <uses-permission android:name="com.icbc.smartpos.bankpay.trans.queryTransRec" /> | |||
| <!-- 获取终端信息 --> | |||
| <uses-permission android:name="com.icbc.smartpos.bankpay.trans.terminalInfo" /> | |||
| <!-- 重打印交易凭条 --> | |||
| <uses-permission android:name="com.icbc.smartpos.bankpay.trans.reprint" /> | |||
| <!-- 设备使用权限 --> | |||
| <!-- 打印机 --> | |||
| <uses-permission android:name="com.icbc.smartpos.bankpay.device.printer" /> | |||
| <!-- 扫码器 --> | |||
| <uses-permission android:name="com.icbc.smartpos.bankpay.device.scanner" /> | |||
| <uses-sdk android:targetSdkVersion="23" /> | |||
| <application | |||
| android:name=".aplication.MyApplication" | |||
| android:allowBackup="true" | |||
| android:hardwareAccelerated="true" | |||
| android:icon="@mipmap/ic_launcher" | |||
| android:largeHeap="true" | |||
| android:label="@string/app_name" | |||
| android:supportsRtl="true" | |||
| android:theme="@style/AppTheme"> | |||
| <activity | |||
| android:name=".LoginActivity" | |||
| android:launchMode="singleTask" > | |||
| <intent-filter> | |||
| <action android:name="android.intent.action.MAIN" /> | |||
| <category android:name="android.intent.category.LAUNCHER" /> | |||
| <category android:name="android.intent.category.HOME" /> | |||
| <category android:name="android.intent.category.DEFAULT" /> | |||
| </intent-filter> | |||
| </activity> | |||
| <!-- 关机广播 --> | |||
| <receiver android:name=".service.recevier.ShutDownRecevier"> | |||
| <intent-filter> | |||
| <action android:name="android.intent.action.ACTION_SHUTDOWN" /> | |||
| <category android:name="android.intent.category.HOME" /> | |||
| </intent-filter> | |||
| </receiver> | |||
| <!-- 开机广播 --> | |||
| <receiver android:name=".service.recevier.BootRecevier"> | |||
| <intent-filter> | |||
| <action android:name="android.intent.action.BOOT_COMPLETED" /> | |||
| <category android:name="android.intent.category.HOME" /> | |||
| </intent-filter> | |||
| </receiver> | |||
| <receiver android:name=".service.recevier.UsbDeviceReceiver"> | |||
| <intent-filter> | |||
| <action android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED" /> | |||
| <action android:name="android.hardware.usb.action.USB_DEVICE_DETACHED" /> | |||
| </intent-filter> | |||
| </receiver> | |||
| <activity | |||
| android:name=".home.MainActivity" | |||
| android:launchMode="singleTask" | |||
| android:screenOrientation="landscape" /> | |||
| <activity | |||
| android:name=".home.MainActivityA90" | |||
| android:launchMode="singleTask" | |||
| android:screenOrientation="portrait" /> | |||
| <activity | |||
| android:name=".cashfunction.activity.CashFunctionActivity" | |||
| android:launchMode="singleTask" /> | |||
| <activity | |||
| android:name=".cashfunction.activity.CashMemberLoginActivity" | |||
| android:launchMode="singleTask" | |||
| android:screenOrientation="landscape" | |||
| android:theme="@style/CustomActivityDialogStyle" /> | |||
| <activity | |||
| android:name=".cashfunction.activity.PayOrderActivity" | |||
| android:launchMode="singleTask" | |||
| android:theme="@style/CustomActivityDialogStyle" /> | |||
| <activity | |||
| android:name=".cashfunction.activity.CountInputActivity" | |||
| android:launchMode="singleTask" | |||
| android:theme="@style/CustomActivityDialogStyle" /> | |||
| <activity | |||
| android:name=".cashfunction.activity.MemberPayActivity" | |||
| android:launchMode="singleTask" | |||
| android:screenOrientation="landscape" | |||
| android:theme="@style/CustomActivityDialogStyle" | |||
| android:windowSoftInputMode="stateHidden" /> | |||
| <activity | |||
| android:name=".set.activity.SettingActivity" | |||
| android:launchMode="singleTask" | |||
| android:screenOrientation="landscape" /> | |||
| <activity | |||
| android:name=".set.activity.SettingActivityA90" | |||
| android:launchMode="singleTask" | |||
| android:screenOrientation="portrait" /> | |||
| <activity | |||
| android:name=".set.activity.ShoppingCartModeActivity" | |||
| android:launchMode="singleTask" | |||
| android:screenOrientation="landscape"/> | |||
| <activity | |||
| android:name=".paymanage.activity.TradeManageActivity" | |||
| android:launchMode="singleTask" /> | |||
| <activity | |||
| android:name=".shop.activity.SelectShopActivity" | |||
| android:launchMode="singleTask"/> | |||
| <activity | |||
| android:name=".member.activity.MemberLoginActivity" | |||
| android:launchMode="singleTask" | |||
| android:screenOrientation="landscape" | |||
| android:windowSoftInputMode="stateHidden" /> | |||
| <activity | |||
| android:name=".member.activity.MemberLoginSuccessActivity" | |||
| android:launchMode="singleTask" | |||
| android:screenOrientation="landscape" | |||
| android:windowSoftInputMode="stateHidden" /> | |||
| <activity | |||
| android:name=".member.activity.MemberRechargeActivity" | |||
| android:launchMode="singleTask" | |||
| android:screenOrientation="landscape" | |||
| android:windowSoftInputMode="stateHidden" /> | |||
| <activity | |||
| android:name=".member.activity.MemberRechargeChangeDetailsActivity" | |||
| android:launchMode="singleTask" | |||
| android:screenOrientation="landscape" | |||
| android:windowSoftInputMode="stateHidden" /> | |||
| <activity | |||
| android:name=".member.activity.MemberRegisterActivity" | |||
| android:launchMode="singleTask" | |||
| android:screenOrientation="landscape" | |||
| android:windowSoftInputMode="stateHidden" /> | |||
| <activity | |||
| android:name=".member.activity.MemberIntegralConsumeActivity" | |||
| android:launchMode="singleTask" | |||
| android:screenOrientation="landscape" | |||
| android:theme="@style/CustomActivityDialogStyle" | |||
| android:windowSoftInputMode="stateHidden" /> | |||
| <activity | |||
| android:name=".member.activity.MemberMenuActivity" | |||
| android:launchMode="singleTask" | |||
| android:windowSoftInputMode="stateHidden" /> | |||
| <activity | |||
| android:name=".member.activity.CardActivateActivity" | |||
| android:launchMode="singleTask" | |||
| android:windowSoftInputMode="stateHidden" /> | |||
| <activity | |||
| android:name=".member.activity.CardRechargeActivity" | |||
| android:launchMode="singleTask" | |||
| android:windowSoftInputMode="stateHidden" /> | |||
| <activity | |||
| android:name=".member.activity.CardRefundActivity" | |||
| android:launchMode="singleTask" | |||
| android:windowSoftInputMode="stateHidden" /> | |||
| <activity | |||
| android:name=".member.activity.CardOperateResultActivity" | |||
| android:launchMode="singleTask" | |||
| android:windowSoftInputMode="stateHidden" /> | |||
| <!-- 订单列表Activity --> | |||
| <activity | |||
| android:name=".order.activity.OrderListActivity" | |||
| android:launchMode="singleTask" | |||
| android:screenOrientation="landscape" | |||
| android:windowSoftInputMode="stateHidden" /> | |||
| <!-- 订单Services --> | |||
| <service android:name=".service.OrderService" /> | |||
| <!-- 订单详情Activity --> | |||
| <activity | |||
| android:name=".order.activity.OrderDetailActivity" | |||
| android:launchMode="singleTask" | |||
| android:screenOrientation="landscape" | |||
| android:windowSoftInputMode="stateHidden" /> | |||
| <activity | |||
| android:name=".paymanage.activity.RefundOrderActivity" | |||
| android:launchMode="singleTask" | |||
| android:screenOrientation="landscape" | |||
| android:windowSoftInputMode="stateHidden" | |||
| android:theme="@style/CustomActivityDialogStyle"/> | |||
| <activity | |||
| android:name=".paymanage.activity.OrderDetailInfoActivity" | |||
| android:launchMode="singleTask" | |||
| android:screenOrientation="landscape" | |||
| android:windowSoftInputMode="stateHidden" /> | |||
| <activity | |||
| android:name=".printer.PosSettingActivity" | |||
| android:launchMode="singleTask" | |||
| android:screenOrientation="landscape" | |||
| android:windowSoftInputMode="stateHidden" /> | |||
| <activity android:name=".pay.recharge.RechargeActivity" /> | |||
| <activity android:name=".set.activity.ModeChangeActivity" /> | |||
| <!-- 后结选桌Activity --> | |||
| <activity | |||
| android:name=".aftersettlement.activity.ChooseTableActivity" | |||
| android:screenOrientation="landscape" | |||
| android:windowSoftInputMode="stateHidden" /> | |||
| <!-- 后结菜单Activity --> | |||
| <activity | |||
| android:name=".aftersettlement.activity.MenuActivity" | |||
| android:screenOrientation="landscape" | |||
| android:windowSoftInputMode="adjustPan" /> | |||
| <activity | |||
| android:name=".aftersettlement.activity.PaymentActivity" | |||
| android:launchMode="singleTask" | |||
| android:screenOrientation="landscape" | |||
| android:windowSoftInputMode="stateHidden"/> | |||
| <activity | |||
| android:name=".paymanage.activity.PaymentDetailActivity" | |||
| android:windowSoftInputMode="stateHidden"/> | |||
| <activity | |||
| android:name=".pay.BankCardRefundActivity" | |||
| android:screenOrientation="landscape" | |||
| android:theme="@android:style/Theme.NoDisplay" | |||
| android:windowSoftInputMode="stateHidden"/> | |||
| </application> | |||
| </manifest> | |||
| @@ -0,0 +1,3 @@ | |||
| package com.icbc.smartpos.deviceservice.aidl; | |||
| parcelable CandidateAppInfo; | |||
| @@ -0,0 +1,133 @@ | |||
| /*** Eclipse Class Decompiler plugin, copyright (c) 2016 Chen Chao (cnfree2000@hotmail.com) ***/ | |||
| package com.icbc.smartpos.deviceservice.aidl; | |||
| import android.os.Parcel; | |||
| import android.os.Parcelable; | |||
| import android.os.Parcelable.Creator; | |||
| public class CandidateAppInfo implements Parcelable { | |||
| /**候选应用标识符(AID)*/ | |||
| private byte[] tAID = new byte[16]; | |||
| /**候选应用标签*/ | |||
| private byte[] tAppLabel = new byte[16]; | |||
| /**应用优先级指示符*/ | |||
| private byte[] tAPN = new byte[16]; | |||
| private byte cAPID; | |||
| private byte cFlgAPID; | |||
| /**语言参考*/ | |||
| private byte[] sLangPref = new byte[8]; | |||
| /**发行方证书索引*/ | |||
| private byte cIssCTIndex; | |||
| private byte cFlgIssCTIndex; | |||
| public static final Creator<CandidateAppInfo> CREATOR = new Creator() { | |||
| public CandidateAppInfo createFromParcel(Parcel source) { | |||
| CandidateAppInfo appInfo = new CandidateAppInfo(); | |||
| readByteArray(source.createByteArray(), appInfo.tAID); | |||
| readByteArray(source.createByteArray(), appInfo.tAppLabel); | |||
| readByteArray(source.createByteArray(), appInfo.tAPN); | |||
| appInfo.cAPID = source.readByte(); | |||
| appInfo.cFlgAPID = source.readByte(); | |||
| readByteArray(source.createByteArray(), appInfo.sLangPref); | |||
| appInfo.cIssCTIndex = source.readByte(); | |||
| appInfo.cFlgIssCTIndex = source.readByte(); | |||
| return appInfo; | |||
| } | |||
| public CandidateAppInfo[] newArray(int size) { | |||
| return new CandidateAppInfo[size]; | |||
| } | |||
| private int getMinValue(int value1, int value2) { | |||
| return ((value1 > value2) ? value2 : value1); | |||
| } | |||
| private void readByteArray(byte[] source, byte[] dest) { | |||
| int arrayLen = 0; | |||
| arrayLen = getMinValue(source.length, dest.length); | |||
| System.arraycopy(source, 0, dest, 0, arrayLen); | |||
| } | |||
| }; | |||
| public void setAID(byte[] aid) { | |||
| this.tAID = aid; | |||
| } | |||
| public byte[] getAID() { | |||
| return this.tAID; | |||
| } | |||
| public void setAppLabel(byte[] appLabel) { | |||
| this.tAppLabel = appLabel; | |||
| } | |||
| public byte[] getAppLabel() { | |||
| return this.tAppLabel; | |||
| } | |||
| public void setAPN(byte[] apn) { | |||
| this.tAPN = apn; | |||
| } | |||
| public byte[] getAPN() { | |||
| return this.tAPN; | |||
| } | |||
| public void setAPID(byte apid) { | |||
| this.cAPID = apid; | |||
| } | |||
| public byte getAPID() { | |||
| return this.cAPID; | |||
| } | |||
| public void setAPIDFlag(byte flag) { | |||
| this.cFlgAPID = flag; | |||
| } | |||
| public byte getAPIDFlag() { | |||
| return this.cFlgAPID; | |||
| } | |||
| public void setLangPref(byte[] langPref) { | |||
| this.sLangPref = langPref; | |||
| } | |||
| public byte[] getLangPref() { | |||
| return this.sLangPref; | |||
| } | |||
| public void setIssCTIndex(byte index) { | |||
| this.cIssCTIndex = index; | |||
| } | |||
| public byte getIssCTIndex() { | |||
| return this.cIssCTIndex; | |||
| } | |||
| public void setIssCTIndexFlag(byte flag) { | |||
| this.cFlgIssCTIndex = flag; | |||
| } | |||
| public byte getIssCTIndexFlag() { | |||
| return this.cFlgIssCTIndex; | |||
| } | |||
| public int describeContents() { | |||
| return 0; | |||
| } | |||
| public void writeToParcel(Parcel dest, int flags) { | |||
| dest.writeByteArray(this.tAID); | |||
| dest.writeByteArray(this.tAppLabel); | |||
| dest.writeByteArray(this.tAPN); | |||
| dest.writeByte(this.cAPID); | |||
| dest.writeByte(this.cFlgAPID); | |||
| dest.writeByteArray(this.sLangPref); | |||
| dest.writeByte(this.cIssCTIndex); | |||
| dest.writeByte(this.cFlgIssCTIndex); | |||
| } | |||
| } | |||
| @@ -0,0 +1,52 @@ | |||
| package com.icbc.smartpos.deviceservice.aidl; | |||
| /** | |||
| * 检卡过程监听接口定义 | |||
| * @author: baoxl | |||
| */ | |||
| interface CheckCardListener { | |||
| /** | |||
| * 检测到磁条卡 | |||
| * @param track - 磁卡数据 | |||
| * <ul> | |||
| * <li>PAN(String) - 主账号(卡号)</li> | |||
| * <li>TRACK1(String) - 磁道1数据</li> | |||
| * <li>TRACK2(String) - 磁道2数据 </li> | |||
| * <li>TRACK3(String) - 磁道3数据</li> | |||
| * <li>SERVICE_CODE(String) - 服务码 </li> | |||
| * <li>EXPIRED_DATE(String) - 卡片有效期 </li> | |||
| * </ul> | |||
| */ | |||
| void onCardSwiped(in Bundle track); | |||
| /** | |||
| * IC卡上电成功 | |||
| */ | |||
| void onCardPowerUp(); | |||
| /** | |||
| * 非接卡激活成功 | |||
| */ | |||
| void onCardActivate(); | |||
| /** | |||
| * 检卡超时 | |||
| */ | |||
| void onTimeout(); | |||
| /** | |||
| * 检卡出错 | |||
| * @param error - 错误码 | |||
| * <ul> | |||
| * <li>SERVICE_CRASH(99) - 服务崩溃 </li> | |||
| * <li>REQUEST_EXCEPTION(100) - 请求异常</li> | |||
| * <li>MAG_SWIPE_ERROR(1) - 刷卡失败</li> | |||
| * <li>IC_INSERT_ERROR(2) - 插卡失败</li> | |||
| * <li>IC_POWERUP_ERROR(3) - 卡上电失败</li> | |||
| * <li>RF_PASS_ERROR(4) - 非接挥卡失败</li> | |||
| * <li>RF_ACTIVATE_ERROR(5) - 非接卡激活失败</li> | |||
| * </ul> | |||
| * @param message - 错误描述 | |||
| */ | |||
| void onError(int error, String message); | |||
| } | |||
| @@ -0,0 +1,14 @@ | |||
| package com.icbc.smartpos.deviceservice.aidl; | |||
| public class FontFamily { | |||
| public static final int SMALL = 0; | |||
| public static final int MIDDLE = 1; | |||
| public static final int BIG = 2; | |||
| public static final boolean NEED_BOLD = true; | |||
| public static final boolean NOT_NEED_BOLD = false; | |||
| public static final int LEFT = 0; | |||
| public static final int CENTER = 1; | |||
| public static final int RIGHT = 2; | |||
| } | |||
| @@ -0,0 +1,21 @@ | |||
| package com.icbc.smartpos.deviceservice.aidl; | |||
| /** | |||
| * 蜂鸣器对象 | |||
| * @author: baoxl | |||
| */ | |||
| interface IBeeper { | |||
| /** | |||
| * 开始蜂鸣<br/> | |||
| * 通过调用该方法可以让POS的蜂鸣器持续鸣叫msec毫秒。如果用户设定msec为0则不进行鸣叫。 | |||
| * 蜂鸣器发声是采用非阻塞方式进行的,调用该函数之后,程序立即退出,不会停留在该函数。 | |||
| * @param msec - 需要鸣叫的时间,单位ms | |||
| */ | |||
| void startBeep(int msec); | |||
| /** | |||
| * 停止蜂鸣<br/> | |||
| * 调用该方法后立即停止鸣叫 | |||
| */ | |||
| void stopBeep(); | |||
| } | |||
| @@ -0,0 +1,102 @@ | |||
| package com.icbc.smartpos.deviceservice.aidl; | |||
| import com.icbc.smartpos.deviceservice.aidl.TusnData; | |||
| /** | |||
| * 终端设备信息对象 | |||
| * @author: baoxl | |||
| */ | |||
| interface IDeviceInfo { | |||
| /** | |||
| * 获取终端序列号 | |||
| * @return 终端设备序列号 | |||
| */ | |||
| String getSerialNo(); | |||
| /** | |||
| * 获取终端IMSI号 | |||
| * @return 终端IMSI号 | |||
| */ | |||
| String getIMSI(); | |||
| /** | |||
| * 获取终端IMEI号 | |||
| * @return 终端IMEI号 | |||
| */ | |||
| String getIMEI(); | |||
| /** | |||
| * 获取SIM卡ICCID | |||
| * @return 终端SIM卡ICCID | |||
| */ | |||
| String getICCID(); | |||
| /** | |||
| * 获取厂商名称 | |||
| * @return 终端厂商名称 | |||
| */ | |||
| String getManufacture(); | |||
| /** | |||
| * 获取终端设备型号 | |||
| * @return 终端设备型号 | |||
| */ | |||
| String getModel(); | |||
| /** | |||
| * 获取终端Android操作系统版本 | |||
| * @return 终端Android系统版本 | |||
| */ | |||
| String getAndroidOSVersion(); | |||
| /** | |||
| * 获取终端Android内核版本 | |||
| * @return 终端Android内核版本 | |||
| */ | |||
| String getAndroidKernelVersion(); | |||
| /** | |||
| * 获取终端ROM版本 | |||
| * @return 终端系统ROM版本 | |||
| */ | |||
| String getROMVersion(); | |||
| /** | |||
| * 获取终端固件版本 | |||
| * @return 终端固件版本 | |||
| */ | |||
| String getFirmwareVersion(); | |||
| /** | |||
| * 获取终端硬件版本 | |||
| * @return 终端硬件版本 -后标贴的硬件版本号,如H:L4200151361,测试机器可能没有录入 | |||
| */ | |||
| String getHardwareVersion(); | |||
| /** | |||
| * 更新终端系统时间 | |||
| * @param date - 日期,格式yyyyMMdd | |||
| * @param time - 时间,格式HHmmss | |||
| * @return 更新成功返回true,失败返回false | |||
| **/ | |||
| boolean updateSystemTime(String date, String time); | |||
| /** | |||
| * 设置系统功能(设置全局有效) | |||
| * @param bundle - 设置参数 | |||
| * <ul> | |||
| * <li>HOMEKEY(boolean) – 是否允许使用HOME键,true可用,false禁用(注:在机器重启后会取消屏蔽)</li> | |||
| * <li>STATUSBARKEY(boolean) – 是否允许使用下拉菜单,true可用,false禁用</li> | |||
| * </ul> | |||
| * @return 设置成功返回true,失败返回false | |||
| **/ | |||
| boolean setSystemFunction(in Bundle bundle); | |||
| /** | |||
| * 获取银联终端唯一终端号TUSN | |||
| * @param mode 模式, 预留参数, 需为0 | |||
| * @param input 指 对TUSN计算 MAC 时,参与计算的随机数(随机因子),允许范围:4~10字节 | |||
| * @return 成功返回TUSN数据,失败返回null。 | |||
| **/ | |||
| TusnData getTUSN(int mode, in byte[] input); | |||
| } | |||
| @@ -0,0 +1,88 @@ | |||
| package com.icbc.smartpos.deviceservice.aidl; | |||
| import com.icbc.smartpos.deviceservice.aidl.IBeeper; | |||
| import com.icbc.smartpos.deviceservice.aidl.IDeviceInfo; | |||
| import com.icbc.smartpos.deviceservice.aidl.ILed; | |||
| import com.icbc.smartpos.deviceservice.aidl.IMagCardReader; | |||
| import com.icbc.smartpos.deviceservice.aidl.IDeviceService; | |||
| import com.icbc.smartpos.deviceservice.aidl.IInsertCardReader; | |||
| import com.icbc.smartpos.deviceservice.aidl.IPBOC; | |||
| import com.icbc.smartpos.deviceservice.aidl.IPinpad; | |||
| import com.icbc.smartpos.deviceservice.aidl.IPrinter; | |||
| import com.icbc.smartpos.deviceservice.aidl.IRFCardReader; | |||
| import com.icbc.smartpos.deviceservice.aidl.IScanner; | |||
| import com.icbc.smartpos.deviceservice.aidl.ISerialPort; | |||
| /** | |||
| * 设备服务对象,提供范围终端各外设对象的服务接口 | |||
| * @author: baoxl | |||
| */ | |||
| interface IDeviceService { | |||
| /** | |||
| * 获取蜂鸣器操作对象 | |||
| * @return IBeeper对象,参见IBeeper.aidl类 | |||
| */ | |||
| IBeeper getBeeper(); | |||
| /** | |||
| * 获取LED灯操作对象 | |||
| * @return ILed对象,参见ILed.aidl类 | |||
| */ | |||
| ILed getLed(); | |||
| /** | |||
| * 获取串口操作对象 | |||
| * @return ISerialPort对象,参见ISerialPort.aidl类 | |||
| */ | |||
| ISerialPort getSerialPort(); | |||
| /** | |||
| * 获取扫码器操作对象 | |||
| * @param cameraId - 0前置扫码器,1后置扫码器 | |||
| * @return IScanner对象,参见IScanner.aidl类 | |||
| */ | |||
| IScanner getScanner(int cameraId); | |||
| /** | |||
| * 获取磁卡操作句柄 | |||
| * @return IMagCardReader对象,参见IMagCardReader.aidl类 | |||
| */ | |||
| IMagCardReader getMagCardReader(); | |||
| /** | |||
| * 获取IC卡操作对象 | |||
| * @return IC卡操作对象,参见IInsertCardReader.aidl类 | |||
| */ | |||
| IInsertCardReader getInsertCardReader(); | |||
| /** | |||
| * 获取RF卡操作对象 | |||
| * @return RF卡操作对象,参见IRFCardReader.aidl类 | |||
| */ | |||
| IRFCardReader getRFCardReader(); | |||
| /** | |||
| * 获取密码键盘操作对象 | |||
| * @param kapId 密码键盘kapId索引,每个kapId对一个逻辑密码键盘 | |||
| * @return IPinpad对象,参见IPinpad.aidl类 | |||
| */ | |||
| IPinpad getPinpad(int kapId); | |||
| /** | |||
| * 获取打印机操作对象 | |||
| * @return IPrinter对象,参见IPrinter.aidl类 | |||
| */ | |||
| IPrinter getPrinter(); | |||
| /** | |||
| * 获取PBOC(EMV)流程操作对象 | |||
| * @return IPBOC对象,参见IPBOC.aidl类 | |||
| */ | |||
| IPBOC getPBOC(); | |||
| /** | |||
| * 设备操作对象 | |||
| * @return IDeviceInfo对象,参见IDeviceInfo.aidl类 | |||
| */ | |||
| IDeviceInfo getDeviceInfo(); | |||
| } | |||
| @@ -0,0 +1,43 @@ | |||
| package com.icbc.smartpos.deviceservice.aidl; | |||
| /** | |||
| * 接触式IC读卡器对象 | |||
| * @author: baoxl | |||
| */ | |||
| interface IInsertCardReader { | |||
| /** | |||
| * 卡上电 | |||
| * @return 上电成功true,上电失败false。 | |||
| */ | |||
| boolean powerUp(); | |||
| /** | |||
| * 卡下电 | |||
| * @return 关闭成功true,失败false。 | |||
| */ | |||
| boolean powerDown(); | |||
| /** | |||
| * 卡片是否在位 | |||
| * @return 卡在位true,卡不在位false。 | |||
| */ | |||
| boolean isCardIn(); | |||
| /** | |||
| * APDU数据通讯 | |||
| * @param apdu - apdu数据 | |||
| * @return 成功返回卡片应答数据,失败返回null。 | |||
| */ | |||
| byte[] exchangeApdu(in byte[] apdu); | |||
| /** | |||
| * 卡片复位 | |||
| * @param resetType - 复位类型 | |||
| * <ul> | |||
| * <li>0- WARMRESET</li> | |||
| * <li>1- COLDRESET</li> | |||
| * </ul> | |||
| * @return 卡片复位应答数据。 | |||
| */ | |||
| byte[] cardReset(int resetType); | |||
| } | |||
| @@ -0,0 +1,31 @@ | |||
| package com.icbc.smartpos.deviceservice.aidl; | |||
| /** | |||
| * 设备LED灯对象 | |||
| * @author: baoxl | |||
| */ | |||
| interface ILed { | |||
| /** | |||
| * 点亮LED灯 | |||
| * @param led - 灯标识 | |||
| * <ul> | |||
| * <li> 1 - 蓝灯</li> | |||
| * <li> 2 - 黄灯</li> | |||
| * <li> 3 - 绿灯</li> | |||
| * <li> 4 - 红灯</li> | |||
| * </ul> | |||
| */ | |||
| void turnOn(int led); | |||
| /** | |||
| * 关闭LED灯 | |||
| * @param led - 灯标识 | |||
| * <ul> | |||
| * <li> 1 - 蓝灯</li> | |||
| * <li> 2 - 黄灯</li> | |||
| * <li> 3 - 绿灯</li> | |||
| * <li> 4 - 红灯</li> | |||
| * </ul> | |||
| */ | |||
| void turnOff(int led); | |||
| } | |||
| @@ -0,0 +1,21 @@ | |||
| package com.icbc.smartpos.deviceservice.aidl; | |||
| import com.icbc.smartpos.deviceservice.aidl.MagCardListener; | |||
| /** | |||
| * 磁条卡刷卡器对象 | |||
| * @author: baoxl | |||
| */ | |||
| interface IMagCardReader { | |||
| /** | |||
| * 等待刷卡,获取磁卡卡片的磁道数据 | |||
| * @param timeout - 检卡超时时间(单位秒) | |||
| * @param listener - 磁卡刷卡监听器 | |||
| */ | |||
| void searchCard(int timeout, MagCardListener listener); | |||
| /** | |||
| * 取消等待刷卡 | |||
| */ | |||
| void stopSearch(); | |||
| } | |||
| @@ -0,0 +1,213 @@ | |||
| package com.icbc.smartpos.deviceservice.aidl; | |||
| import com.icbc.smartpos.deviceservice.aidl.CheckCardListener; | |||
| import com.icbc.smartpos.deviceservice.aidl.UPCardListener; | |||
| import com.icbc.smartpos.deviceservice.aidl.PBOCHandler; | |||
| import com.icbc.smartpos.deviceservice.aidl.OnlineResultHandler; | |||
| import com.icbc.smartpos.deviceservice.aidl.CandidateAppInfo; | |||
| /** | |||
| * PBOC金融交易对象<br/> | |||
| * 用来处理金融卡相关的EMV交易流程。 | |||
| * @author: baoxl | |||
| */ | |||
| interface IPBOC { | |||
| /** | |||
| * PBOC检卡 | |||
| * @param cardOption - 支持的卡类型,磁条卡、IC卡和非接卡 | |||
| * <ul> | |||
| * <li>supportMagCard(boolean) - 是否支持磁条卡</li> | |||
| * <li>supportICCard(boolean) - 是否支持IC卡</li> | |||
| * <li>supportRFCard(boolean) - 是否支持非接卡</li> | |||
| * </ul> | |||
| * @param timeout - 超时时间 | |||
| * @param listener - 检卡结果监听器 | |||
| */ | |||
| void checkCard(in Bundle cardOption, int timeout, CheckCardListener listener); | |||
| /** | |||
| * 停止PBOC检卡 | |||
| */ | |||
| void stopCheckCard(); | |||
| /** | |||
| * 读取手机芯片卡 | |||
| * @param listener - 读卡结果监听器 | |||
| */ | |||
| void readUPCard(UPCardListener listener); | |||
| /** | |||
| * 启动PBOC处理流程 | |||
| * @param transType - 交易类型 | |||
| * <ul> | |||
| * <li>EC_BALANCE(1) - 查询电子现金余额</li> | |||
| * <li>TRANSFER(2) - 转账</li> | |||
| * <li>EC_LOAD(3) - 指定账户圈存</li> | |||
| * <li>EC_LOAD_U(4) - 非制定账户圈存</li> | |||
| * <li>EC_LOAD_CASH(5) - 现金圈存/现金充值</li> | |||
| * <li>EC_LOAD_CASH_VOID(6) - 圈存撤销</li> | |||
| * <li>PURCHASE(7) - 消费</li> | |||
| * <li>Q_PURCHASE(8) - 快速支付</li> | |||
| * <li>CHECK_BALANCE(9) - 余额查询</li> | |||
| * <li>PRE_AUTH(10) - 预授权交易</li> | |||
| * <li>SALE_VOID(11) - 消费撤销</li> | |||
| * <li>SIMPLE_PROCESS(12) - 简易流程交易</li> | |||
| * <li>Q_QUERY(13) - qPBOC查询</li> | |||
| * </ul> | |||
| * @param intent - 请求数据 | |||
| * <ul> | |||
| * <li>cardType(int):卡类型,CARD_INSERT(0)-接触式IC卡,CARD_RF(1)-非接触式卡</li> | |||
| * <li>authAmount(long):授权金额(交易金额)</li> | |||
| * <li>isSupportQ(boolean):是否支持QPBOC</li> | |||
| * <li>isSupportSM(boolean):是否支持国密</li> | |||
| * <li>isQPBOCForceOnline(boolean):QPBOC是否支持强制联机</li> | |||
| * <li>merchantName(String):商户名称</li> | |||
| * <li>merchantId(String):商户号</li> | |||
| * <li>terminalId(String):终端号</li> | |||
| * </ul> | |||
| * @param handler - PBOC流程回调处理器 | |||
| */ | |||
| void startPBOC(int transType, in Bundle intent, PBOCHandler handler); | |||
| /** | |||
| * 终止PBOC流程 | |||
| */ | |||
| void abortPBOC(); | |||
| /** | |||
| * 更新AID参数 | |||
| * @param operation - 操作标识,增、删、清空 | |||
| * <ul> | |||
| * <li>1:新增 </li> | |||
| * <li>2:删除 </li> | |||
| * <li>3:清空 </li> | |||
| * </ul> | |||
| * @param aidType - AID参数类型 | |||
| * <ul> | |||
| * <li>1:接触式 </li> | |||
| * <li>2:非接</li> | |||
| * </ul> | |||
| * @param aid - AID参数,构成AID参数的TLV字符串 | |||
| * @return 成功true,失败false | |||
| */ | |||
| boolean updateAID(int operation,int aidType, String aid); | |||
| /** | |||
| * 获取当前aid参数TLV数据列表,在选择完应用后可以调用 | |||
| * @param taglist - 需要获得的tag列表 | |||
| * @return tlv数据,null表示没有数据 | |||
| */ | |||
| String getAidParams(in String[] taglist); | |||
| /** | |||
| * 更新CA公钥参数 | |||
| * @param operation - 操作标识,增、删、清空 | |||
| * <ul> | |||
| * <li>1:新增 </li> | |||
| * <li>2:删除 </li> | |||
| * <li>3:清空 </li> | |||
| * </ul> | |||
| * @param rid - CA公钥参数 | |||
| * @return 成功true,失败false | |||
| */ | |||
| boolean updateRID(int operation, String rid); | |||
| /** | |||
| * 导入金额 | |||
| * @param amount - 交易金额 | |||
| */ | |||
| void importAmount(long amount); | |||
| /** | |||
| * 导入应用选择结果(多应用卡片) | |||
| * @param index - 应用选择的序号从1开始,0为取消 | |||
| */ | |||
| void importAppSelect(int index); | |||
| /** | |||
| * 导入PIN | |||
| * @param option(int) - 操作选项 | |||
| * <ul> | |||
| * <li> CANCEL(0) - 取消 </li> | |||
| * <li> CONFIRM(1) - 确认 </li> | |||
| * </ul> | |||
| * @param pin - PIN数据 | |||
| */ | |||
| void importPin(int option, in byte[] pin); | |||
| /** | |||
| * 导入身份认证结果 | |||
| * @param option - 确认结果 | |||
| * <ul> | |||
| * <li> CANCEL(0) - 取消,跳过(BYPASS) </li> | |||
| * <li> CONFIRM(1) - 确认持卡人身份 </li> | |||
| * <li> NOTMATCH(2) - 身份信息不符 </li> | |||
| * </ul> | |||
| */ | |||
| void importCertConfirmResult(int option); | |||
| /** | |||
| * 导入卡信息确认结果 | |||
| * @param pass - 是否通过 | |||
| */ | |||
| void importCardConfirmResult(boolean pass); | |||
| /** | |||
| * 导入联机应答数据 | |||
| * @param onlineResult - 联机结果 | |||
| * <ul> | |||
| * <li> isOnline(boolean) - 是否联机</li> | |||
| * <li> respCode(String) - 应答码</li> | |||
| * <li> authCode(String) - 授权码 </li> | |||
| * <li> field55(String) - 55域应答数据</li> | |||
| * </ul> | |||
| * @param handler - 联机结果处理 | |||
| */ | |||
| void inputOnlineResult(in Bundle onlineResult, OnlineResultHandler handler); | |||
| /** | |||
| * 设置TLV内核参数 | |||
| * @param tlvList 内置参数TLV列表 | |||
| */ | |||
| void setEMVData(in List<String> tlvList); | |||
| /** | |||
| * 获取PBOC内核TLV数据列表 | |||
| * @param taglist - 需要获得的tag列表 | |||
| * @return tlv数据,null表示没有数据 | |||
| */ | |||
| String getAppTLVList(in String[] taglist); | |||
| /** | |||
| * 获取卡片上EMV数据元 | |||
| * @param tagName - 标签名称 | |||
| * @return 卡片EMV数据 | |||
| */ | |||
| byte[] getCardData(String tagName); | |||
| /** | |||
| * 获取PBOC流程数据,如卡号、有效期、卡片序列号等。<br/> | |||
| * <em>EMV必须执行到相应流程才能获取到对应的卡片数据,否则返回空。</em> | |||
| * @param tagName - 标签名称 | |||
| * <ul> | |||
| * <li> PAN - 卡号</li> | |||
| * <li> TRACK2 - 磁道二数据</li> | |||
| * <li> CARD_SN - 卡片序列号 </li> | |||
| * <li> EXPIRED_DATE - 有效期</li> | |||
| * <li> DATE - 日期</li> | |||
| * <li> TIME - 时间</li> | |||
| * <li> BALANCE - 电子现金余额</li> | |||
| * <li> CURRENCY - 币种</li> | |||
| * </ul> | |||
| * @return PBOC流程中内核返回的数据 | |||
| */ | |||
| String getPBOCData(String tagName); | |||
| /** | |||
| * 获取卡片候选应用信息<br/> | |||
| * 用于电子签名上送和小额免签免密。 | |||
| * @return 卡片候选应用信息,具体见{@link CandidateAppInfo}定义 | |||
| */ | |||
| CandidateAppInfo getCandidateAppInfo(); | |||
| } | |||
| @@ -0,0 +1,113 @@ | |||
| package com.icbc.smartpos.deviceservice.aidl; | |||
| import com.icbc.smartpos.deviceservice.aidl.PinInputListener; | |||
| /** | |||
| * 密码键盘对象<br/> | |||
| * 实现金融交易过程中密钥管理、数据加密和PIN输入处理过程。 | |||
| * @author: baoxl | |||
| */ | |||
| interface IPinpad { | |||
| /** | |||
| * 判断密钥是否存在(密钥已下载)<br/> | |||
| * @param keyType - 密钥类型 | |||
| * <ul> | |||
| * <li>0-主密钥</li> | |||
| * <li>1-MAC密钥</li> | |||
| * <li>2-PIN密钥</li> | |||
| * <li>3-TD密钥</li> | |||
| * </ul> | |||
| * @param keyId - 密钥ID | |||
| * @return 存在返回true,不存在返回false | |||
| **/ | |||
| boolean isKeyExist(int keyType, int keyId); | |||
| /** | |||
| * 下装TEK密钥<br/> | |||
| * TEK是加密主密钥的密钥KEK | |||
| * @param keyId - 密钥存储的ID | |||
| * @param key - 密钥 | |||
| * @param checkValue - 校验值 | |||
| * @return 成功true,失败false | |||
| */ | |||
| boolean loadTEK(int keyId, in byte[] key, in byte[] checkValue); | |||
| /** | |||
| * 下装密文主密钥<br/> | |||
| * @param keyId - 下装密钥存储ID | |||
| * @param key - 密钥 | |||
| * @param checkValue - 校验值 | |||
| * @return 成功true,失败false | |||
| */ | |||
| boolean loadEncryptMainKey(int keyId, in byte[] key, in byte[] checkValue); | |||
| /** | |||
| * 下装明文主密钥<br/> | |||
| * @param keyId - 下装密钥存储ID | |||
| * @param key - 密钥 | |||
| * @param checkValue - 校验值 | |||
| * @return 成功true,失败false | |||
| */ | |||
| boolean loadMainKey(int keyId, in byte[] key, in byte[] checkValue); | |||
| /** | |||
| * 下装工作密钥<br/> | |||
| * @param keyType - 下装工作密钥类型,1-MAC密钥,2-PIN密钥,3-TD密钥 | |||
| * @param mkId - 解密工作密钥的主密钥ID | |||
| * @param keyId - 下装密钥存储ID | |||
| * @param key - 密钥 | |||
| * @param checkValue - 校验值 | |||
| * @return 成功true,失败false | |||
| */ | |||
| boolean loadWorkKey(int keyType, int mkId, int wkId, in byte[] key, in byte[] checkValue); | |||
| /** | |||
| * 计算MAC值<br/> | |||
| * @param keyId - MAC密钥索引 | |||
| * @param data - 计算MAC的数据 | |||
| * @return 成功返回mac值,失败返回null | |||
| */ | |||
| byte[] calcMAC(int keyId, in byte[] data); | |||
| /** | |||
| * 加密磁道数据<br/> | |||
| * @param mode - 加密模式 | |||
| * @param keyId - TDK密钥索引 | |||
| * @param trkData - 待加密磁道数据 | |||
| * @return 成功返回加密后的磁道数据,失败返回null | |||
| */ | |||
| byte[] encryptTrackData(int mode, int keyId, in byte[] trkData); | |||
| /** | |||
| * 启动PIN输入<br/> | |||
| * @param keyId - PIN密钥索引 | |||
| * @param param - PIN输入配置参数 | |||
| * <ul> | |||
| * <li>pinLimit(byte[]) - 允许输入密码的长度</li> | |||
| * <li>timeout(int) - 输入超时时间,单位(秒)</li> | |||
| * <li>isOnline(boolean) - 是否联机PIN</li> | |||
| * <li>promptString(String) - 提示信息</li> | |||
| * <li>pan(String) - 用于加密联机PIN的主帐号(卡号)</li> | |||
| * </ul> | |||
| * @param listener - PIN输入过程监听器 | |||
| * @return | |||
| */ | |||
| void startPinInput(int keyId, in Bundle param, PinInputListener listener); | |||
| /** | |||
| * 提交PIN输入 | |||
| */ | |||
| void submitPinInput(); | |||
| /** | |||
| * 取消PIN输入过程 | |||
| */ | |||
| void stopPinInput(); | |||
| /** | |||
| * 获取最近一次操作的错误<br/> | |||
| * @return 错误描述 | |||
| */ | |||
| String getLastError(); | |||
| } | |||
| @@ -0,0 +1,105 @@ | |||
| package com.icbc.smartpos.deviceservice.aidl; | |||
| import com.icbc.smartpos.deviceservice.aidl.PrinterListener; | |||
| /** | |||
| * 打印机对象<br/> | |||
| * 实现对文本、条形码、二维码和图片的打印功能。 | |||
| * @author: baoxl | |||
| */ | |||
| interface IPrinter { | |||
| /** | |||
| * 获取打印机状态 | |||
| * @return 打印机状态 | |||
| * <ul> | |||
| * <li>ERROR_NONE(0x00) - 状态正常</li> | |||
| * <li>ERROR_PAPERENDED(0xF0) - 缺纸,不能打印</li> | |||
| * <li>ERROR_HARDERR(0xF2) - 硬件错误</li> | |||
| * <li>ERROR_OVERHEAT(0xF3) - 打印头过热</li> | |||
| * <li>ERROR_BUFOVERFLOW(0xF5) - 缓冲模式下所操作的位置超出范围 </li> | |||
| * <li>ERROR_LOWVOL(0xE1) - 低压保护 </li> | |||
| * <li>ERROR_PAPERENDING(0xF4) - 纸张将要用尽,还允许打印(单步进针打特有返回值)</li> | |||
| * <li>ERROR_MOTORERR(0xFB) - 打印机芯故障(过快或者过慢)</li> | |||
| * <li>ERROR_PENOFOUND(0xFC) - 自动定位没有找到对齐位置,纸张回到原来位置 </li> | |||
| * <li>ERROR_PAPERJAM(0xEE) - 卡纸</li> | |||
| * <li>ERROR_NOBM(0xF6) - 没有找到黑标</li> | |||
| * <li>ERROR_BUSY(0xF7) - 打印机处于忙状态</li> | |||
| * <li>ERROR_BMBLACK(0xF8) - 黑标探测器检测到黑色信号</li> | |||
| * <li>ERROR_WORKON(0xE6) - 打印机电源处于打开状态</li> | |||
| * <li>ERROR_LIFTHEAD(0xE0) - 打印头抬起(自助热敏打印机特有返回值)</li> | |||
| * <li>ERROR_CUTPOSITIONERR(0xE2) - 切纸刀不在原位(自助热敏打印机特有返回值)</li> | |||
| * <li>ERROR_LOWTEMP(0xE3) - 低温保护或AD出错(自助热敏打印机特有返回值)</li> | |||
| * </ul> | |||
| */ | |||
| int getStatus(); | |||
| /** | |||
| * 设置打印灰度 | |||
| * @param gray - 打印灰度0~10 | |||
| */ | |||
| void setGray(int gray); | |||
| /** | |||
| * 添加一行打印文本 | |||
| * @param format - 打印字体格式 | |||
| * <ul> | |||
| * <li>font(int) - 0:small, 1:normal, 2:large</li> | |||
| * <li>align(int) - 0:left, 1:center, 2:right</li> | |||
| * </ul> | |||
| * @param text - 打印文本 | |||
| */ | |||
| void addText(in Bundle format, String text); | |||
| /** | |||
| * 添加条码打印 | |||
| * @param format - 打印格式,可设置打印的位置、宽度、高度 | |||
| * <ul> | |||
| * <li>align(int) - 0:left, 1:center, 2:right</li> | |||
| * <li>width(int) - 宽度</li> | |||
| * <li>height(int) - 高度</li> | |||
| * </ul> | |||
| * @param barcode - 条码内容 | |||
| */ | |||
| void addBarCode(in Bundle format, in String barcode); | |||
| /** | |||
| * 添加二维码打印 | |||
| * @param format - 打印格式,可设置打印的位置、期望高度 | |||
| * <ul> | |||
| * <li>offset(int) - 打印起始位置 </li> | |||
| * <li>expectedHeight(int) - 期望高度</li> | |||
| * </ul> | |||
| * @param qrCode - 二维码内容 | |||
| */ | |||
| void addQrCode(in Bundle format, String qrCode); | |||
| /** | |||
| * 添加图片打印 | |||
| * @param format - 打印格式,可设置打印的位置、宽度、高度 | |||
| * <ul> | |||
| * <li>offset(int) - 打印起始位置</li> | |||
| * <li>width(int) - 宽度</li> | |||
| * <li>height(int) - 高度</li> | |||
| * </ul> | |||
| * @param imageData - 图片数据 | |||
| */ | |||
| void addImage(in Bundle format, in byte[] imageData); | |||
| /** | |||
| * 走纸 | |||
| * @param lines - 行数 | |||
| */ | |||
| void feedLine(int lines); | |||
| /** | |||
| * 启动打印 | |||
| * @param listener - 打印结果监听器 | |||
| */ | |||
| void startPrint(PrinterListener listener); | |||
| /** | |||
| * 启动打印(打印不清缓存) | |||
| * @param listener - 打印结果监听器 | |||
| */ | |||
| void startSaveCachePrint(PrinterListener listener); | |||
| } | |||
| @@ -0,0 +1,127 @@ | |||
| package com.icbc.smartpos.deviceservice.aidl; | |||
| import com.icbc.smartpos.deviceservice.aidl.RFSearchListener; | |||
| /** | |||
| * 非接触式IC读卡器对象 | |||
| * @author: baoxl | |||
| */ | |||
| interface IRFCardReader { | |||
| /** | |||
| * 非接寻卡上电<br/> | |||
| * @param listener - 寻卡监听器 | |||
| */ | |||
| void searchCard(RFSearchListener listener); | |||
| /** | |||
| * 停止寻卡操作 | |||
| * @return 关闭成功true,失败false。 | |||
| */ | |||
| void stopSearch(); | |||
| /** | |||
| * 激活卡片<br/> | |||
| * @param driver - 驱动名称 | |||
| * <ul> | |||
| * <li>"S50" - S50卡</li> | |||
| * <li>"S70" - S70卡</li> | |||
| * <li>"CPU" - CPU卡</li> | |||
| * <li>"PRO" - PRO、S5O_PRO、S70_PRO卡</li> | |||
| * </ul> | |||
| * @param responseData - 激活卡片应答数据 | |||
| * @return 返回0表示激活成功,其他失败。 | |||
| */ | |||
| int activate(String driver, out byte[] responseData); | |||
| /** | |||
| * 关闭设备,再次操作需从寻卡开始<br/> | |||
| */ | |||
| void halt(); | |||
| /** | |||
| * 卡片是否在位 | |||
| * @return 卡在位true,卡不在位false。 | |||
| */ | |||
| boolean isExist(); | |||
| /** | |||
| * APDU数据通讯 | |||
| * @param apdu - apdu数据 | |||
| * @return 成功返回卡片应答数据。 | |||
| */ | |||
| byte[] exchangeApdu(in byte[] apdu); | |||
| /** | |||
| * 卡片复位 | |||
| * @param resetType - 复位类型 | |||
| * <ul> | |||
| * <li>0- WARMRESET</li> | |||
| * <li>1- COLDRESET</li> | |||
| * </ul> | |||
| * @return 卡片复位应答数据。 | |||
| */ | |||
| byte[] cardReset(int resetType); | |||
| /** | |||
| * 认证(根据块号) | |||
| * <ul> | |||
| * <li>对卡执行读写,修改的操作时必须先进行认证,而且同时只能认证一个扇区,在对不同的扇区进行操作时需要再做认证。</li><br/> | |||
| * <li>该接口需要指定认证块的具体位置</li> | |||
| * </ul> | |||
| * @param blockNo 块号,从0开始 | |||
| * @param keyType 密钥类型 KEY_A 或者 KEY_B | |||
| * @param key 密钥,6字节长 | |||
| * @return 成功0x00,其他失败 | |||
| */ | |||
| int authBlock(int blockNo, int keyType, in byte[] key); | |||
| /** | |||
| * 认证(根扇区号)<br/> | |||
| * <ul> | |||
| * <li>对卡执行读写,修改的操作时必须先进行认证,而且同时只能认证一个扇区,在对不同的扇区进行操作时需要再做认证。</li><br/> | |||
| * <li>该接口只需要指定要操作的扇区,会自动对该扇区的第3块(从0起)进行认证操作</li> | |||
| * </ul> | |||
| * @param sectorNo 扇区号,从0开始 | |||
| * @param keyType 密钥类型 KEY_A 或者 KEY_B | |||
| * @param key 密钥,6字节长 | |||
| * @return 成功0x00,其他失败 | |||
| */ | |||
| int authSector(int sectorNo, int keyType, in byte[] key); | |||
| /** | |||
| * 读块数据 | |||
| * @param blockNo - 块号 | |||
| * @param data - 读取返回的块数据(16字节数据) | |||
| * @return 成功0x00,其他失败 | |||
| */ | |||
| int readBlock(int blockNo, out byte[] data); | |||
| /** | |||
| * 写块数据<br/> | |||
| * <ul> | |||
| * <li>必须是16个字节的数据,没有达到字节数将认为是错误。</li> | |||
| * </ul> | |||
| * @param blockNo - 块号 | |||
| * @param data - 指定数据写到指定的块里 | |||
| * @return 成功0x00,其他失败 | |||
| */ | |||
| int writeBlock(int blockNo, in byte[] data); | |||
| /** | |||
| * 加值</br> | |||
| * 把指定的值加至一数值块中<br/> | |||
| * @param blockNo - 块号 | |||
| * @param value - 数据 | |||
| * @return 成功0x00,其他失败 | |||
| */ | |||
| int increaseValue(int blockNo, int value); | |||
| /** | |||
| * 减值</br> | |||
| * 从一数值块中减去指定的值<br/> | |||
| * @param blockNo - 块号 | |||
| * @param value - 数据 | |||
| * @return 成功0x00,其他失败 | |||
| */ | |||
| int decreaseValue(int blockNo, int value); | |||
| } | |||
| @@ -0,0 +1,25 @@ | |||
| package com.icbc.smartpos.deviceservice.aidl; | |||
| import com.icbc.smartpos.deviceservice.aidl.ScannerListener; | |||
| /** | |||
| * 扫码器对象<br/> | |||
| * 实现设备对二维码扫码功能。 | |||
| * @author: baoxl | |||
| */ | |||
| interface IScanner { | |||
| /** | |||
| * 启动扫码 | |||
| * @param配置参数 | |||
| * upPromptString(String):扫描框上方提示信息,最大20汉字,默认中间对齐, | |||
| * downPromptString(String):扫描框下方提示信息,最大20汉字,默认中间对齐 | |||
| * timeout - 超时时间,单位ms | |||
| * listener - 扫码结果监听 | |||
| */ | |||
| void startScan(in Bundle param, long timeout, ScannerListener listener); | |||
| /** | |||
| * 停止扫码 | |||
| */ | |||
| void stopScan(); | |||
| } | |||
| @@ -0,0 +1,75 @@ | |||
| package com.icbc.smartpos.deviceservice.aidl; | |||
| /** | |||
| * 串口设备对象<br/> | |||
| * 实现串口数据收发功能。 | |||
| * @author: baoxl | |||
| */ | |||
| interface ISerialPort { | |||
| /** | |||
| * 打开串口 | |||
| * @return 成功true,失败false | |||
| */ | |||
| boolean open(); | |||
| /** | |||
| * 关闭串口 | |||
| * @return 成功true,失败false | |||
| */ | |||
| boolean close(); | |||
| /** | |||
| * 初始化串口 | |||
| * @param bps 波特率 | |||
| * <ul> | |||
| * <li>1200 - 对应1200波特率</li> | |||
| * <li>2400 - 对应2400波特率</li> | |||
| * <li>4800 - 对应4800波特率</li> | |||
| * <li>9600 - 对应9600波特率</li> | |||
| * <li>14400 - 对应14400波特率</li> | |||
| * <li>19200 - 对应19200波特率</li> | |||
| * <li>28800 - 对应28800波特率</li> | |||
| * <li>38400 - 对应38400波特率</li> | |||
| * <li>57600 - 对应57600波特率</li> | |||
| * <li>115200 - 对应115200波特率</li> | |||
| * </ul> | |||
| * @param par 效验 | |||
| * <ul> | |||
| * <li>0 - 不校验</li> | |||
| * <li>1 - 奇校验</li> | |||
| * <li>2 - 偶校验</li> | |||
| * </ul> | |||
| * @param dbs 数据位 | |||
| * @return 成功true,失败false | |||
| */ | |||
| boolean init(int bps, int par, int dbs); | |||
| /** | |||
| * 读数据(接收) | |||
| * @param buffer 缓冲区 | |||
| * @param timeout 超时时间,毫秒 | |||
| * @return 返回实际读取的数据长度,失败返回-1 | |||
| */ | |||
| int read(inout byte[] buffer, int timeout); | |||
| /** | |||
| * 写数据(发送) | |||
| * @param data 要发送的数据 | |||
| * @param timeout 超时时间,毫秒 | |||
| * @return 返回实际写入的数据长度,失败返回-1 | |||
| */ | |||
| int write(in byte[] data, int timeout); | |||
| /** | |||
| * 清除接收缓冲区 | |||
| * @return 成功true,失败false | |||
| */ | |||
| boolean clearInputBuffer(); | |||
| /** | |||
| * 查看缓冲区是否为空 | |||
| * @param input true为输入缓冲区,false为输出缓冲区 | |||
| * @return 成功true,失败false | |||
| */ | |||
| boolean isBufferEmpty(boolean input); | |||
| } | |||
| @@ -0,0 +1,38 @@ | |||
| package com.icbc.smartpos.deviceservice.aidl; | |||
| /** | |||
| * 磁条卡刷卡结果回调 | |||
| * @author: baoxl | |||
| */ | |||
| interface MagCardListener { | |||
| /** | |||
| * 刷卡成功回调 | |||
| * @param track - 磁卡数据对象 | |||
| * <ul> | |||
| * <li>PAN(String) - 主账号(卡号)</li> | |||
| * <li>TRACK1(String) - 磁道1数据</li> | |||
| * <li>TRACK2(String) - 磁道2数据 </li> | |||
| * <li>TRACK3(String) - 磁道3数据</li> | |||
| * <li>SERVICE_CODE(String) - 服务码 </li> | |||
| * <li>EXPIRED_DATE(String) - 卡片有效期 </li> | |||
| * </ul> | |||
| */ | |||
| void onSuccess(in Bundle track); | |||
| /** | |||
| * 刷卡失败回调 | |||
| * @param error - 错误码 | |||
| * <ul> | |||
| * <li>SERVICE_CRASH(99) - 服务崩溃 </li> | |||
| * <li>REQUEST_EXCEPTION(100) - 请求异常</li> | |||
| * <li>MAG_SWIPE_ERROR(1) - 刷卡失败</li> | |||
| * </ul> | |||
| * @param message - 错误描述 | |||
| */ | |||
| void onError(int error,String message ); | |||
| /** | |||
| * 刷卡超时回调 | |||
| */ | |||
| void onTimeout(); | |||
| } | |||
| @@ -0,0 +1,30 @@ | |||
| package com.icbc.smartpos.deviceservice.aidl; | |||
| /** | |||
| * PBOC流程联机应答数据处理 | |||
| * @author: baoxl | |||
| */ | |||
| interface OnlineResultHandler { | |||
| /** | |||
| * 联机应答数据处理回调<br/> | |||
| * @param result - 处理结果 | |||
| * <ul> | |||
| * <li>ONLINE_RESULT_TC(0) - 联机成功 </li> | |||
| * <li>ONLINE_RESULT_AAC(1) - 联机拒绝 </li> | |||
| * <li>ONLINE_RESULT_OFFLINE_TC(101) - 联机失败,脱机成功 </li> | |||
| * <li>ONLINE_RESULT_SCRIPT_NOT_EXECUTE(102) - 脚本未执行 </li> | |||
| * <li>ONLINE_RESULT_SCRIPT_EXECUTE_FAIL(103) - 脚本执行失败 </li> | |||
| * <li>ONLINE_RESULT_NO_SCRIPT(104) - 联机失败,未下送脚本 </li> | |||
| * <li>ONLINE_RESULT_TOO_MANY_SCRIPT(105) - 联机失败,脚本超过1个 </li> | |||
| * <li>ONLINE_RESULT_TERMINATE(106) - 联机失败,交易终止(GAC返回非9000,要提示交易终止,0x8F) </li> | |||
| * <li>ONLINE_RESULT_ERROR(107) - 联机失败,EMV内核错误</li> | |||
| * </ul> | |||
| * @param data - 结果数据 | |||
| * <ul> | |||
| * <li>TC_DATA(String) - 交易结果TLV数据 </li> | |||
| * <li>SCRIPT_DATA(String) - 脚本结果TLV数据 </li> | |||
| * <li>REVERSAL_DATA(String) - 冲正TLV数据 </li> | |||
| * </ul> | |||
| */ | |||
| void onProccessResult(int result, in Bundle data); | |||
| } | |||
| @@ -0,0 +1,100 @@ | |||
| package com.icbc.smartpos.deviceservice.aidl; | |||
| /** | |||
| * PBOC交易流程回调接口 | |||
| * @author: baoxl | |||
| */ | |||
| interface PBOCHandler { | |||
| /** | |||
| * 请求输入金额 | |||
| */ | |||
| void onRequestAmount(); | |||
| /** | |||
| * 请求应用选择 | |||
| * @param appList - 应用列表 | |||
| */ | |||
| void onSelectApplication(in List<String> appList); | |||
| /** | |||
| * 要求确认卡信息 | |||
| * @param info - 卡片信息 | |||
| * <ul> | |||
| * <li>PAN(String) - 主账号(卡号)</li> | |||
| * <li>TRACK2(String) - 磁道2数据 </li> | |||
| * <li>CARD_SN(String) - 卡片序列号 </li> | |||
| * <li>SERVICE_CODE(String) - 服务码 </li> | |||
| * <li>EXPIRED_DATE(String) - 卡片有效期 </li> | |||
| * </ul> | |||
| */ | |||
| void onConfirmCardInfo(in Bundle info); | |||
| /** | |||
| * 要求输入PIN | |||
| * @param isOnlinePin 是否是联机pin | |||
| * @param retryTimes 脱机pin的输入次数 | |||
| */ | |||
| void onRequestInputPIN(boolean isOnlinePin, int retryTimes); | |||
| /** | |||
| * 要求确认持卡人证件 | |||
| * @param certType - 认证类型 | |||
| * @param certInfo - 认证信息 | |||
| */ | |||
| void onConfirmCertInfo(String certType, String certInfo); | |||
| /** | |||
| * 联机处理请求 | |||
| * @param aaResult 卡片分析结果,联机数据 | |||
| * <ul> | |||
| * <li>RESULT(int) - 结果类型:QPBOC_ARQC(201) - qPBOC联机请求; AARESULT_ARQC(2) - 行为分析结果ARQC</li> | |||
| * <li>ARQC_DATA(String) - 联机请求卡片55域数据 </li> | |||
| * <li>REVERSAL_DATA(String) - IC卡冲正TLV数据 </li> | |||
| * </ul> | |||
| */ | |||
| void onRequestOnlineProcess(in Bundle aaResult); | |||
| /** | |||
| * PBOC交易结果,见于简易流程,qPBOC,以及交易过程失败等 | |||
| * @param result - 交易结果 | |||
| * <ul> | |||
| * <li>EMV_COMPLETE(9) - EMV简易流程结束 </li> | |||
| * <li>EMV_ERROR(11) - EMV内核错误</li> | |||
| * <li>EMV_FALLBACK(12) - FALLBACK </li> | |||
| * <li>EMV_DATA_AUTH_FAIL(13) - 脱机数据认证失败 </li> | |||
| * <li>EMV_APP_BLOCKED(14) - 应用被锁定 </li> | |||
| * <li>EMV_NOT_ECCARD(15) - 非电子现金卡 </li> | |||
| * <li>EMV_UNSUPPORT_ECCARD(16) - 该交易不支持电子现金卡 </li> | |||
| * <li>EMV_AMOUNT_EXCEED_ON_PURELYEC(17) - 纯电子现金卡消费金额超限 </li> | |||
| * <li>EMV_SET_PARAM_ERROR(18) - 参数设置错误(9F7A) </li> | |||
| * <li>EMV_PAN_NOT_MATCH_TRACK2(19) - 主账号与二磁道不符 </li> | |||
| * <li>EMV_CARD_HOLDER_VALIDATE_ERROR(20) - 持卡人认证失败 </li> | |||
| * <li>EMV_PURELYEC_REJECT(21) - 纯电子现金卡被拒绝交易 </li> | |||
| * <li>EMV_BALANCE_INSUFFICIENT(22) - 余额不足 </li> | |||
| * <li>EMV_AMOUNT_EXCEED_ON_RFLIMIT_CHECK(23) - 交易金额超过非接限额检查 </li> | |||
| * <li>EMV_CARD_BIN_CHECK_FAIL(24) - 卡BIN检查失败 </li> | |||
| * <li>EMV_CARD_BLOCKED(25) - 卡被锁 </li> | |||
| * <li>EMV_MULTI_CARD_ERROR(26) - 多卡冲突 </li> | |||
| * <li>EMV_BALANCE_EXCEED(27) - 余额超出 </li> | |||
| * <li>EMV_RFCARD_PASS_FAIL(60) - 挥卡失败 </li> | |||
| * <li>EMV_IN_QPBOC_PROCESS(99) - QPBOC流程处理中 </li> | |||
| * | |||
| * <li>AARESULT_TC(0) - 行为分析结果,交易批准(脱机)</li> | |||
| * <li>AARESULT_AAC(1) - 行为分析结果,交易拒绝 </li> | |||
| * <li>QPBOC_AAC(202) - qPBOC交易结果,交易拒绝</li> | |||
| * <li>QPBOC_ERROR(203) - qPBOC交易结果,交易失败 </li> | |||
| * <li>QPBOC_TC(204) - qPBOC交易结果,交易批准 </li> | |||
| * <li>QPBOC_CONT(205) - qPBOC结果,转接触式卡 </li> | |||
| * <li>QPBOC_NO_APP(206) - qPBOC交易结果,无应用(可转UP Card)</li> | |||
| * <li>QPBOC_NOT_CPU_CARD(207) - qPBOC交易结果,该卡非TYPE B/PRO卡</li> | |||
| * </ul> | |||
| * | |||
| * @param data 交易结果数据 | |||
| * <ul> | |||
| * <li>TC_DATA(String) - IC卡交易批准卡片返回数据 </li> | |||
| * <li>REVERSAL_DATA(String) - IC卡冲正数据 </li> | |||
| * <li>ERROR(String) - 错误描述(PBOC流程错误返回) </li> | |||
| * </ul> | |||
| */ | |||
| void onTransactionResult(int result, in Bundle data); | |||
| } | |||
| @@ -0,0 +1,32 @@ | |||
| package com.icbc.smartpos.deviceservice.aidl; | |||
| /** | |||
| * PIN输入过程监听器 | |||
| * @author: baoxl | |||
| */ | |||
| interface PinInputListener { | |||
| /** | |||
| * 按键按压事件 | |||
| * @param len - 已输入密码长度 | |||
| * @param key - 当前的Key值 | |||
| */ | |||
| void onInput(int len, int key); | |||
| /** | |||
| * 用户确认PIN输入时调用 | |||
| * @param data - pin码,输入为空时候,为null | |||
| * @param isNonePin - 输入为空的时候为true | |||
| */ | |||
| void onConfirm(in byte[] data, boolean isNonePin); | |||
| /** | |||
| * 取消PIN输入时调用 | |||
| */ | |||
| void onCancel(); | |||
| /** | |||
| * 错误时回调 | |||
| * @param errorCode - 错误码 | |||
| */ | |||
| void onError(int errorCode); | |||
| } | |||
| @@ -0,0 +1,16 @@ | |||
| package com.icbc.smartpos.deviceservice.aidl; | |||
| public class PinpadKeyType{ | |||
| // ABC中密钥类型0: MASTER KEY | |||
| public static final int MASTERKEY = 0; | |||
| //1:MAC key | |||
| public static final int MACKEY = 1; | |||
| // 2:PIN key | |||
| public static final int PINKEY = 2; | |||
| //3:TDK key | |||
| public static final int TDKEY = 3; | |||
| } | |||
| @@ -0,0 +1,10 @@ | |||
| package com.icbc.smartpos.deviceservice.aidl; | |||
| public class PrinterConfig { | |||
| /**字体,默认小字体**/ | |||
| public static final String BUNDLE_PRINT_FONT = "font"; | |||
| /**对齐方式,默认左对齐**/ | |||
| public static final String BUNDLE_PRINT_ALIGN = "align"; | |||
| } | |||
| @@ -0,0 +1,18 @@ | |||
| package com.icbc.smartpos.deviceservice.aidl; | |||
| /** | |||
| * 打印结果监听器 | |||
| * @author: baoxl | |||
| */ | |||
| interface PrinterListener { | |||
| /** | |||
| * 打印成功回调 | |||
| */ | |||
| void onFinish(); | |||
| /** | |||
| * 打印失败回调 | |||
| * @param error - 错误码 | |||
| */ | |||
| void onError(int error); | |||
| } | |||
| @@ -0,0 +1,37 @@ | |||
| package com.icbc.smartpos.deviceservice.aidl; | |||
| /** | |||
| * 非接寻卡过程监听接口定义 | |||
| * @author: baoxl | |||
| */ | |||
| interface RFSearchListener { | |||
| /** | |||
| * 检测到磁条卡 | |||
| * @param cardType - 卡类型 | |||
| * <ul> | |||
| * <li>S50_CARD(0x00) - S50卡</li> | |||
| * <li>S70_CARD(0x01) - S70卡</li> | |||
| * <li>PRO_CARD(0x02) - PRO卡</li> | |||
| * <li>S50_PRO_CARD(0x03) - 支持S50驱动与PRO驱动的PRO卡</li> | |||
| * <li>S70_PRO_CARD(0x04) - 支持S70驱动与PRO驱动的PRO卡 </li> | |||
| * <li>CPU_CARD(0x05) - CPU卡</li> | |||
| * </ul> | |||
| */ | |||
| void onCardPass(int cardType); | |||
| /** | |||
| * 寻卡失败回调 | |||
| * @param error - 错误码 | |||
| * <ul> | |||
| * <li>ERROR_TRANSERR(0xA2) - 通讯错误 </li> | |||
| * <li>ERROR_PROTERR(0xA3) - 卡片返回数据不符合规范要求</li> | |||
| * <li>ERROR_MULTIERR(0xA4) - 感应区内多卡存在</li> | |||
| * <li>ERROR_CARDTIMEOUT(0xA7) - 超时无响应</li> | |||
| * <li>ERROR_CARDNOACT(0xB3) - Pro卡或者TypeB卡未激活</li> | |||
| * <li>ERROR_MCSERVICE_CRASH(0xff01) - 主控服务异常</li> | |||
| * <li>ERROR_REQUEST_EXCEPTION(0xff02) - 请求异常</li> | |||
| * </ul> | |||
| * @param message - 错误描述 | |||
| */ | |||
| void onFail(int error, String message); | |||
| } | |||
| @@ -0,0 +1,30 @@ | |||
| package com.icbc.smartpos.deviceservice.aidl; | |||
| /** | |||
| * 扫码器扫码结果监听器 | |||
| * @author: baoxl | |||
| */ | |||
| interface ScannerListener { | |||
| /** | |||
| * 扫码成功回调 | |||
| * @param barcode - 条码 | |||
| */ | |||
| void onSuccess(String barcode); | |||
| /** | |||
| * 扫码出错 | |||
| * @param error - 错误码 | |||
| * @param message - 错误描述 | |||
| */ | |||
| void onError(int error, String message); | |||
| /** | |||
| * 扫码超时回调 | |||
| */ | |||
| void onTimeout(); | |||
| /** | |||
| * 扫码取消回调 | |||
| */ | |||
| void onCancel(); | |||
| } | |||
| @@ -0,0 +1,3 @@ | |||
| package com.icbc.smartpos.deviceservice.aidl; | |||
| parcelable TusnData; | |||
| @@ -0,0 +1,75 @@ | |||
| package com.icbc.smartpos.deviceservice.aidl; | |||
| import android.os.Parcel; | |||
| import android.os.Parcelable; | |||
| /** | |||
| * TUSN数据 | |||
| * @author baoxl | |||
| * | |||
| */ | |||
| public class TusnData implements Parcelable { | |||
| public static final Parcelable.Creator<TusnData> CREATOR = new Parcelable.Creator<TusnData>() { | |||
| @Override | |||
| public TusnData createFromParcel(Parcel source) { | |||
| return new TusnData(source); | |||
| } | |||
| @Override | |||
| public TusnData[] newArray(int size) { | |||
| return new TusnData[size]; | |||
| } | |||
| }; | |||
| /** 终端类型: 1为旧终端 2为新终端 */ | |||
| private int mTerminalType; | |||
| /** 使用终端TUSN_AUK对传入随机数与TUSN进行MAC运算的结果,对于旧终端,为8个空格 */ | |||
| private String mMac; | |||
| /** 唯一终端序列号 */ | |||
| private String mTusn; | |||
| protected TusnData(Parcel source) { | |||
| mTerminalType = source.readInt(); | |||
| mMac = source.readString(); | |||
| mTusn = source.readString(); | |||
| } | |||
| public TusnData() {} | |||
| public void setTerminalType(int type) { | |||
| mTerminalType = type; | |||
| } | |||
| public void setMac(String mac) { | |||
| mMac = mac; | |||
| } | |||
| public void setTusn(String tusn) { | |||
| mTusn = tusn; | |||
| } | |||
| public String getMac() { | |||
| return mMac; | |||
| } | |||
| public String getTusn() { | |||
| return mTusn; | |||
| } | |||
| public int getTerminalType() { | |||
| return mTerminalType; | |||
| } | |||
| @Override | |||
| public int describeContents() { | |||
| return 0; | |||
| } | |||
| @Override | |||
| public void writeToParcel(Parcel dest, int flags) { | |||
| dest.writeInt(mTerminalType); | |||
| dest.writeString(mMac); | |||
| dest.writeString(mTusn); | |||
| } | |||
| } | |||
| @@ -0,0 +1,35 @@ | |||
| package com.icbc.smartpos.deviceservice.aidl; | |||
| /** | |||
| * 读手机芯片卡结果监听器 | |||
| * @author: baoxl | |||
| */ | |||
| interface UPCardListener { | |||
| /** | |||
| * 读卡成功回调 | |||
| * @param data - 芯片卡卡数据 | |||
| * <ul> | |||
| * <li>PAN(String) - 主账号(卡号)</li> | |||
| * <li>TRACK2(String) - 磁道2数据 </li> | |||
| * <li>TRACK3(String) - 磁道3数据</li> | |||
| * <li>CARD_SN(String) - 卡片序列号</li> | |||
| * <li>EXPIRED_DATE(String) - 卡片有效期 </li> | |||
| * <li>TLV_DATA(String) - 带标签(DF32,DF33,DF34)TLV数据</li> | |||
| * </ul> | |||
| */ | |||
| void onRead(out Bundle data); | |||
| /** | |||
| * 读卡错误回调 | |||
| * @param error - 错误码 | |||
| * <ul> | |||
| * <li>ERROR_DETECT_CARD(1) - 识别卡失败</li> | |||
| * <li>ERROR_READ_SN(2) - 读卡序列号失败</li> | |||
| * <li>ERROR_READ_TRACK(3) - 读卡信息失败</li> | |||
| * <li>ERROR_SERVICE_CRASH(4) - 设备服务异常</li> | |||
| * <li>ERROR_NULL_DRIVER(5) - 非接驱动为null</li> | |||
| * </ul> | |||
| * @param message - 错误描述 | |||
| */ | |||
| void onError(int error, String message); | |||
| } | |||
| @@ -0,0 +1,15 @@ | |||
| package com.icbc.smartpos.transservice.aidl; | |||
| import com.icbc.smartpos.transservice.aidl.TransHandler; | |||
| /** | |||
| * 交易调用接口 | |||
| * @author: icbc | |||
| */ | |||
| interface ITransService { | |||
| long startTrans(String transType, | |||
| in Bundle ctrlData, | |||
| in Bundle transData, | |||
| in TransHandler handler); | |||
| } | |||
| @@ -0,0 +1,10 @@ | |||
| package com.icbc.smartpos.transservice.aidl; | |||
| /** | |||
| * 交易流程回调接口 | |||
| * @author: icbc | |||
| */ | |||
| interface TransHandler { | |||
| void onFinish(in Bundle baseResult, in Bundle transResult, in Bundle extraInfo); | |||
| } | |||
| @@ -0,0 +1,115 @@ | |||
| package com.unionpay.CloudPosPaymentClient.aidl; | |||
| interface ICloudPay{ | |||
| //-----------收单---------- | |||
| //消费 | |||
| String payCash(String jsonData); | |||
| //交易查询 | |||
| String getPayInfo(String jsonData); | |||
| //获取终端信息 | |||
| String getPOSInfo(String jsonData); | |||
| //签到 | |||
| String signIn(String jsonData); | |||
| //批结算 | |||
| String settlement(String jsonData); | |||
| //余额查询 | |||
| String balanceQuery(String jsonData); | |||
| //消费撤销 | |||
| String consumeCancel(String jsonData); | |||
| //账户验证 | |||
| String accountVerify(String jsonData); | |||
| //交易统计 | |||
| String dealStatics(String jsonData); | |||
| //交易明细查询 | |||
| String detailQuery(String jsonData); | |||
| //退货 | |||
| String returnGoods(String jsonData); | |||
| //-----------多渠道---------- | |||
| //建立委托 | |||
| String createCommission(String jsonData); | |||
| //撤销委托 | |||
| String undoCommission(String jsonData); | |||
| //单笔代收 | |||
| String singleCollection(String jsonData); | |||
| //实时代付 | |||
| String payForAnother(String jsonData); | |||
| //代付确认 | |||
| String payConfirm(String jsonData); | |||
| //单笔限额和单日限额查询 | |||
| String limitQuery(); | |||
| //单笔限额和单日限额设置 | |||
| String limitSet(String jsonData); | |||
| //剩余限额查询 | |||
| String limitLeft(); | |||
| //结算统计 | |||
| String dealStatistics(String jsonData); | |||
| // 信用卡还款 | |||
| String creditCardPayment(String jsonData); | |||
| // 转帐 | |||
| String transfer(String jsonData); | |||
| // 手机充值 | |||
| String phoneRecharge(String jsonData); | |||
| //单笔撤消 | |||
| String cancelSingleCollection(String jsonData); | |||
| //水电煤缴费 | |||
| String billPayment(String jsonData); | |||
| //账单查询 | |||
| String billQuery(String jsonData); | |||
| //终端激活 | |||
| String getAppTerms(); | |||
| //-----------UPOP---------- | |||
| // 代收 | |||
| String takeCollection(String data); | |||
| // 代付 | |||
| String takePayment(String data); | |||
| // 交易撤销 | |||
| String cancelTransaction(String data); | |||
| // 交易查询 | |||
| String queryTransaction(String data); | |||
| // 文件传输类交易 | |||
| String transactByFile(String data); | |||
| // 代收控制交易 | |||
| String cancelControl(String data); | |||
| //--------贷款类--------- | |||
| // 贷款查询 | |||
| String loanQuery(String jsonData); | |||
| // 贷款试算 | |||
| String loanCal(String jsonData); | |||
| // 贷款还款 | |||
| String loanPayment(String jsonData); | |||
| // 社保缴费 | |||
| String tipsLevyAgent(String jsonData); | |||
| //助农取款 | |||
| String withDrawal(String jsonData); | |||
| //--------贷款类二期-------- | |||
| // 帐单费用查询 | |||
| String billQueryServlet(String jsonData); | |||
| // 缴费有密 | |||
| String billPaymentServletWithPin(String jsonData); | |||
| // 缴费无密 | |||
| String billPaymentServletWithoutPin(String jsonData); | |||
| //--------银联钱包2.0-------- | |||
| // 增值服务消费(先查后消费) | |||
| String walletSec(String jsonData); | |||
| // 增值服务撤销 | |||
| String walletSecRevoke(String jsonData); | |||
| // 增值服务退货 | |||
| String walletSecReturn(String jsonData); | |||
| //单笔限额和单日限额查询,支持应用商户号 | |||
| String limitQueryWithAppInfo(String jsonData); | |||
| //剩余限额查询 | |||
| String limitLeftWithAppInfo(String jsonData); | |||
| //扫码付接口 | |||
| String scanPay(String jsonData); | |||
| } | |||
| @@ -0,0 +1,7 @@ | |||
| package com.unionpay.facepay; | |||
| import com.unionpay.facepay.IFacePayListener; | |||
| interface IFacePayInterface { | |||
| boolean facePay(String payInfo, IFacePayListener listener); | |||
| } | |||