+
+
diff --git a/V2rayNG/.gitignore b/V2rayNG/.gitignore
new file mode 100644
index 000000000..4d128e872
--- /dev/null
+++ b/V2rayNG/.gitignore
@@ -0,0 +1,9 @@
+*.iml
+.gradle
+/local.properties
+/.idea
+.DS_Store
+/build
+/captures
+*.apk
+signing.properties
diff --git a/V2rayNG/app/.gitignore b/V2rayNG/app/.gitignore
new file mode 100644
index 000000000..2abde4aab
--- /dev/null
+++ b/V2rayNG/app/.gitignore
@@ -0,0 +1,2 @@
+/build
+/google-services.json
diff --git a/V2rayNG/app/build.gradle b/V2rayNG/app/build.gradle
new file mode 100644
index 000000000..e025b84a8
--- /dev/null
+++ b/V2rayNG/app/build.gradle
@@ -0,0 +1,129 @@
+apply plugin: 'com.android.application'
+apply plugin: 'kotlin-android'
+apply plugin: 'kotlin-android-extensions'
+
+android {
+ compileSdkVersion 28
+ buildToolsVersion '28.0.3'
+
+ compileOptions {
+ targetCompatibility = "8"
+ sourceCompatibility = "8"
+ }
+
+ defaultConfig {
+ applicationId "com.v2ray.ang"
+ minSdkVersion 17
+ targetSdkVersion Integer.parseInt("$targetSdkVer")
+ multiDexEnabled true
+ versionCode 212
+ versionName "1.0.2"
+ }
+
+ signingConfigs {
+ release {
+ storeFile file("../key.jks")
+ keyAlias 'ang'
+ keyPassword '123456'
+ storePassword '123456'
+ }
+ debug {
+ storeFile file("../key.jks")
+ keyAlias 'ang'
+ keyPassword '123456'
+ storePassword '123456'
+ }
+ }
+
+ buildTypes {
+ release {
+ minifyEnabled false
+ zipAlignEnabled false
+ shrinkResources false
+ signingConfig signingConfigs.release
+// proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+ }
+ debug {
+ minifyEnabled false
+ zipAlignEnabled false
+ shrinkResources false
+ signingConfig signingConfigs.release
+ }
+ }
+
+ sourceSets {
+ main.java.srcDirs += 'src/main/kotlin'
+ }
+
+ splits {
+ abi {
+ enable true
+ reset()
+ include 'x86', 'x86_64', 'armeabi-v7a', 'arm64-v8a' //select ABIs to build APKs for
+ universalApk true //generate an additional APK that contains all the ABIs
+ }
+ }
+
+ // map for the version code
+ project.ext.versionCodes = ['armeabi-v7a': 1, 'arm64-v8a': 2, 'x86': 3, 'x86_64': 4]
+
+ android.applicationVariants.all { variant ->
+ // assign different version code for each output
+ variant.outputs.each { output ->
+ output.versionCodeOverride =
+ project.ext.versionCodes.get(output.getFilter(com.android.build.OutputFile.ABI), 0) *
+ 1000000 + android.defaultConfig.versionCode
+ }
+ }
+}
+
+dependencies {
+ implementation fileTree(include: ['*.jar'], dir: 'libs')
+ implementation 'com.android.support.constraint:constraint-layout:1.1.3'
+ testImplementation 'junit:junit:4.12'
+ implementation project(':dpreference')
+ implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion"
+ implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlinVersion"
+ // Android support library
+ implementation "com.android.support:support-v4:$supportLibVersion"
+ implementation "com.android.support:appcompat-v7:$supportLibVersion"
+ implementation "com.android.support:design:$supportLibVersion"
+ implementation "com.android.support:cardview-v7:$supportLibVersion"
+ implementation "com.android.support:preference-v7:$supportLibVersion"
+ implementation "com.android.support:recyclerview-v7:$supportLibVersion"
+ // DSL
+ implementation "org.jetbrains.anko:anko-sdk15:$ankoVersion"
+ implementation "org.jetbrains.anko:anko-support-v4:$ankoVersion"
+ implementation "org.jetbrains.anko:anko-appcompat-v7:$ankoVersion"
+ implementation "org.jetbrains.anko:anko-design:$ankoVersion"
+ implementation 'com.google.code.gson:gson:2.8.5'
+ implementation 'io.reactivex:rxjava:1.3.4'
+ implementation 'io.reactivex:rxandroid:1.2.1'
+ implementation 'com.tbruyelle.rxpermissions:rxpermissions:0.9.4@aar'
+ implementation 'com.dinuscxj:recycleritemdecoration:1.0.0'
+ implementation 'io.reactivex:rxkotlin:0.60.0'
+ implementation 'me.dm7.barcodescanner:core:1.9.8'
+ implementation 'me.dm7.barcodescanner:zxing:1.9.8'
+ implementation 'com.github.jorgecastilloprz:fabprogresscircle:1.01@aar'
+ implementation 'com.beust:klaxon:3.0.1'
+ implementation 'com.android.support:multidex:1.0.3'
+
+ implementation(name: 'libv2ray', ext: 'aar')
+ //implementation(name: 'tun2socks', ext: 'aar')
+}
+
+buildscript {
+ repositories {
+ google()
+ jcenter()
+ maven { url 'https://maven.google.com' }
+ }
+ dependencies {
+ classpath "org.jetbrains.kotlin:kotlin-android-extensions:$kotlinVersion"
+ }
+}
+repositories {
+ flatDir {
+ dirs 'libs'
+ }
+}
\ No newline at end of file
diff --git a/V2rayNG/app/libs/Readme.md b/V2rayNG/app/libs/Readme.md
new file mode 100644
index 000000000..0f2efdb3f
--- /dev/null
+++ b/V2rayNG/app/libs/Readme.md
@@ -0,0 +1 @@
+https://github.com/2dust/v2rayNG/tree/master/AndroidLibV2rayLite
diff --git a/V2rayNG/app/libs/libv2ray.aar b/V2rayNG/app/libs/libv2ray.aar
new file mode 100644
index 000000000..9b0f93595
Binary files /dev/null and b/V2rayNG/app/libs/libv2ray.aar differ
diff --git a/V2rayNG/app/proguard-rules.pro b/V2rayNG/app/proguard-rules.pro
new file mode 100644
index 000000000..e56daed37
--- /dev/null
+++ b/V2rayNG/app/proguard-rules.pro
@@ -0,0 +1,61 @@
+# Add project specific ProGuard rules here.
+# By default, the flags in this file are appended to flags specified
+# in G:\android-sdk/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 *;
+#}
+
+# Gson uses generic type information stored in a class file when working with fields. Proguard
+# removes such information by default, so configure it to keep all of it.
+-keepattributes Signature
+
+# For using GSON @Expose annotation
+-keepattributes *Annotation*
+
+# Gson specific classes
+-keep class sun.misc.Unsafe { *; }
+
+-dontwarn org.apache.commons.**
+-keep class org.apache.commons.** { *;}
+
+# Disable debug info output
+-assumenosideeffects class android.util.Log {
+ public static boolean isLoggable(java.lang.String,int);
+ public static int v(...);
+ public static int i(...);
+ public static int w(...);
+ public static int d(...);
+ public static int e(...);
+}
+-assumenosideeffects class kotlin.jvm.internal.Intrinsics {
+ static void checkParameterIsNotNull(java.lang.Object, java.lang.String);
+ static void checkExpressionValueIsNotNull(java.lang.Object, java.lang.String);
+ static void throwUninitializedPropertyAccessException(java.lang.String);
+}
+
+-dontwarn org.jetbrains.anko.internals.**
+-keep class org.jetbrains.anko.internals.** { *;}
+
+-dontwarn rx.internal.util.unsafe.**
+-keep class rx.internal.util.unsafe.** { *;}
+
+-dontwarn app.dinus.**
+-keep class app.dinus.** { *;}
+
+-keepclassmembers class ** {
+ @com.hwangjr.rxbus.annotation.Subscribe public *;
+ @com.hwangjr.rxbus.annotation.Produce public *;
+}
+
+-keep class libv2ray.** { *;}
diff --git a/V2rayNG/app/src/androidTest/java/com/v2ray/ang/ApplicationTest.java b/V2rayNG/app/src/androidTest/java/com/v2ray/ang/ApplicationTest.java
new file mode 100644
index 000000000..e221e7142
--- /dev/null
+++ b/V2rayNG/app/src/androidTest/java/com/v2ray/ang/ApplicationTest.java
@@ -0,0 +1,13 @@
+package com.v2ray.ang;
+
+import android.app.Application;
+import android.test.ApplicationTestCase;
+
+/**
+ * Testing Fundamentals
+ */
+public class ApplicationTest extends ApplicationTestCaseRecyclerView.Adapter to listen for {@link
+ * ItemTouchHelperAdapter} callbacks and the RecyclerView.ViewHolder to implement
+ * {@link ItemTouchHelperViewHolder}.
+ *
+ * @author Paul Burke (ipaulpro)
+ */
+public class SimpleItemTouchHelperCallback extends ItemTouchHelper.Callback {
+
+ public static final float ALPHA_FULL = 1.0f;
+
+ private final ItemTouchHelperAdapter mAdapter;
+
+ public SimpleItemTouchHelperCallback(ItemTouchHelperAdapter adapter) {
+ mAdapter = adapter;
+ }
+
+ @Override
+ public boolean isLongPressDragEnabled() {
+ return true;
+ }
+
+ @Override
+ public boolean isItemViewSwipeEnabled() {
+ return false;
+ }
+
+ @Override
+ public int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
+ // Set movement flags based on the layout manager
+ if (recyclerView.getLayoutManager() instanceof GridLayoutManager) {
+ final int dragFlags = ItemTouchHelper.UP | ItemTouchHelper.DOWN | ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT;
+ final int swipeFlags = 0;
+ return makeMovementFlags(dragFlags, swipeFlags);
+ } else {
+ final int dragFlags = ItemTouchHelper.UP | ItemTouchHelper.DOWN;
+ final int swipeFlags = ItemTouchHelper.START | ItemTouchHelper.END;
+ return makeMovementFlags(dragFlags, swipeFlags);
+ }
+ }
+
+ @Override
+ public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder source, RecyclerView.ViewHolder target) {
+ if (source.getItemViewType() != target.getItemViewType()) {
+ return false;
+ }
+
+ // Notify the adapter of the move
+ mAdapter.onItemMove(source.getAdapterPosition(), target.getAdapterPosition());
+ return true;
+ }
+
+ @Override
+ public void onSwiped(RecyclerView.ViewHolder viewHolder, int i) {
+ // Notify the adapter of the dismissal
+ mAdapter.onItemDismiss(viewHolder.getAdapterPosition());
+ }
+
+ @Override
+ public void onChildDraw(Canvas c, RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, float dX, float dY, int actionState, boolean isCurrentlyActive) {
+ if (actionState == ItemTouchHelper.ACTION_STATE_SWIPE) {
+ // Fade out the view as it is swiped out of the parent's bounds
+ final float alpha = ALPHA_FULL - Math.abs(dX) / (float) viewHolder.itemView.getWidth();
+ viewHolder.itemView.setAlpha(alpha);
+ viewHolder.itemView.setTranslationX(dX);
+ } else {
+ super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive);
+ }
+ }
+
+ @Override
+ public void onSelectedChanged(RecyclerView.ViewHolder viewHolder, int actionState) {
+ // We only want the active item to change
+ if (actionState != ItemTouchHelper.ACTION_STATE_IDLE) {
+ if (viewHolder instanceof ItemTouchHelperViewHolder) {
+ // Let the view holder know that this item is being moved or dragged
+ ItemTouchHelperViewHolder itemViewHolder = (ItemTouchHelperViewHolder) viewHolder;
+ itemViewHolder.onItemSelected();
+ }
+ }
+
+ super.onSelectedChanged(viewHolder, actionState);
+ }
+
+ @Override
+ public void clearView(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
+ super.clearView(recyclerView, viewHolder);
+
+ viewHolder.itemView.setAlpha(ALPHA_FULL);
+
+ if (viewHolder instanceof ItemTouchHelperViewHolder) {
+ // Tell the view holder it's time to restore the idle state
+ ItemTouchHelperViewHolder itemViewHolder = (ItemTouchHelperViewHolder) viewHolder;
+ itemViewHolder.onItemClear();
+ }
+ }
+}
diff --git a/V2rayNG/app/src/main/java/com/v2ray/ang/util/AssetsUtil.java b/V2rayNG/app/src/main/java/com/v2ray/ang/util/AssetsUtil.java
new file mode 100644
index 000000000..38f17d9c4
--- /dev/null
+++ b/V2rayNG/app/src/main/java/com/v2ray/ang/util/AssetsUtil.java
@@ -0,0 +1,121 @@
+package com.v2ray.ang.util;
+
+import static android.content.Context.MODE_PRIVATE;
+
+import android.content.Context;
+import android.content.res.AssetManager;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+
+public class AssetsUtil {
+ public static boolean copyAssetFolder(AssetManager assetManager,
+ String fromAssetPath, String toPath) {
+ try {
+ String[] files = assetManager.list(fromAssetPath);
+ new File(toPath).mkdirs();
+ boolean res = true;
+ for (String file : files)
+ if (file.contains("."))
+ res &= copyAsset(assetManager,
+ fromAssetPath + "/" + file,
+ toPath + "/" + file);
+ else
+ res &= copyAssetFolder(assetManager,
+ fromAssetPath + "/" + file,
+ toPath + "/" + file);
+ return res;
+ } catch (Exception e) {
+ e.printStackTrace();
+ return false;
+ }
+ }
+
+ public static boolean copyAsset(AssetManager assetManager,
+ String fromAssetPath, String toPath) {
+ InputStream in = null;
+ OutputStream out = null;
+ try {
+ in = assetManager.open(fromAssetPath);
+ new File(toPath).createNewFile();
+ out = new FileOutputStream(toPath);
+ copyFile(in, out);
+ in.close();
+ return true;
+ } catch (Exception e) {
+ e.printStackTrace();
+ return false;
+ } finally {
+ try {
+ if (out != null) {
+ out.close();
+ }
+ if (in != null) {
+ in.close();
+ }
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+
+ public static String readTextFromAssets(AssetManager assetManager, String fileName) {
+ try {
+ InputStreamReader inputReader = new InputStreamReader(assetManager.open(fileName));
+ BufferedReader bufReader = new BufferedReader(inputReader);
+ String line;
+ String Result = "";
+ while ((line = bufReader.readLine()) != null)
+ Result += line;
+ return Result;
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+
+ return null;
+ }
+
+ public static String getAssetPath(Context context, String assetPath) {
+ InputStream in = null;
+ OutputStream out = null;
+ try {
+ context.deleteFile(assetPath);
+
+ in = context.getAssets().open(assetPath);
+ out = context.openFileOutput(assetPath, MODE_PRIVATE);
+ copyFile(in, out);
+ in.close();
+
+ String path = context.getFilesDir().toString();
+ return path + "/" + assetPath;
+
+ } catch (Exception e) {
+ e.printStackTrace();
+ return "";
+ } finally {
+ try {
+ if (out != null) {
+ out.close();
+ }
+ if (in != null) {
+ in.close();
+ }
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+
+ private static void copyFile(InputStream in, OutputStream out) throws IOException {
+ byte[] buffer = new byte[1024];
+ int read;
+ while ((read = in.read(buffer)) != -1) {
+ out.write(buffer, 0, read);
+ }
+ }
+}
\ No newline at end of file
diff --git a/V2rayNG/app/src/main/java/com/v2ray/ang/util/Base64.java b/V2rayNG/app/src/main/java/com/v2ray/ang/util/Base64.java
new file mode 100644
index 000000000..eb69a0626
--- /dev/null
+++ b/V2rayNG/app/src/main/java/com/v2ray/ang/util/Base64.java
@@ -0,0 +1,570 @@
+// Portions copyright 2002, Google, Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.v2ray.ang.util;
+
+// This code was converted from code at http://iharder.sourceforge.net/base64/
+// Lots of extraneous features were removed.
+/* The original code said:
+ * + * I am placing this code in the Public Domain. Do with it as you will. + * This software comes with no guarantees or warranties but with + * plenty of well-wishing instead! + * Please visit + * http://iharder.net/xmlizable + * periodically to check for updates or to contribute improvements. + *
+ * + * @author Robert Harder + * @author rharder@usa.net + * @version 1.3 + */ + +/** + * Base64 converter class. This code is not a complete MIME encoder; + * it simply converts binary data to base64 data and back. + * + *Note {@link CharBase64} is a GWT-compatible implementation of this + * class. + */ +public class Base64 { + /** Specify encoding (value is {@code true}). */ + public final static boolean ENCODE = true; + + /** Specify decoding (value is {@code false}). */ + public final static boolean DECODE = false; + + /** The equals sign (=) as a byte. */ + private final static byte EQUALS_SIGN = (byte) '='; + + /** The new line character (\n) as a byte. */ + private final static byte NEW_LINE = (byte) '\n'; + + /** + * The 64 valid Base64 values. + */ + private final static byte[] ALPHABET = + {(byte) 'A', (byte) 'B', (byte) 'C', (byte) 'D', (byte) 'E', (byte) 'F', + (byte) 'G', (byte) 'H', (byte) 'I', (byte) 'J', (byte) 'K', + (byte) 'L', (byte) 'M', (byte) 'N', (byte) 'O', (byte) 'P', + (byte) 'Q', (byte) 'R', (byte) 'S', (byte) 'T', (byte) 'U', + (byte) 'V', (byte) 'W', (byte) 'X', (byte) 'Y', (byte) 'Z', + (byte) 'a', (byte) 'b', (byte) 'c', (byte) 'd', (byte) 'e', + (byte) 'f', (byte) 'g', (byte) 'h', (byte) 'i', (byte) 'j', + (byte) 'k', (byte) 'l', (byte) 'm', (byte) 'n', (byte) 'o', + (byte) 'p', (byte) 'q', (byte) 'r', (byte) 's', (byte) 't', + (byte) 'u', (byte) 'v', (byte) 'w', (byte) 'x', (byte) 'y', + (byte) 'z', (byte) '0', (byte) '1', (byte) '2', (byte) '3', + (byte) '4', (byte) '5', (byte) '6', (byte) '7', (byte) '8', + (byte) '9', (byte) '+', (byte) '/'}; + + /** + * The 64 valid web safe Base64 values. + */ + private final static byte[] WEBSAFE_ALPHABET = + {(byte) 'A', (byte) 'B', (byte) 'C', (byte) 'D', (byte) 'E', (byte) 'F', + (byte) 'G', (byte) 'H', (byte) 'I', (byte) 'J', (byte) 'K', + (byte) 'L', (byte) 'M', (byte) 'N', (byte) 'O', (byte) 'P', + (byte) 'Q', (byte) 'R', (byte) 'S', (byte) 'T', (byte) 'U', + (byte) 'V', (byte) 'W', (byte) 'X', (byte) 'Y', (byte) 'Z', + (byte) 'a', (byte) 'b', (byte) 'c', (byte) 'd', (byte) 'e', + (byte) 'f', (byte) 'g', (byte) 'h', (byte) 'i', (byte) 'j', + (byte) 'k', (byte) 'l', (byte) 'm', (byte) 'n', (byte) 'o', + (byte) 'p', (byte) 'q', (byte) 'r', (byte) 's', (byte) 't', + (byte) 'u', (byte) 'v', (byte) 'w', (byte) 'x', (byte) 'y', + (byte) 'z', (byte) '0', (byte) '1', (byte) '2', (byte) '3', + (byte) '4', (byte) '5', (byte) '6', (byte) '7', (byte) '8', + (byte) '9', (byte) '-', (byte) '_'}; + + /** + * Translates a Base64 value to either its 6-bit reconstruction value + * or a negative number indicating some other meaning. + **/ + private final static byte[] DECODABET = {-9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 0 - 8 + -5, -5, // Whitespace: Tab and Linefeed + -9, -9, // Decimal 11 - 12 + -5, // Whitespace: Carriage Return + -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 14 - 26 + -9, -9, -9, -9, -9, // Decimal 27 - 31 + -5, // Whitespace: Space + -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 33 - 42 + 62, // Plus sign at decimal 43 + -9, -9, -9, // Decimal 44 - 46 + 63, // Slash at decimal 47 + 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, // Numbers zero through nine + -9, -9, -9, // Decimal 58 - 60 + -1, // Equals sign at decimal 61 + -9, -9, -9, // Decimal 62 - 64 + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, // Letters 'A' through 'N' + 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, // Letters 'O' through 'Z' + -9, -9, -9, -9, -9, -9, // Decimal 91 - 96 + 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, // Letters 'a' through 'm' + 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, // Letters 'n' through 'z' + -9, -9, -9, -9, -9 // Decimal 123 - 127 + /* ,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 128 - 139 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 140 - 152 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 153 - 165 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 166 - 178 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 179 - 191 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 192 - 204 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 205 - 217 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 218 - 230 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 231 - 243 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9 // Decimal 244 - 255 */ + }; + + /** The web safe decodabet */ + private final static byte[] WEBSAFE_DECODABET = + {-9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 0 - 8 + -5, -5, // Whitespace: Tab and Linefeed + -9, -9, // Decimal 11 - 12 + -5, // Whitespace: Carriage Return + -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 14 - 26 + -9, -9, -9, -9, -9, // Decimal 27 - 31 + -5, // Whitespace: Space + -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 33 - 44 + 62, // Dash '-' sign at decimal 45 + -9, -9, // Decimal 46-47 + 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, // Numbers zero through nine + -9, -9, -9, // Decimal 58 - 60 + -1, // Equals sign at decimal 61 + -9, -9, -9, // Decimal 62 - 64 + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, // Letters 'A' through 'N' + 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, // Letters 'O' through 'Z' + -9, -9, -9, -9, // Decimal 91-94 + 63, // Underscore '_' at decimal 95 + -9, // Decimal 96 + 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, // Letters 'a' through 'm' + 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, // Letters 'n' through 'z' + -9, -9, -9, -9, -9 // Decimal 123 - 127 + /* ,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 128 - 139 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 140 - 152 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 153 - 165 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 166 - 178 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 179 - 191 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 192 - 204 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 205 - 217 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 218 - 230 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 231 - 243 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9 // Decimal 244 - 255 */ + }; + + // Indicates white space in encoding + private final static byte WHITE_SPACE_ENC = -5; + // Indicates equals sign in encoding + private final static byte EQUALS_SIGN_ENC = -1; + + /** Defeats instantiation. */ + private Base64() { + } + + /* ******** E N C O D I N G M E T H O D S ******** */ + + /** + * Encodes up to three bytes of the array source + * and writes the resulting four Base64 bytes to destination. + * The source and destination arrays can be manipulated + * anywhere along their length by specifying + * srcOffset and destOffset. + * This method does not check to make sure your arrays + * are large enough to accommodate srcOffset + 3 for + * the source array or destOffset + 4 for + * the destination array. + * The actual number of significant bytes in your array is + * given by numSigBytes. + * + * @param source the array to convert + * @param srcOffset the index where conversion begins + * @param numSigBytes the number of significant bytes in your array + * @param destination the array to hold the conversion + * @param destOffset the index where output will be put + * @param alphabet is the encoding alphabet + * @return the destination array + * @since 1.3 + */ + private static byte[] encode3to4(byte[] source, int srcOffset, + int numSigBytes, byte[] destination, int destOffset, byte[] alphabet) { + // 1 2 3 + // 01234567890123456789012345678901 Bit position + // --------000000001111111122222222 Array position from threeBytes + // --------| || || || | Six bit groups to index alphabet + // >>18 >>12 >> 6 >> 0 Right shift necessary + // 0x3f 0x3f 0x3f Additional AND + + // Create buffer with zero-padding if there are only one or two + // significant bytes passed in the array. + // We have to shift left 24 in order to flush out the 1's that appear + // when Java treats a value as negative that is cast from a byte to an int. + int inBuff = + (numSigBytes > 0 ? ((source[srcOffset] << 24) >>> 8) : 0) + | (numSigBytes > 1 ? ((source[srcOffset + 1] << 24) >>> 16) : 0) + | (numSigBytes > 2 ? ((source[srcOffset + 2] << 24) >>> 24) : 0); + + switch (numSigBytes) { + case 3: + destination[destOffset] = alphabet[(inBuff >>> 18)]; + destination[destOffset + 1] = alphabet[(inBuff >>> 12) & 0x3f]; + destination[destOffset + 2] = alphabet[(inBuff >>> 6) & 0x3f]; + destination[destOffset + 3] = alphabet[(inBuff) & 0x3f]; + return destination; + case 2: + destination[destOffset] = alphabet[(inBuff >>> 18)]; + destination[destOffset + 1] = alphabet[(inBuff >>> 12) & 0x3f]; + destination[destOffset + 2] = alphabet[(inBuff >>> 6) & 0x3f]; + destination[destOffset + 3] = EQUALS_SIGN; + return destination; + case 1: + destination[destOffset] = alphabet[(inBuff >>> 18)]; + destination[destOffset + 1] = alphabet[(inBuff >>> 12) & 0x3f]; + destination[destOffset + 2] = EQUALS_SIGN; + destination[destOffset + 3] = EQUALS_SIGN; + return destination; + default: + return destination; + } // end switch + } // end encode3to4 + + /** + * Encodes a byte array into Base64 notation. + * Equivalent to calling + * {@code encodeBytes(source, 0, source.length)} + * + * @param source The data to convert + * @since 1.4 + */ + public static String encode(byte[] source) { + return encode(source, 0, source.length, ALPHABET, true); + } + + /** + * Encodes a byte array into web safe Base64 notation. + * + * @param source The data to convert + * @param doPadding is {@code true} to pad result with '=' chars + * if it does not fall on 3 byte boundaries + */ + public static String encodeWebSafe(byte[] source, boolean doPadding) { + return encode(source, 0, source.length, WEBSAFE_ALPHABET, doPadding); + } + + /** + * Encodes a byte array into Base64 notation. + * + * @param source the data to convert + * @param off offset in array where conversion should begin + * @param len length of data to convert + * @param alphabet the encoding alphabet + * @param doPadding is {@code true} to pad result with '=' chars + * if it does not fall on 3 byte boundaries + * @since 1.4 + */ + public static String encode(byte[] source, int off, int len, byte[] alphabet, + boolean doPadding) { + byte[] outBuff = encode(source, off, len, alphabet, Integer.MAX_VALUE); + int outLen = outBuff.length; + + // If doPadding is false, set length to truncate '=' + // padding characters + while (doPadding == false && outLen > 0) { + if (outBuff[outLen - 1] != '=') { + break; + } + outLen -= 1; + } + + return new String(outBuff, 0, outLen); + } + + /** + * Encodes a byte array into Base64 notation. + * + * @param source the data to convert + * @param off offset in array where conversion should begin + * @param len length of data to convert + * @param alphabet is the encoding alphabet + * @param maxLineLength maximum length of one line. + * @return the BASE64-encoded byte array + */ + public static byte[] encode(byte[] source, int off, int len, byte[] alphabet, + int maxLineLength) { + int lenDiv3 = (len + 2) / 3; // ceil(len / 3) + int len43 = lenDiv3 * 4; + byte[] outBuff = new byte[len43 // Main 4:3 + + (len43 / maxLineLength)]; // New lines + + int d = 0; + int e = 0; + int len2 = len - 2; + int lineLength = 0; + for (; d < len2; d += 3, e += 4) { + + // The following block of code is the same as + // encode3to4( source, d + off, 3, outBuff, e, alphabet ); + // but inlined for faster encoding (~20% improvement) + int inBuff = + ((source[d + off] << 24) >>> 8) + | ((source[d + 1 + off] << 24) >>> 16) + | ((source[d + 2 + off] << 24) >>> 24); + outBuff[e] = alphabet[(inBuff >>> 18)]; + outBuff[e + 1] = alphabet[(inBuff >>> 12) & 0x3f]; + outBuff[e + 2] = alphabet[(inBuff >>> 6) & 0x3f]; + outBuff[e + 3] = alphabet[(inBuff) & 0x3f]; + + lineLength += 4; + if (lineLength == maxLineLength) { + outBuff[e + 4] = NEW_LINE; + e++; + lineLength = 0; + } // end if: end of line + } // end for: each piece of array + + if (d < len) { + encode3to4(source, d + off, len - d, outBuff, e, alphabet); + + lineLength += 4; + if (lineLength == maxLineLength) { + // Add a last newline + outBuff[e + 4] = NEW_LINE; + e++; + } + e += 4; + } + + assert (e == outBuff.length); + return outBuff; + } + + + /* ******** D E C O D I N G M E T H O D S ******** */ + + + /** + * Decodes four bytes from array source + * and writes the resulting bytes (up to three of them) + * to destination. + * The source and destination arrays can be manipulated + * anywhere along their length by specifying + * srcOffset and destOffset. + * This method does not check to make sure your arrays + * are large enough to accommodate srcOffset + 4 for + * the source array or destOffset + 3 for + * the destination array. + * This method returns the actual number of bytes that + * were converted from the Base64 encoding. + * + * + * @param source the array to convert + * @param srcOffset the index where conversion begins + * @param destination the array to hold the conversion + * @param destOffset the index where output will be put + * @param decodabet the decodabet for decoding Base64 content + * @return the number of decoded bytes converted + * @since 1.3 + */ + private static int decode4to3(byte[] source, int srcOffset, + byte[] destination, int destOffset, byte[] decodabet) { + // Example: Dk== + if (source[srcOffset + 2] == EQUALS_SIGN) { + int outBuff = + ((decodabet[source[srcOffset]] << 24) >>> 6) + | ((decodabet[source[srcOffset + 1]] << 24) >>> 12); + + destination[destOffset] = (byte) (outBuff >>> 16); + return 1; + } else if (source[srcOffset + 3] == EQUALS_SIGN) { + // Example: DkL= + int outBuff = + ((decodabet[source[srcOffset]] << 24) >>> 6) + | ((decodabet[source[srcOffset + 1]] << 24) >>> 12) + | ((decodabet[source[srcOffset + 2]] << 24) >>> 18); + + destination[destOffset] = (byte) (outBuff >>> 16); + destination[destOffset + 1] = (byte) (outBuff >>> 8); + return 2; + } else { + // Example: DkLE + int outBuff = + ((decodabet[source[srcOffset]] << 24) >>> 6) + | ((decodabet[source[srcOffset + 1]] << 24) >>> 12) + | ((decodabet[source[srcOffset + 2]] << 24) >>> 18) + | ((decodabet[source[srcOffset + 3]] << 24) >>> 24); + + destination[destOffset] = (byte) (outBuff >> 16); + destination[destOffset + 1] = (byte) (outBuff >> 8); + destination[destOffset + 2] = (byte) (outBuff); + return 3; + } + } // end decodeToBytes + + + /** + * Decodes data from Base64 notation. + * + * @param s the string to decode (decoded in default encoding) + * @return the decoded data + * @since 1.4 + */ + public static byte[] decode(String s) throws Base64DecoderException { + byte[] bytes = s.getBytes(); + return decode(bytes, 0, bytes.length); + } + + /** + * Decodes data from web safe Base64 notation. + * Web safe encoding uses '-' instead of '+', '_' instead of '/' + * + * @param s the string to decode (decoded in default encoding) + * @return the decoded data + */ + public static byte[] decodeWebSafe(String s) throws Base64DecoderException { + byte[] bytes = s.getBytes(); + return decodeWebSafe(bytes, 0, bytes.length); + } + + /** + * Decodes Base64 content in byte array format and returns + * the decoded byte array. + * + * @param source The Base64 encoded data + * @return decoded data + * @since 1.3 + * @throws Base64DecoderException + */ + public static byte[] decode(byte[] source) throws Base64DecoderException { + return decode(source, 0, source.length); + } + + /** + * Decodes web safe Base64 content in byte array format and returns + * the decoded data. + * Web safe encoding uses '-' instead of '+', '_' instead of '/' + * + * @param source the string to decode (decoded in default encoding) + * @return the decoded data + */ + public static byte[] decodeWebSafe(byte[] source) + throws Base64DecoderException { + return decodeWebSafe(source, 0, source.length); + } + + /** + * Decodes Base64 content in byte array format and returns + * the decoded byte array. + * + * @param source the Base64 encoded data + * @param off the offset of where to begin decoding + * @param len the length of characters to decode + * @return decoded data + * @since 1.3 + * @throws Base64DecoderException + */ + public static byte[] decode(byte[] source, int off, int len) + throws Base64DecoderException { + return decode(source, off, len, DECODABET); + } + + /** + * Decodes web safe Base64 content in byte array format and returns + * the decoded byte array. + * Web safe encoding uses '-' instead of '+', '_' instead of '/' + * + * @param source the Base64 encoded data + * @param off the offset of where to begin decoding + * @param len the length of characters to decode + * @return decoded data + */ + public static byte[] decodeWebSafe(byte[] source, int off, int len) + throws Base64DecoderException { + return decode(source, off, len, WEBSAFE_DECODABET); + } + + /** + * Decodes Base64 content using the supplied decodabet and returns + * the decoded byte array. + * + * @param source the Base64 encoded data + * @param off the offset of where to begin decoding + * @param len the length of characters to decode + * @param decodabet the decodabet for decoding Base64 content + * @return decoded data + */ + public static byte[] decode(byte[] source, int off, int len, byte[] decodabet) + throws Base64DecoderException { + int len34 = len * 3 / 4; + byte[] outBuff = new byte[2 + len34]; // Upper limit on size of output + int outBuffPosn = 0; + + byte[] b4 = new byte[4]; + int b4Posn = 0; + int i = 0; + byte sbiCrop = 0; + byte sbiDecode = 0; + for (i = 0; i < len; i++) { + sbiCrop = (byte) (source[i + off] & 0x7f); // Only the low seven bits + sbiDecode = decodabet[sbiCrop]; + + if (sbiDecode >= WHITE_SPACE_ENC) { // White space Equals sign or better + if (sbiDecode >= EQUALS_SIGN_ENC) { + // An equals sign (for padding) must not occur at position 0 or 1 + // and must be the last byte[s] in the encoded value + if (sbiCrop == EQUALS_SIGN) { + int bytesLeft = len - i; + byte lastByte = (byte) (source[len - 1 + off] & 0x7f); + if (b4Posn == 0 || b4Posn == 1) { + throw new Base64DecoderException( + "invalid padding byte '=' at byte offset " + i); + } else if ((b4Posn == 3 && bytesLeft > 2) + || (b4Posn == 4 && bytesLeft > 1)) { + throw new Base64DecoderException( + "padding byte '=' falsely signals end of encoded value " + + "at offset " + i); + } else if (lastByte != EQUALS_SIGN && lastByte != NEW_LINE) { + throw new Base64DecoderException( + "encoded value has invalid trailing byte"); + } + break; + } + + b4[b4Posn++] = sbiCrop; + if (b4Posn == 4) { + outBuffPosn += decode4to3(b4, 0, outBuff, outBuffPosn, decodabet); + b4Posn = 0; + } + } + } else { + throw new Base64DecoderException("Bad Base64 input character at " + i + + ": " + source[i + off] + "(decimal)"); + } + } + + // Because web safe encoding allows non padding base64 encodes, we + // need to pad the rest of the b4 buffer with equal signs when + // b4Posn != 0. There can be at most 2 equal signs at the end of + // four characters, so the b4 buffer must have two or three + // characters. This also catches the case where the input is + // padded with EQUALS_SIGN + if (b4Posn != 0) { + if (b4Posn == 1) { + throw new Base64DecoderException("single trailing character at offset " + + (len - 1)); + } + b4[b4Posn++] = EQUALS_SIGN; + outBuffPosn += decode4to3(b4, 0, outBuff, outBuffPosn, decodabet); + } + + byte[] out = new byte[outBuffPosn]; + System.arraycopy(outBuff, 0, out, 0, outBuffPosn); + return out; + } +} diff --git a/V2rayNG/app/src/main/java/com/v2ray/ang/util/Base64DecoderException.java b/V2rayNG/app/src/main/java/com/v2ray/ang/util/Base64DecoderException.java new file mode 100644 index 000000000..b113e43f8 --- /dev/null +++ b/V2rayNG/app/src/main/java/com/v2ray/ang/util/Base64DecoderException.java @@ -0,0 +1,32 @@ +// Copyright 2002, Google, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.v2ray.ang.util; + +/** + * Exception thrown when encountering an invalid Base64 input character. + * + * @author nelson + */ +public class Base64DecoderException extends Exception { + public Base64DecoderException() { + super(); + } + + public Base64DecoderException(String s) { + super(s); + } + + private static final long serialVersionUID = 1L; +} diff --git a/V2rayNG/app/src/main/java/com/v2ray/ang/util/IabException.java b/V2rayNG/app/src/main/java/com/v2ray/ang/util/IabException.java new file mode 100644 index 000000000..e6320808d --- /dev/null +++ b/V2rayNG/app/src/main/java/com/v2ray/ang/util/IabException.java @@ -0,0 +1,43 @@ +/* Copyright (c) 2012 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.v2ray.ang.util; + +/** + * Exception thrown when something went wrong with in-app billing. + * An IabException has an associated IabResult (an error). + * To get the IAB result that caused this exception to be thrown, + * call {@link #getResult()}. + */ +public class IabException extends Exception { + IabResult mResult; + + public IabException(IabResult r) { + this(r, null); + } + public IabException(int response, String message) { + this(new IabResult(response, message)); + } + public IabException(IabResult r, Exception cause) { + super(r.getMessage(), cause); + mResult = r; + } + public IabException(int response, String message, Exception cause) { + this(new IabResult(response, message), cause); + } + + /** Returns the IAB result (error) that this exception signals. */ + public IabResult getResult() { return mResult; } +} \ No newline at end of file diff --git a/V2rayNG/app/src/main/java/com/v2ray/ang/util/IabHelper.java b/V2rayNG/app/src/main/java/com/v2ray/ang/util/IabHelper.java new file mode 100644 index 000000000..911d20dae --- /dev/null +++ b/V2rayNG/app/src/main/java/com/v2ray/ang/util/IabHelper.java @@ -0,0 +1,979 @@ +/* Copyright (c) 2012 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.v2ray.ang.util; + +import android.app.Activity; +import android.app.PendingIntent; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.IntentSender.SendIntentException; +import android.content.ServiceConnection; +import android.os.Bundle; +import android.os.Handler; +import android.os.IBinder; +import android.os.RemoteException; +import android.text.TextUtils; +import android.util.Log; + +import com.android.vending.billing.IInAppBillingService; + +import org.json.JSONException; + +import java.util.ArrayList; +import java.util.List; + + +/** + * Provides convenience methods for in-app billing. You can create one instance of this + * class for your application and use it to process in-app billing operations. + * It provides synchronous (blocking) and asynchronous (non-blocking) methods for + * many common in-app billing operations, as well as automatic signature + * verification. + *
+ * After instantiating, you must perform setup in order to start using the object. + * To perform setup, call the {@link #startSetup} method and provide a listener; + * that listener will be notified when setup is complete, after which (and not before) + * you may call other methods. + *
+ * After setup is complete, you will typically want to request an inventory of owned + * items and subscriptions. See {@link #queryInventory}, {@link #queryInventoryAsync} + * and related methods. + *
+ * When you are done with this object, don't forget to call {@link #dispose} + * to ensure proper cleanup. This object holds a binding to the in-app billing + * service, which will leak unless you dispose of it correctly. If you created + * the object on an Activity's onCreate method, then the recommended + * place to dispose of it is the Activity's onDestroy method. + *
+ * A note about threading: When using this object from a background thread, you may
+ * call the blocking versions of methods; when using from a UI thread, call
+ * only the asynchronous versions and handle the results via callbacks.
+ * Also, notice that you can only call one asynchronous operation at a time;
+ * attempting to start a second asynchronous operation while the first one
+ * has not yet completed will result in an exception being thrown.
+ *
+ * @author Bruno Oliveira (Google)
+ */
+public class IabHelper {
+ // Is debug logging enabled?
+ boolean mDebugLog = false;
+ String mDebugTag = "IabHelper";
+
+ // Is setup done?
+ boolean mSetupDone = false;
+
+ // Has this object been disposed of? (If so, we should ignore callbacks, etc)
+ boolean mDisposed = false;
+
+ // Are subscriptions supported?
+ boolean mSubscriptionsSupported = false;
+
+ // Is an asynchronous operation in progress?
+ // (only one at a time can be in progress)
+ boolean mAsyncInProgress = false;
+
+ // (for logging/debugging)
+ // if mAsyncInProgress == true, what asynchronous operation is in progress?
+ String mAsyncOperation = "";
+
+ // Context we were passed during initialization
+ Context mContext;
+
+ // Connection to the service
+ IInAppBillingService mService;
+ ServiceConnection mServiceConn;
+
+ // The request code used to launch purchase flow
+ int mRequestCode;
+
+ // The item type of the current purchase flow
+ String mPurchasingItemType;
+
+ // Public key for verifying signature, in base64 encoding
+ String mSignatureBase64 = null;
+
+ // Billing response codes
+ public static final int BILLING_RESPONSE_RESULT_OK = 0;
+ public static final int BILLING_RESPONSE_RESULT_USER_CANCELED = 1;
+ public static final int BILLING_RESPONSE_RESULT_BILLING_UNAVAILABLE = 3;
+ public static final int BILLING_RESPONSE_RESULT_ITEM_UNAVAILABLE = 4;
+ public static final int BILLING_RESPONSE_RESULT_DEVELOPER_ERROR = 5;
+ public static final int BILLING_RESPONSE_RESULT_ERROR = 6;
+ public static final int BILLING_RESPONSE_RESULT_ITEM_ALREADY_OWNED = 7;
+ public static final int BILLING_RESPONSE_RESULT_ITEM_NOT_OWNED = 8;
+
+ // IAB Helper error codes
+ public static final int IABHELPER_ERROR_BASE = -1000;
+ public static final int IABHELPER_REMOTE_EXCEPTION = -1001;
+ public static final int IABHELPER_BAD_RESPONSE = -1002;
+ public static final int IABHELPER_VERIFICATION_FAILED = -1003;
+ public static final int IABHELPER_SEND_INTENT_FAILED = -1004;
+ public static final int IABHELPER_USER_CANCELLED = -1005;
+ public static final int IABHELPER_UNKNOWN_PURCHASE_RESPONSE = -1006;
+ public static final int IABHELPER_MISSING_TOKEN = -1007;
+ public static final int IABHELPER_UNKNOWN_ERROR = -1008;
+ public static final int IABHELPER_SUBSCRIPTIONS_NOT_AVAILABLE = -1009;
+ public static final int IABHELPER_INVALID_CONSUMPTION = -1010;
+
+ // Keys for the responses from InAppBillingService
+ public static final String RESPONSE_CODE = "RESPONSE_CODE";
+ public static final String RESPONSE_GET_SKU_DETAILS_LIST = "DETAILS_LIST";
+ public static final String RESPONSE_BUY_INTENT = "BUY_INTENT";
+ public static final String RESPONSE_INAPP_PURCHASE_DATA = "INAPP_PURCHASE_DATA";
+ public static final String RESPONSE_INAPP_SIGNATURE = "INAPP_DATA_SIGNATURE";
+ public static final String RESPONSE_INAPP_ITEM_LIST = "INAPP_PURCHASE_ITEM_LIST";
+ public static final String RESPONSE_INAPP_PURCHASE_DATA_LIST = "INAPP_PURCHASE_DATA_LIST";
+ public static final String RESPONSE_INAPP_SIGNATURE_LIST = "INAPP_DATA_SIGNATURE_LIST";
+ public static final String INAPP_CONTINUATION_TOKEN = "INAPP_CONTINUATION_TOKEN";
+
+ // Item types
+ public static final String ITEM_TYPE_INAPP = "inapp";
+ public static final String ITEM_TYPE_SUBS = "subs";
+
+ // some fields on the getSkuDetails response bundle
+ public static final String GET_SKU_DETAILS_ITEM_LIST = "ITEM_ID_LIST";
+ public static final String GET_SKU_DETAILS_ITEM_TYPE_LIST = "ITEM_TYPE_LIST";
+
+ /**
+ * Creates an instance. After creation, it will not yet be ready to use. You must perform
+ * setup by calling {@link #startSetup} and wait for setup to complete. This constructor does not
+ * block and is safe to call from a UI thread.
+ *
+ * @param ctx Your application or Activity context. Needed to bind to the in-app billing service.
+ * @param base64PublicKey Your application's public key, encoded in base64.
+ * This is used for verification of purchase signatures. You can find your app's base64-encoded
+ * public key in your application's page on Google Play Developer Console. Note that this
+ * is NOT your "developer public key".
+ */
+ public IabHelper(Context ctx, String base64PublicKey) {
+ mContext = ctx.getApplicationContext();
+ mSignatureBase64 = base64PublicKey;
+ logDebug("IAB helper created.");
+ }
+
+ /**
+ * Enables or disable debug logging through LogCat.
+ */
+ public void enableDebugLogging(boolean enable, String tag) {
+ checkNotDisposed();
+ mDebugLog = enable;
+ mDebugTag = tag;
+ }
+
+ public void enableDebugLogging(boolean enable) {
+ checkNotDisposed();
+ mDebugLog = enable;
+ }
+
+ /**
+ * Callback for setup process. This listener's {@link #onIabSetupFinished} method is called
+ * when the setup process is complete.
+ */
+ public interface OnIabSetupFinishedListener {
+ /**
+ * Called to notify that setup is complete.
+ *
+ * @param result The result of the setup process.
+ */
+ void onIabSetupFinished(IabResult result);
+ }
+
+ /**
+ * Starts the setup process. This will start up the setup process asynchronously.
+ * You will be notified through the listener when the setup process is complete.
+ * This method is safe to call from a UI thread.
+ *
+ * @param listener The listener to notify when the setup process is complete.
+ */
+ public void startSetup(final OnIabSetupFinishedListener listener) {
+ // If already set up, can't do it again.
+ checkNotDisposed();
+ if (mSetupDone) throw new IllegalStateException("IAB helper is already set up.");
+
+ // Connection to IAB service
+ logDebug("Starting in-app billing setup.");
+ mServiceConn = new ServiceConnection() {
+ @Override
+ public void onServiceDisconnected(ComponentName name) {
+ logDebug("Billing service disconnected.");
+ mService = null;
+ }
+
+ @Override
+ public void onServiceConnected(ComponentName name, IBinder service) {
+ if (mDisposed) return;
+ logDebug("Billing service connected.");
+ mService = IInAppBillingService.Stub.asInterface(service);
+ String packageName = mContext.getPackageName();
+ try {
+ logDebug("Checking for in-app billing 3 support.");
+
+ // check for in-app billing v3 support
+ int response = mService.isBillingSupported(3, packageName, ITEM_TYPE_INAPP);
+ if (response != BILLING_RESPONSE_RESULT_OK) {
+ if (listener != null) listener.onIabSetupFinished(new IabResult(response,
+ "Error checking for billing v3 support."));
+
+ // if in-app purchases aren't supported, neither are subscriptions.
+ mSubscriptionsSupported = false;
+ return;
+ }
+ logDebug("In-app billing version 3 supported for " + packageName);
+
+ // check for v3 subscriptions support
+ response = mService.isBillingSupported(3, packageName, ITEM_TYPE_SUBS);
+ if (response == BILLING_RESPONSE_RESULT_OK) {
+ logDebug("Subscriptions AVAILABLE.");
+ mSubscriptionsSupported = true;
+ } else {
+ logDebug("Subscriptions NOT AVAILABLE. Response: " + response);
+ }
+
+ mSetupDone = true;
+ } catch (RemoteException e) {
+ if (listener != null) {
+ listener.onIabSetupFinished(new IabResult(IABHELPER_REMOTE_EXCEPTION,
+ "RemoteException while setting up in-app billing."));
+ }
+ e.printStackTrace();
+ return;
+ }
+
+ if (listener != null) {
+ listener.onIabSetupFinished(new IabResult(BILLING_RESPONSE_RESULT_OK, "Setup successful."));
+ }
+ }
+ };
+
+// Intent serviceIntent = new Intent("ir.cafebazaar.pardakht.InAppBillingService.BIND");
+// serviceIntent.setPackage("com.farsitel.bazaar");
+ Intent serviceIntent = new Intent("com.android.vending.billing.InAppBillingService.BIND");
+ serviceIntent.setPackage("com.android.vending");
+ if (!mContext.getPackageManager().queryIntentServices(serviceIntent, 0).isEmpty()) {
+ // service available to handle that Intent
+ mContext.bindService(serviceIntent, mServiceConn, Context.BIND_AUTO_CREATE);
+ } else {
+ // no service available to handle that Intent
+ if (listener != null) {
+ listener.onIabSetupFinished(
+ new IabResult(BILLING_RESPONSE_RESULT_BILLING_UNAVAILABLE,
+ "Billing service unavailable on device."));
+ }
+ }
+ }
+
+ /**
+ * Dispose of object, releasing resources. It's very important to call this
+ * method when you are done with this object. It will release any resources
+ * used by it such as service connections. Naturally, once the object is
+ * disposed of, it can't be used again.
+ */
+ public void dispose() {
+ logDebug("Disposing.");
+ mSetupDone = false;
+ if (mServiceConn != null) {
+ logDebug("Unbinding from service.");
+ if (mContext != null) mContext.unbindService(mServiceConn);
+ }
+ mDisposed = true;
+ mContext = null;
+ mServiceConn = null;
+ mService = null;
+ mPurchaseListener = null;
+ }
+
+ private void checkNotDisposed() {
+ if (mDisposed)
+ throw new IllegalStateException("IabHelper was disposed of, so it cannot be used.");
+ }
+
+ /**
+ * Returns whether subscriptions are supported.
+ */
+ public boolean subscriptionsSupported() {
+ checkNotDisposed();
+ return mSubscriptionsSupported;
+ }
+
+
+ /**
+ * Callback that notifies when a purchase is finished.
+ */
+ public interface OnIabPurchaseFinishedListener {
+ /**
+ * Called to notify that an in-app purchase finished. If the purchase was successful,
+ * then the sku parameter specifies which item was purchased. If the purchase failed,
+ * the sku and extraData parameters may or may not be null, depending on how far the purchase
+ * process went.
+ *
+ * @param result The result of the purchase.
+ * @param info The purchase information (null if purchase failed)
+ */
+ void onIabPurchaseFinished(IabResult result, Purchase info);
+ }
+
+ // The listener registered on launchPurchaseFlow, which we have to call back when
+ // the purchase finishes
+ OnIabPurchaseFinishedListener mPurchaseListener;
+
+ public void launchPurchaseFlow(Activity act, String sku, int requestCode, OnIabPurchaseFinishedListener listener) {
+ launchPurchaseFlow(act, sku, requestCode, listener, "");
+ }
+
+ public void launchPurchaseFlow(Activity act, String sku, int requestCode,
+ OnIabPurchaseFinishedListener listener, String extraData) {
+ launchPurchaseFlow(act, sku, ITEM_TYPE_INAPP, requestCode, listener, extraData);
+ }
+
+ public void launchSubscriptionPurchaseFlow(Activity act, String sku, int requestCode,
+ OnIabPurchaseFinishedListener listener) {
+ launchSubscriptionPurchaseFlow(act, sku, requestCode, listener, "");
+ }
+
+ public void launchSubscriptionPurchaseFlow(Activity act, String sku, int requestCode,
+ OnIabPurchaseFinishedListener listener, String extraData) {
+ launchPurchaseFlow(act, sku, ITEM_TYPE_SUBS, requestCode, listener, extraData);
+ }
+
+ /**
+ * Initiate the UI flow for an in-app purchase. Call this method to initiate an in-app purchase,
+ * which will involve bringing up the Google Play screen. The calling activity will be paused while
+ * the user interacts with Google Play, and the result will be delivered via the activity's
+ * {@link android.app.Activity#onActivityResult} method, at which point you must call
+ * this object's {@link #handleActivityResult} method to continue the purchase flow. This method
+ * MUST be called from the UI thread of the Activity.
+ *
+ * @param act The calling activity.
+ * @param sku The sku of the item to purchase.
+ * @param itemType indicates if it's a product or a subscription (ITEM_TYPE_INAPP or ITEM_TYPE_SUBS)
+ * @param requestCode A request code (to differentiate from other responses --
+ * as in {@link android.app.Activity#startActivityForResult}).
+ * @param listener The listener to notify when the purchase process finishes
+ * @param extraData Extra data (developer payload), which will be returned with the purchase data
+ * when the purchase completes. This extra data will be permanently bound to that purchase
+ * and will always be returned when the purchase is queried.
+ */
+ public void launchPurchaseFlow(Activity act, String sku, String itemType, int requestCode,
+ OnIabPurchaseFinishedListener listener, String extraData) {
+ checkNotDisposed();
+ checkSetupDone("launchPurchaseFlow");
+ flagStartAsync("launchPurchaseFlow");
+ IabResult result;
+
+ if (itemType.equals(ITEM_TYPE_SUBS) && !mSubscriptionsSupported) {
+ IabResult r = new IabResult(IABHELPER_SUBSCRIPTIONS_NOT_AVAILABLE,
+ "Subscriptions are not available.");
+ flagEndAsync();
+ if (listener != null) listener.onIabPurchaseFinished(r, null);
+ return;
+ }
+
+ try {
+ logDebug("Constructing buy intent for " + sku + ", item type: " + itemType);
+ Bundle buyIntentBundle = mService.getBuyIntent(3, mContext.getPackageName(), sku, itemType, extraData);
+ int response = getResponseCodeFromBundle(buyIntentBundle);
+ if (response != BILLING_RESPONSE_RESULT_OK) {
+ logError("Unable to buy item, Error response: " + getResponseDesc(response));
+ flagEndAsync();
+ result = new IabResult(response, "Unable to buy item");
+ if (listener != null) listener.onIabPurchaseFinished(result, null);
+ return;
+ }
+
+ PendingIntent pendingIntent = buyIntentBundle.getParcelable(RESPONSE_BUY_INTENT);
+ logDebug("Launching buy intent for " + sku + ". Request code: " + requestCode);
+ mRequestCode = requestCode;
+ mPurchaseListener = listener;
+ mPurchasingItemType = itemType;
+ act.startIntentSenderForResult(pendingIntent.getIntentSender(),
+ requestCode, new Intent(),
+ Integer.valueOf(0), Integer.valueOf(0),
+ Integer.valueOf(0));
+ } catch (SendIntentException e) {
+ logError("SendIntentException while launching purchase flow for sku " + sku);
+ e.printStackTrace();
+ flagEndAsync();
+
+ result = new IabResult(IABHELPER_SEND_INTENT_FAILED, "Failed to send intent.");
+ if (listener != null) listener.onIabPurchaseFinished(result, null);
+ } catch (RemoteException e) {
+ logError("RemoteException while launching purchase flow for sku " + sku);
+ e.printStackTrace();
+ flagEndAsync();
+
+ result = new IabResult(IABHELPER_REMOTE_EXCEPTION, "Remote exception while starting purchase flow");
+ if (listener != null) listener.onIabPurchaseFinished(result, null);
+ }
+ }
+
+ /**
+ * Handles an activity result that's part of the purchase flow in in-app billing. If you
+ * are calling {@link #launchPurchaseFlow}, then you must call this method from your
+ * Activity's {@link android.app.Activity@onActivityResult} method. This method
+ * MUST be called from the UI thread of the Activity.
+ *
+ * @param requestCode The requestCode as you received it.
+ * @param resultCode The resultCode as you received it.
+ * @param data The data (Intent) as you received it.
+ * @return Returns true if the result was related to a purchase flow and was handled;
+ * false if the result was not related to a purchase, in which case you should
+ * handle it normally.
+ */
+ public boolean handleActivityResult(int requestCode, int resultCode, Intent data) {
+ IabResult result;
+ if (requestCode != mRequestCode) return false;
+
+ checkNotDisposed();
+ checkSetupDone("handleActivityResult");
+
+ // end of async purchase operation that started on launchPurchaseFlow
+ flagEndAsync();
+
+ if (data == null) {
+ logError("Null data in IAB activity result.");
+ result = new IabResult(IABHELPER_BAD_RESPONSE, "Null data in IAB result");
+ if (mPurchaseListener != null) mPurchaseListener.onIabPurchaseFinished(result, null);
+ return true;
+ }
+
+ int responseCode = getResponseCodeFromIntent(data);
+ String purchaseData = data.getStringExtra(RESPONSE_INAPP_PURCHASE_DATA);
+ String dataSignature = data.getStringExtra(RESPONSE_INAPP_SIGNATURE);
+
+ if (resultCode == Activity.RESULT_OK && responseCode == BILLING_RESPONSE_RESULT_OK) {
+ logDebug("Successful resultcode from purchase activity.");
+ logDebug("Purchase data: " + purchaseData);
+ logDebug("Data signature: " + dataSignature);
+ logDebug("Extras: " + data.getExtras());
+ logDebug("Expected item type: " + mPurchasingItemType);
+
+ if (purchaseData == null || dataSignature == null) {
+ logError("BUG: either purchaseData or dataSignature is null.");
+ logDebug("Extras: " + data.getExtras().toString());
+ result = new IabResult(IABHELPER_UNKNOWN_ERROR, "IAB returned null purchaseData or dataSignature");
+ if (mPurchaseListener != null)
+ mPurchaseListener.onIabPurchaseFinished(result, null);
+ return true;
+ }
+
+ Purchase purchase = null;
+ try {
+ purchase = new Purchase(mPurchasingItemType, purchaseData, dataSignature);
+ String sku = purchase.getSku();
+
+ // Verify signature
+ if (!Security.verifyPurchase(mSignatureBase64, purchaseData, dataSignature)) {
+ logError("Purchase signature verification FAILED for sku " + sku);
+ result = new IabResult(IABHELPER_VERIFICATION_FAILED, "Signature verification failed for sku " + sku);
+ if (mPurchaseListener != null)
+ mPurchaseListener.onIabPurchaseFinished(result, purchase);
+ return true;
+ }
+ logDebug("Purchase signature successfully verified.");
+ } catch (JSONException e) {
+ logError("Failed to parse purchase data.");
+ e.printStackTrace();
+ result = new IabResult(IABHELPER_BAD_RESPONSE, "Failed to parse purchase data.");
+ if (mPurchaseListener != null)
+ mPurchaseListener.onIabPurchaseFinished(result, null);
+ return true;
+ }
+
+ if (mPurchaseListener != null) {
+ mPurchaseListener.onIabPurchaseFinished(new IabResult(BILLING_RESPONSE_RESULT_OK, "Success"), purchase);
+ }
+ } else if (resultCode == Activity.RESULT_OK) {
+ // result code was OK, but in-app billing response was not OK.
+ logDebug("Result code was OK but in-app billing response was not OK: " + getResponseDesc(responseCode));
+ if (mPurchaseListener != null) {
+ result = new IabResult(responseCode, "Problem purchashing item.");
+ mPurchaseListener.onIabPurchaseFinished(result, null);
+ }
+ } else if (resultCode == Activity.RESULT_CANCELED) {
+ logDebug("Purchase canceled - Response: " + getResponseDesc(responseCode));
+ result = new IabResult(IABHELPER_USER_CANCELLED, "User canceled.");
+ if (mPurchaseListener != null) mPurchaseListener.onIabPurchaseFinished(result, null);
+ } else {
+ logError("Purchase failed. Result code: " + Integer.toString(resultCode)
+ + ". Response: " + getResponseDesc(responseCode));
+ result = new IabResult(IABHELPER_UNKNOWN_PURCHASE_RESPONSE, "Unknown purchase response.");
+ if (mPurchaseListener != null) mPurchaseListener.onIabPurchaseFinished(result, null);
+ }
+ return true;
+ }
+
+ public Inventory queryInventory(boolean querySkuDetails, List