diff --git a/app/build.gradle b/app/build.gradle
index 4bc614a..fface70 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -41,9 +41,9 @@ android {
//新平台正式
newly {
flavorDimensions "default"
- versionCode 518
+ versionCode 519
//versionCode 1037
- versionName "2.1.8"
+ versionName "2.1.9"
/*********************************极光推送************************************/
manifestPlaceholders = [
JPUSH_PKGNAME: "com.jiaoguanyi.appstore",
@@ -61,8 +61,8 @@ android {
//新平台测试
beta {
flavorDimensions "default"
- versionCode 525
- versionName "2.1.5"
+ versionCode 528
+ versionName "2.1.8"
/*********************************极光推送************************************/
manifestPlaceholders = [
JPUSH_PKGNAME: "com.jiaoguanyi.appstore",
@@ -264,8 +264,8 @@ dependencies {
//implementation fileTree(include: ['*.jar'], dir: 'libs')
compileOnly files('src/main/libs/classes.jar')
- implementation 'androidx.appcompat:appcompat:1.2.0'
- implementation 'androidx.recyclerview:recyclerview:1.1.0'
+ implementation 'androidx.appcompat:appcompat:1.3.0'
+ implementation 'androidx.recyclerview:recyclerview:1.2.1'
implementation 'androidx.cardview:cardview:1.0.0'
implementation 'androidx.multidex:multidex:2.0.1'
implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 2f29617..032a0d1 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -56,13 +56,10 @@
android:maxSdkVersion="22" />
-
+ android:maxSdkVersion="23" />
-
-
-
-
+
+
@@ -75,16 +72,13 @@
-
-
-
-
+
+
-
+ android:process=":remote"
+ android:exported="true" />
+
@@ -118,14 +117,13 @@
-
-
-
+
+
@@ -366,8 +364,7 @@
android:value="developer-default" />
-
+ android:value="${JPUSH_APPKEY}" />
{
+ //设置二维码
+ void getQRImage(String mac);
//获取学生信息
void getStudesInfo();
//获取设备锁定状态
diff --git a/app/src/main/java/com/mjsheng/myappstore/activity/MainPresenter.java b/app/src/main/java/com/mjsheng/myappstore/activity/MainPresenter.java
index 19bf18c..d5b4f6c 100644
--- a/app/src/main/java/com/mjsheng/myappstore/activity/MainPresenter.java
+++ b/app/src/main/java/com/mjsheng/myappstore/activity/MainPresenter.java
@@ -20,7 +20,6 @@ import com.google.gson.JsonParser;
import com.google.gson.reflect.TypeToken;
import com.mjsheng.myappstore.BuildConfig;
import com.mjsheng.myappstore.base.BaseApplication;
-import com.mjsheng.myappstore.bean.Appground;
import com.mjsheng.myappstore.bean.BaseResponse;
import com.mjsheng.myappstore.bean.BrowserData;
import com.mjsheng.myappstore.bean.ForceDownloadBean;
@@ -31,8 +30,9 @@ import com.mjsheng.myappstore.bean.StudentsInfo;
import com.mjsheng.myappstore.jpush.TagAliasOperatorHelper;
import com.mjsheng.myappstore.manager.NetInterfaceManager;
import com.mjsheng.myappstore.network.HTTPInterface;
-import com.mjsheng.myappstore.server.MainService;
+import com.mjsheng.myappstore.service.MainService;
import com.mjsheng.myappstore.utils.ApkUtils;
+import com.mjsheng.myappstore.utils.AES.CXAESUtil;
import com.mjsheng.myappstore.utils.ForegroundAppUtil;
import com.mjsheng.myappstore.utils.JGYUtils;
import com.mjsheng.myappstore.utils.SPUtils;
@@ -88,6 +88,25 @@ public class MainPresenter implements MainContact.Presenter {
this.mView = null;
}
+ String key = "0123456789ABCDEF";
+
+ @Override
+ public void getQRImage(String mac) {
+ Log.e(TAG, "getQRImage: " + mac);
+ JsonObject jsonObject = new JsonObject();
+ jsonObject.addProperty("sn", Utils.getSerial());
+ jsonObject.addProperty("mac", mac);
+ String json = jsonObject.toString();
+ String content = mac;
+ try {
+ content = CXAESUtil.encrypt(key, json);
+ Log.e(TAG, "getQRImage: " + content);
+ } catch (Exception e) {
+ Log.e(TAG, "getQRImage: " + e.getMessage());
+ e.printStackTrace();
+ }
+ mView.setQRImage(JGYUtils.getInstance().createQRImage(content, 300, 300));
+ }
/**
* 通过sn获取用户信息
diff --git a/app/src/main/java/com/mjsheng/myappstore/manager/NetInterfaceManager.java b/app/src/main/java/com/mjsheng/myappstore/manager/NetInterfaceManager.java
index 1fa5e88..8be0f6b 100644
--- a/app/src/main/java/com/mjsheng/myappstore/manager/NetInterfaceManager.java
+++ b/app/src/main/java/com/mjsheng/myappstore/manager/NetInterfaceManager.java
@@ -1,5 +1,6 @@
package com.mjsheng.myappstore.manager;
+import android.annotation.SuppressLint;
import android.content.Context;
import android.os.Environment;
@@ -65,7 +66,8 @@ import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory;
import retrofit2.converter.gson.GsonConverterFactory;
public class NetInterfaceManager {
- private static NetInterfaceManager sInstance;
+ @SuppressLint("StaticFieldLeak")
+ private static NetInterfaceManager INSTANCE;
private Context mContext;
private static Retrofit mRetrofit;
@@ -86,17 +88,17 @@ public class NetInterfaceManager {
}
public static void init(Context context) {
- if (sInstance == null) {
- sInstance = new NetInterfaceManager(context);
+ if (INSTANCE == null) {
+ INSTANCE = new NetInterfaceManager(context);
}
}
public static NetInterfaceManager getInstance() {
- if (sInstance == null) {
+ if (INSTANCE == null) {
throw new IllegalStateException("You must be init NetworkManager first");
}
- return sInstance;
+ return INSTANCE;
}
private static final long cacheSize = 1024 * 1024 * 32;// 缓存文件最大限制大小20M
diff --git a/app/src/main/java/com/mjsheng/myappstore/network/HTTPInterface.java b/app/src/main/java/com/mjsheng/myappstore/network/HTTPInterface.java
index bcb9ba5..ae03f46 100644
--- a/app/src/main/java/com/mjsheng/myappstore/network/HTTPInterface.java
+++ b/app/src/main/java/com/mjsheng/myappstore/network/HTTPInterface.java
@@ -24,17 +24,15 @@ import com.mjsheng.myappstore.utils.JGYUtils;
import com.mjsheng.myappstore.utils.URLUtils;
import com.lzy.okgo.OkGo;
import com.lzy.okgo.callback.StringCallback;
-import com.mjsheng.myappstore.base.BaseApplication;
import com.mjsheng.myappstore.bean.Appground;
import com.mjsheng.myappstore.bean.BaseResponse;
import com.mjsheng.myappstore.bean.NetAndLaunchBean;
-import com.mjsheng.myappstore.bean.NetAndLaunchData;
import com.mjsheng.myappstore.jpush.TagAliasOperatorHelper;
import com.mjsheng.myappstore.manager.NetInterfaceManager;
import com.mjsheng.myappstore.network.api.newapi.SnTimeControl;
import com.mjsheng.myappstore.network.api.newapi.TopAppControlApi;
import com.mjsheng.myappstore.network.api.newapi.UpdateDeviceInfoApi;
-import com.mjsheng.myappstore.server.MainService;
+import com.mjsheng.myappstore.service.MainService;
import com.mjsheng.myappstore.utils.ApkUtils;
import com.mjsheng.myappstore.utils.ForegroundAppUtil;
import com.mjsheng.myappstore.utils.Logger;
diff --git a/app/src/main/java/com/mjsheng/myappstore/receiver/BootReceiver.java b/app/src/main/java/com/mjsheng/myappstore/receiver/BootReceiver.java
index 600cc6e..1ad9ec9 100644
--- a/app/src/main/java/com/mjsheng/myappstore/receiver/BootReceiver.java
+++ b/app/src/main/java/com/mjsheng/myappstore/receiver/BootReceiver.java
@@ -3,17 +3,13 @@ package com.mjsheng.myappstore.receiver;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
-import android.provider.Settings;
-import android.text.TextUtils;
import android.util.Log;
-import com.mjsheng.myappstore.server.GuardService;
-import com.mjsheng.myappstore.server.LogcatService;
-import com.mjsheng.myappstore.server.MainService;
-import com.mjsheng.myappstore.server.StepService;
+import com.mjsheng.myappstore.service.GuardService;
+import com.mjsheng.myappstore.service.LogcatService;
+import com.mjsheng.myappstore.service.MainService;
+import com.mjsheng.myappstore.service.StepService;
import com.mjsheng.myappstore.utils.BootManager;
-import com.mjsheng.myappstore.utils.JGYUtils;
-import com.mjsheng.myappstore.utils.SPUtils;
public class BootReceiver extends BroadcastReceiver {
private String TAG = BootReceiver.class.getSimpleName() + ":myappstore";
diff --git a/app/src/main/java/com/mjsheng/myappstore/receiver/MyJPushReceiver.java b/app/src/main/java/com/mjsheng/myappstore/receiver/MyJPushReceiver.java
index 56a5e36..e359dea 100644
--- a/app/src/main/java/com/mjsheng/myappstore/receiver/MyJPushReceiver.java
+++ b/app/src/main/java/com/mjsheng/myappstore/receiver/MyJPushReceiver.java
@@ -1,7 +1,6 @@
package com.mjsheng.myappstore.receiver;
import android.annotation.SuppressLint;
-import android.bluetooth.BluetoothAdapter;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.ContextWrapper;
@@ -35,7 +34,7 @@ import com.mjsheng.myappstore.manager.AmapManager;
import com.mjsheng.myappstore.manager.NetInterfaceManager;
import com.mjsheng.myappstore.network.HTTPInterface;
import com.mjsheng.myappstore.network.URLAddress;
-import com.mjsheng.myappstore.server.MainService;
+import com.mjsheng.myappstore.service.MainService;
import com.mjsheng.myappstore.utils.ApkUtils;
import com.mjsheng.myappstore.utils.CmdUtil;
import com.mjsheng.myappstore.utils.ForegroundAppUtil;
diff --git a/app/src/main/java/com/mjsheng/myappstore/server/GuardService.java b/app/src/main/java/com/mjsheng/myappstore/service/GuardService.java
similarity index 99%
rename from app/src/main/java/com/mjsheng/myappstore/server/GuardService.java
rename to app/src/main/java/com/mjsheng/myappstore/service/GuardService.java
index 0504b61..cf6338d 100644
--- a/app/src/main/java/com/mjsheng/myappstore/server/GuardService.java
+++ b/app/src/main/java/com/mjsheng/myappstore/service/GuardService.java
@@ -1,4 +1,4 @@
-package com.mjsheng.myappstore.server;
+package com.mjsheng.myappstore.service;
/**
* 作者 mjsheng
diff --git a/app/src/main/java/com/mjsheng/myappstore/server/JWebSocketClient.java b/app/src/main/java/com/mjsheng/myappstore/service/JWebSocketClient.java
similarity index 95%
rename from app/src/main/java/com/mjsheng/myappstore/server/JWebSocketClient.java
rename to app/src/main/java/com/mjsheng/myappstore/service/JWebSocketClient.java
index 0957f74..22d986f 100644
--- a/app/src/main/java/com/mjsheng/myappstore/server/JWebSocketClient.java
+++ b/app/src/main/java/com/mjsheng/myappstore/service/JWebSocketClient.java
@@ -1,4 +1,4 @@
-package com.mjsheng.myappstore.server;
+package com.mjsheng.myappstore.service;
import android.util.Log;
diff --git a/app/src/main/java/com/mjsheng/myappstore/server/LogcatService.java b/app/src/main/java/com/mjsheng/myappstore/service/LogcatService.java
similarity index 98%
rename from app/src/main/java/com/mjsheng/myappstore/server/LogcatService.java
rename to app/src/main/java/com/mjsheng/myappstore/service/LogcatService.java
index 5f4de5a..482726f 100644
--- a/app/src/main/java/com/mjsheng/myappstore/server/LogcatService.java
+++ b/app/src/main/java/com/mjsheng/myappstore/service/LogcatService.java
@@ -1,16 +1,14 @@
-package com.mjsheng.myappstore.server;
+package com.mjsheng.myappstore.service;
import android.app.Service;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
-import android.os.Environment;
import android.os.IBinder;
import android.text.TextUtils;
import android.util.Log;
-import com.android.server.am.ServiceRecordProto;
import com.mjsheng.myappstore.utils.Utils;
import java.io.BufferedReader;
diff --git a/app/src/main/java/com/mjsheng/myappstore/server/MainService.java b/app/src/main/java/com/mjsheng/myappstore/service/MainService.java
similarity index 99%
rename from app/src/main/java/com/mjsheng/myappstore/server/MainService.java
rename to app/src/main/java/com/mjsheng/myappstore/service/MainService.java
index aafcda6..864b245 100644
--- a/app/src/main/java/com/mjsheng/myappstore/server/MainService.java
+++ b/app/src/main/java/com/mjsheng/myappstore/service/MainService.java
@@ -1,4 +1,4 @@
-package com.mjsheng.myappstore.server;
+package com.mjsheng.myappstore.service;
import android.annotation.SuppressLint;
import android.app.Service;
@@ -6,6 +6,7 @@ import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.graphics.Bitmap;
import android.graphics.Color;
import android.graphics.PixelFormat;
import android.os.Build;
@@ -27,9 +28,7 @@ import com.mjsheng.myappstore.activity.MainActivity;
import com.mjsheng.myappstore.activity.MainContact;
import com.mjsheng.myappstore.activity.MainPresenter;
import com.mjsheng.myappstore.utils.ApkUtils;
-import com.mjsheng.myappstore.utils.CacheUtils;
import com.mjsheng.myappstore.utils.ForegroundAppUtil;
-import com.mjsheng.myappstore.utils.JGYUtils;
import com.mjsheng.myappstore.utils.SPUtils;
import com.mjsheng.myappstore.utils.SaveListUtils;
import com.mjsheng.myappstore.utils.SysSettingUtils;
@@ -421,6 +420,11 @@ public class MainService extends Service implements MainContact.MainView {
}
}
+ @Override
+ public void setQRImage(Bitmap qrImage) {
+
+ }
+
@Override
public void setBatchText(String text, int visibility) {
diff --git a/app/src/main/java/com/mjsheng/myappstore/server/MyDownloadService.java b/app/src/main/java/com/mjsheng/myappstore/service/MyDownloadService.java
similarity index 99%
rename from app/src/main/java/com/mjsheng/myappstore/server/MyDownloadService.java
rename to app/src/main/java/com/mjsheng/myappstore/service/MyDownloadService.java
index 189c90e..d53a8aa 100644
--- a/app/src/main/java/com/mjsheng/myappstore/server/MyDownloadService.java
+++ b/app/src/main/java/com/mjsheng/myappstore/service/MyDownloadService.java
@@ -1,5 +1,5 @@
-package com.mjsheng.myappstore.server;
+package com.mjsheng.myappstore.service;
import android.app.Service;
import android.content.Intent;
diff --git a/app/src/main/java/com/mjsheng/myappstore/service/RemoteService.java b/app/src/main/java/com/mjsheng/myappstore/service/RemoteService.java
new file mode 100644
index 0000000..895b85b
--- /dev/null
+++ b/app/src/main/java/com/mjsheng/myappstore/service/RemoteService.java
@@ -0,0 +1,41 @@
+package com.mjsheng.myappstore.service;
+
+import android.app.Service;
+import android.content.Intent;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Log;
+
+import com.jiaoguanyi.appstore.IGetLicenseInterface;
+import com.mjsheng.myappstore.utils.Utils;
+
+public class RemoteService extends Service {
+ private String TAG = RemoteService.class.getSimpleName();
+
+ public RemoteService() {
+ }
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ Log.e(TAG, "onBind: ");
+ return mBinde;
+ }
+
+ @Override
+ public int onStartCommand(Intent intent, int flags, int startId) {
+ return super.onStartCommand(intent, flags, startId);
+ }
+
+ IGetLicenseInterface.Stub mBinde = new IGetLicenseInterface.Stub() {
+ @Override
+ public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, String aString) throws RemoteException {
+
+ }
+
+ @Override
+ public String getLicense() throws RemoteException {
+ Log.e(TAG, "getLicense: ");
+ return "8054u98729";
+ }
+ };
+}
diff --git a/app/src/main/java/com/mjsheng/myappstore/server/StepService.java b/app/src/main/java/com/mjsheng/myappstore/service/StepService.java
similarity index 99%
rename from app/src/main/java/com/mjsheng/myappstore/server/StepService.java
rename to app/src/main/java/com/mjsheng/myappstore/service/StepService.java
index 324c5a4..1f7df92 100644
--- a/app/src/main/java/com/mjsheng/myappstore/server/StepService.java
+++ b/app/src/main/java/com/mjsheng/myappstore/service/StepService.java
@@ -1,4 +1,4 @@
-package com.mjsheng.myappstore.server;
+package com.mjsheng.myappstore.service;
/**
* 作者 mjsheng
diff --git a/app/src/main/java/com/mjsheng/myappstore/utils/AES/AESCrypt.java b/app/src/main/java/com/mjsheng/myappstore/utils/AES/AESCrypt.java
new file mode 100644
index 0000000..f41534f
--- /dev/null
+++ b/app/src/main/java/com/mjsheng/myappstore/utils/AES/AESCrypt.java
@@ -0,0 +1,199 @@
+package com.mjsheng.myappstore.utils.AES;
+
+import android.util.Base64;
+import android.util.Log;
+
+import java.io.UnsupportedEncodingException;
+import java.security.GeneralSecurityException;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+
+import javax.crypto.Cipher;
+import javax.crypto.spec.IvParameterSpec;
+import javax.crypto.spec.SecretKeySpec;
+
+/**
+ * Encrypt and decrypt messages using AES 256 bit encryption that are compatible with AESCrypt-ObjC and AESCrypt Ruby.
+ *
+ * Created by scottab on 04/10/2014.
+ */
+public final class AESCrypt {
+
+ private static final String TAG = "AESCrypt";
+
+ //AESCrypt-ObjC uses CBC and PKCS7Padding
+ private static final String AES_MODE = "AES/CBC/PKCS7Padding";
+ private static final String CHARSET = "UTF-8";
+
+ //AESCrypt-ObjC uses SHA-256 (and so a 256-bit key)
+ private static final String HASH_ALGORITHM = "SHA-256";
+
+ //AESCrypt-ObjC uses blank IV (not the best security, but the aim here is compatibility)
+ private static final byte[] ivBytes = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+
+ //togglable log option (please turn off in live!)
+ public static boolean DEBUG_LOG_ENABLED = false;
+
+
+ /**
+ * Generates SHA256 hash of the password which is used as key
+ *
+ * @param password used to generated key
+ * @return SHA256 of the password
+ */
+ private static SecretKeySpec generateKey(final String password) throws NoSuchAlgorithmException, UnsupportedEncodingException {
+ final MessageDigest digest = MessageDigest.getInstance(HASH_ALGORITHM);
+ byte[] bytes = password.getBytes("UTF-8");
+ digest.update(bytes, 0, bytes.length);
+ byte[] key = digest.digest();
+
+ log("SHA-256 key ", key);
+
+ SecretKeySpec secretKeySpec = new SecretKeySpec(key, "AES");
+ return secretKeySpec;
+ }
+
+
+ /**
+ * Encrypt and encode message using 256-bit AES with key generated from password.
+ *
+ *
+ * @param password used to generated key
+ * @param message the thing you want to encrypt assumed String UTF-8
+ * @return Base64 encoded CipherText
+ * @throws GeneralSecurityException if problems occur during encryption
+ */
+ public static String encrypt(final String password, String message)
+ throws GeneralSecurityException {
+
+ try {
+ final SecretKeySpec key = generateKey(password);
+
+ log("message", message);
+
+ byte[] cipherText = encrypt(key, ivBytes, message.getBytes(CHARSET));
+
+ //NO_WRAP is important as was getting \n at the end
+ String encoded = Base64.encodeToString(cipherText, Base64.NO_WRAP);
+ log("Base64.NO_WRAP", encoded);
+ return encoded;
+ } catch (UnsupportedEncodingException e) {
+ if (DEBUG_LOG_ENABLED)
+ Log.e(TAG, "UnsupportedEncodingException ", e);
+ throw new GeneralSecurityException(e);
+ }
+ }
+
+
+ /**
+ * More flexible AES encrypt that doesn't encode
+ * @param key AES key typically 128, 192 or 256 bit
+ * @param iv Initiation Vector
+ * @param message in bytes (assumed it's already been decoded)
+ * @return Encrypted cipher text (not encoded)
+ * @throws GeneralSecurityException if something goes wrong during encryption
+ */
+ public static byte[] encrypt(final SecretKeySpec key, final byte[] iv, final byte[] message)
+ throws GeneralSecurityException {
+ final Cipher cipher = Cipher.getInstance(AES_MODE);
+ IvParameterSpec ivSpec = new IvParameterSpec(iv);
+ cipher.init(Cipher.ENCRYPT_MODE, key, ivSpec);
+ byte[] cipherText = cipher.doFinal(message);
+
+ log("cipherText", cipherText);
+
+ return cipherText;
+ }
+
+
+ /**
+ * Decrypt and decode ciphertext using 256-bit AES with key generated from password
+ *
+ * @param password used to generated key
+ * @param base64EncodedCipherText the encrpyted message encoded with base64
+ * @return message in Plain text (String UTF-8)
+ * @throws GeneralSecurityException if there's an issue decrypting
+ */
+ public static String decrypt(final String password, String base64EncodedCipherText)
+ throws GeneralSecurityException {
+
+ try {
+ final SecretKeySpec key = generateKey(password);
+
+ log("base64EncodedCipherText", base64EncodedCipherText);
+ byte[] decodedCipherText = Base64.decode(base64EncodedCipherText, Base64.NO_WRAP);
+ log("decodedCipherText", decodedCipherText);
+
+ byte[] decryptedBytes = decrypt(key, ivBytes, decodedCipherText);
+
+ log("decryptedBytes", decryptedBytes);
+ String message = new String(decryptedBytes, CHARSET);
+ log("message", message);
+
+
+ return message;
+ } catch (UnsupportedEncodingException e) {
+ if (DEBUG_LOG_ENABLED)
+ Log.e(TAG, "UnsupportedEncodingException ", e);
+
+ throw new GeneralSecurityException(e);
+ }
+ }
+
+
+ /**
+ * More flexible AES decrypt that doesn't encode
+ *
+ * @param key AES key typically 128, 192 or 256 bit
+ * @param iv Initiation Vector
+ * @param decodedCipherText in bytes (assumed it's already been decoded)
+ * @return Decrypted message cipher text (not encoded)
+ * @throws GeneralSecurityException if something goes wrong during encryption
+ */
+ public static byte[] decrypt(final SecretKeySpec key, final byte[] iv, final byte[] decodedCipherText)
+ throws GeneralSecurityException {
+ final Cipher cipher = Cipher.getInstance(AES_MODE);
+ IvParameterSpec ivSpec = new IvParameterSpec(iv);
+ cipher.init(Cipher.DECRYPT_MODE, key, ivSpec);
+ byte[] decryptedBytes = cipher.doFinal(decodedCipherText);
+
+ log("decryptedBytes", decryptedBytes);
+
+ return decryptedBytes;
+ }
+
+
+
+
+ private static void log(String what, byte[] bytes) {
+ if (DEBUG_LOG_ENABLED)
+ Log.d(TAG, what + "[" + bytes.length + "] [" + bytesToHex(bytes) + "]");
+ }
+
+ private static void log(String what, String value) {
+ if (DEBUG_LOG_ENABLED)
+ Log.d(TAG, what + "[" + value.length() + "] [" + value + "]");
+ }
+
+
+ /**
+ * Converts byte array to hexidecimal useful for logging and fault finding
+ * @param bytes
+ * @return
+ */
+ private static String bytesToHex(byte[] bytes) {
+ final char[] hexArray = {'0', '1', '2', '3', '4', '5', '6', '7', '8',
+ '9', 'A', 'B', 'C', 'D', 'E', 'F'};
+ char[] hexChars = new char[bytes.length * 2];
+ int v;
+ for (int j = 0; j < bytes.length; j++) {
+ v = bytes[j] & 0xFF;
+ hexChars[j * 2] = hexArray[v >>> 4];
+ hexChars[j * 2 + 1] = hexArray[v & 0x0F];
+ }
+ return new String(hexChars);
+ }
+
+ private AESCrypt() {
+ }
+}
diff --git a/app/src/main/java/com/mjsheng/myappstore/utils/AES/AESEncrypt.java b/app/src/main/java/com/mjsheng/myappstore/utils/AES/AESEncrypt.java
new file mode 100644
index 0000000..e741959
--- /dev/null
+++ b/app/src/main/java/com/mjsheng/myappstore/utils/AES/AESEncrypt.java
@@ -0,0 +1,141 @@
+package com.mjsheng.myappstore.utils.AES;
+
+import android.util.Base64;
+import android.util.Log;
+
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+
+import javax.crypto.Cipher;
+import javax.crypto.spec.SecretKeySpec;
+/**
+ * AES 对称加密算法,加解密工具类
+ */
+public class AESEncrypt {
+
+ private static final String TAG = AESEncrypt.class.getSimpleName() + " --> ";
+
+ /**
+ * 加密算法
+ */
+ private static final String KEY_ALGORITHM = "AES";
+
+ /**
+ * AES 的 密钥长度,32 字节,范围:16 - 32 字节
+ */
+ public static final int SECRET_KEY_LENGTH = 32;
+
+ /**
+ * 字符编码
+ */
+ private static final Charset CHARSET_UTF8 = StandardCharsets.UTF_8;
+
+ /**
+ * 秘钥长度不足 16 个字节时,默认填充位数
+ */
+ private static final String DEFAULT_VALUE = "0";
+ /**
+ * 加解密算法/工作模式/填充方式
+ */
+ private static final String CIPHER_ALGORITHM = "AES/ECB/PKCS5Padding";
+
+ /**
+ * AES 加密
+ *
+ * @param data 待加密内容
+ * @param secretKey 加密密码,长度:16 或 32 个字符
+ * @return 返回Base64转码后的加密数据
+ */
+ public static String encrypt(String data, String secretKey) {
+ try {
+ //创建密码器
+ Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM);
+ //初始化为加密密码器
+ cipher.init(Cipher.ENCRYPT_MODE, getSecretKey(secretKey));
+ byte[] encryptByte = cipher.doFinal(data.getBytes(CHARSET_UTF8));
+ // 将加密以后的数据进行 Base64 编码
+ return base64Encode(encryptByte);
+ } catch (Exception e) {
+ handleException(e);
+ }
+ return null;
+ }
+
+ /**
+ * AES 解密
+ *
+ * @param base64Data 加密的密文 Base64 字符串
+ * @param secretKey 解密的密钥,长度:16 或 32 个字符
+ */
+ public static String decrypt(String base64Data, String secretKey) {
+ try {
+ byte[] data = base64Decode(base64Data);
+ Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM);
+ //设置为解密模式
+ cipher.init(Cipher.DECRYPT_MODE, getSecretKey(secretKey));
+ //执行解密操作
+ byte[] result = cipher.doFinal(data);
+ return new String(result, CHARSET_UTF8);
+ } catch (Exception e) {
+ handleException(e);
+ }
+ return null;
+ }
+
+ /**
+ * 使用密码获取 AES 秘钥
+ */
+ public static SecretKeySpec getSecretKey(String secretKey) {
+ secretKey = toMakeKey(secretKey, SECRET_KEY_LENGTH, DEFAULT_VALUE);
+ return new SecretKeySpec(secretKey.getBytes(CHARSET_UTF8), KEY_ALGORITHM);
+ }
+
+ /**
+ * 如果 AES 的密钥小于 {@code length} 的长度,就对秘钥进行补位,保证秘钥安全。
+ *
+ * @param secretKey 密钥 key
+ * @param length 密钥应有的长度
+ * @param text 默认补的文本
+ * @return 密钥
+ */
+ private static String toMakeKey(String secretKey, int length, String text) {
+ // 获取密钥长度
+ int strLen = secretKey.length();
+ // 判断长度是否小于应有的长度
+ if (strLen < length) {
+ // 补全位数
+ StringBuilder builder = new StringBuilder();
+ // 将key添加至builder中
+ builder.append(secretKey);
+ // 遍历添加默认文本
+ for (int i = 0; i < length - strLen; i++) {
+ builder.append(text);
+ }
+ // 赋值
+ secretKey = builder.toString();
+ }
+ return secretKey;
+ }
+
+ /**
+ * 将 Base64 字符串 解码成 字节数组
+ */
+ public static byte[] base64Decode(String data) {
+ return Base64.decode(data, Base64.NO_WRAP);
+ }
+
+ /**
+ * 将 字节数组 转换成 Base64 编码
+ */
+ public static String base64Encode(byte[] data) {
+ return Base64.encodeToString(data, Base64.NO_WRAP);
+ }
+
+ /**
+ * 处理异常
+ */
+ private static void handleException(Exception e) {
+ e.printStackTrace();
+ Log.e(TAG, TAG + e);
+ }
+}
diff --git a/app/src/main/java/com/mjsheng/myappstore/utils/AES/AESUtil.java b/app/src/main/java/com/mjsheng/myappstore/utils/AES/AESUtil.java
new file mode 100644
index 0000000..35c4225
--- /dev/null
+++ b/app/src/main/java/com/mjsheng/myappstore/utils/AES/AESUtil.java
@@ -0,0 +1,170 @@
+package com.mjsheng.myappstore.utils.AES;
+
+import android.util.Base64;
+
+import javax.crypto.Cipher;
+import javax.crypto.KeyGenerator;
+import javax.crypto.SecretKey;
+import javax.crypto.spec.SecretKeySpec;
+
+import java.io.UnsupportedEncodingException;
+import java.security.SecureRandom;
+
+/**
+ * AES加密解密
+ */
+public class AESUtil {
+ private static final String default_key = "12345678";
+ static final String charset = "utf-8";
+ static final String AES = "AES";
+
+ /**
+ * 先AES加密,再Base64加密
+ *
+ * @param content
+ * @return
+ */
+ public static String encodeBase64(String content) {
+ String encode = encode(content);//AES加密
+ if (encode == null) return null;//加密失败返回空
+ try {
+ String s = Base64.encodeToString(encode.getBytes(charset), Base64.NO_WRAP);//Base64加密
+ return s;
+ } catch (UnsupportedEncodingException e) {
+ e.printStackTrace();
+ }
+ return null;
+ }
+
+ /**
+ * 先Base64解密,再AES解密
+ *
+ * @param content
+ * @return
+ */
+ public static String decodeBase64(String content) {
+ try {
+ String s = new String(Base64.decode(content, Base64.DEFAULT), charset);//Base64解密
+ String decode = decode(s);//AES解密
+ return decode;
+ } catch (UnsupportedEncodingException e) {
+ e.printStackTrace();
+ }
+ return null;//解密失败返回空
+ }
+
+ /**
+ * AES加密
+ *
+ * @param content
+ * @return
+ */
+ public static String encode(String content) {
+ return encode(default_key, content);
+ }
+
+ /**
+ * AES解密
+ *
+ * @param content
+ * @return
+ */
+ public static String decode(String content) {
+ return decode(default_key, content);
+ }
+
+ /*
+ * AES加密
+ * 1.构造密钥生成器
+ * 2.根据ecnodeRules规则初始化密钥生成器
+ * 3.产生密钥
+ * 4.创建和初始化密码器
+ * 5.内容加密
+ * 6.返回字符串
+ */
+ public static String encode(String encodeRules, String content) {
+ try {
+ //1.构造密钥生成器,指定为AES算法,不区分大小写
+ KeyGenerator keygen = KeyGenerator.getInstance(AES);
+ //2.根据ecnodeRules规则初始化密钥生成器
+ //生成一个128位的随机源,根据传入的字节数组
+// keygen.init(128, new SecureRandom(encodeRules.getBytes()));
+ SecureRandom random = SecureRandom.getInstance("SHA1PRNG");
+ random.setSeed(encodeRules.getBytes());
+ keygen.init(128, random);
+ //3.产生原始对称密钥
+ SecretKey original_key = keygen.generateKey();
+ //4.获得原始对称密钥的字节数组
+ byte[] raw = original_key.getEncoded();
+ //5.根据字节数组生成AES密钥
+ SecretKey key = new SecretKeySpec(raw, AES);
+ //6.根据指定算法AES自成密码器
+ Cipher cipher = Cipher.getInstance(AES);
+ //7.初始化密码器,第一个参数为加密(Encrypt_mode)或者解密解密(Decrypt_mode)操作,第二个参数为使用的KEY
+ cipher.init(Cipher.ENCRYPT_MODE, key);
+ //8.获取加密内容的字节数组(这里要设置为utf-8)不然内容中如果有中文和英文混合中文就会解密为乱码
+ byte[] byte_encode = content.getBytes(charset);
+ //9.根据密码器的初始化方式--加密:将数据加密
+ byte[] byte_AES = cipher.doFinal(byte_encode);
+ //10.将加密后的数据转换为字符串
+ //这里用Base64Encoder中会找不到包
+ //解决办法:
+ //在项目的Build path中先移除JRE System Library,再添加库JRE System Library,重新编译后就一切正常了。
+ String AES_encode = Base64.encodeToString(byte_AES, Base64.NO_WRAP);
+ //11.将字符串返回
+ return AES_encode;
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ return null;//加密失败返回空
+ }
+
+ /*
+ * AES解密
+ * 解密过程:
+ * 1.同加密1-4步
+ * 2.将加密后的字符串反纺成byte[]数组
+ * 3.将加密内容解密
+ */
+ public static String decode(String encodeRules, String content) {
+ try {
+ //1.构造密钥生成器,指定为AES算法,不区分大小写
+ KeyGenerator keygen = KeyGenerator.getInstance(AES);
+ //2.根据ecnodeRules规则初始化密钥生成器
+ //生成一个128位的随机源,根据传入的字节数组
+// keygen.init(128, new SecureRandom(encodeRules.getBytes()));
+ SecureRandom random = SecureRandom.getInstance("SHA1PRNG");
+ random.setSeed(encodeRules.getBytes());
+ keygen.init(128, random);
+ //3.产生原始对称密钥
+ SecretKey original_key = keygen.generateKey();
+ //4.获得原始对称密钥的字节数组
+ byte[] raw = original_key.getEncoded();
+ //5.根据字节数组生成AES密钥
+ SecretKey key = new SecretKeySpec(raw, AES);
+ //6.根据指定算法AES自成密码器
+ Cipher cipher = Cipher.getInstance(AES);
+ //7.初始化密码器,第一个参数为加密(Encrypt_mode)或者解密(Decrypt_mode)操作,第二个参数为使用的KEY
+ cipher.init(Cipher.DECRYPT_MODE, key);
+ //8.将加密并编码后的内容解码成字节数组
+ byte[] byte_content = Base64.decode(content, Base64.NO_WRAP);
+ /*
+ * 解密
+ */
+ byte[] byte_decode = cipher.doFinal(byte_content);
+ String AES_decode = new String(byte_decode, charset);
+ return AES_decode;
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ return null;//解密失败返回空
+ }
+
+ public static void main(String[] args) {
+ String a = "1236";
+ String s = encodeBase64(a);
+ System.out.println(s);
+ String s1 = decodeBase64(s);
+ System.out.println(s1);
+ }
+}
diff --git a/app/src/main/java/com/mjsheng/myappstore/utils/AES/AESUtils3.java b/app/src/main/java/com/mjsheng/myappstore/utils/AES/AESUtils3.java
new file mode 100644
index 0000000..98d5e4d
--- /dev/null
+++ b/app/src/main/java/com/mjsheng/myappstore/utils/AES/AESUtils3.java
@@ -0,0 +1,126 @@
+package com.mjsheng.myappstore.utils.AES;
+
+import java.io.UnsupportedEncodingException;
+
+import javax.crypto.Cipher;
+import javax.crypto.spec.SecretKeySpec;
+
+public class AESUtils3 {
+ /* 算法/模式/填充 */
+ private static final String CipherMode = "AES/ECB/PKCS5Padding";
+
+ /* 创建密钥 */
+ private static SecretKeySpec createKey(String password) {
+ byte[] data = null;
+ if (password == null) {
+ password = "";
+ }
+ StringBuffer sb = new StringBuffer(32);
+ sb.append(password);
+ while (sb.length() < 32) {
+ sb.append("0");
+ }
+ if (sb.length() > 32) {
+ sb.setLength(32);
+ }
+
+ try {
+ data = sb.toString().getBytes("UTF-8");
+ } catch (UnsupportedEncodingException e) {
+ e.printStackTrace();
+ }
+ return new SecretKeySpec(data, "AES");
+ }
+
+ /* 加密字节数据 */
+ public static byte[] encrypt(byte[] content, String password) {
+ try {
+ SecretKeySpec key = createKey(password);
+ System.out.println(key);
+ Cipher cipher = Cipher.getInstance(CipherMode);
+ cipher.init(Cipher.ENCRYPT_MODE, key);
+ byte[] result = cipher.doFinal(content);
+ return result;
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ return null;
+ }
+
+ /*加密(结果为16进制字符串) */
+ public static String encrypt(String content, String password) {
+ byte[] data = null;
+ try {
+ data = content.getBytes("UTF-8");
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ data = encrypt(data, password);
+ String result = byte2hex(data);
+ return result;
+ }
+
+ /*解密字节数组*/
+ public static byte[] decrypt(byte[] content, String password) {
+ try {
+ SecretKeySpec key = createKey(password);
+ Cipher cipher = Cipher.getInstance(CipherMode);
+ cipher.init(Cipher.DECRYPT_MODE, key);
+ byte[] result = cipher.doFinal(content);
+ return result;
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ return null;
+ }
+
+ /*解密16进制的字符串为字符串 */
+ public static String decrypt(String content, String password) {
+ byte[] data = null;
+ try {
+ data = hex2byte(content);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ data = decrypt(data, password);
+ if (data == null) return null;
+ String result = null;
+ try {
+ result = new String(data, "UTF-8");
+ } catch (UnsupportedEncodingException e) {
+ e.printStackTrace();
+ }
+ return result;
+ }
+
+ /*字节数组转成16进制字符串 */
+ public static String byte2hex(byte[] b) { // 一个字节的数,
+ StringBuffer sb = new StringBuffer(b.length * 2);
+ String tmp = "";
+ for (int n = 0; n < b.length; n++) {
+ // 整数转成十六进制表示
+ tmp = (java.lang.Integer.toHexString(b[n] & 0XFF));
+ if (tmp.length() == 1) {
+ sb.append("0");
+ }
+ sb.append(tmp);
+ }
+ return sb.toString().toUpperCase(); // 转成大写
+ }
+
+ /*将hex字符串转换成字节数组 */
+ private static byte[] hex2byte(String inputString) {
+ if (inputString == null || inputString.length() < 2) {
+ return new byte[0];
+ }
+ inputString = inputString.toLowerCase();
+ int l = inputString.length() / 2;
+ byte[] result = new byte[l];
+ for (int i = 0; i < l; ++i) {
+ String tmp = inputString.substring(2 * i, 2 * i + 2);
+ result[i] = (byte) (Integer.parseInt(tmp, 16) & 0xFF);
+ }
+ return result;
+ }
+}
+
diff --git a/app/src/main/java/com/mjsheng/myappstore/utils/AES/AesCbcWithIntegrity.java b/app/src/main/java/com/mjsheng/myappstore/utils/AES/AesCbcWithIntegrity.java
new file mode 100644
index 0000000..493f22d
--- /dev/null
+++ b/app/src/main/java/com/mjsheng/myappstore/utils/AES/AesCbcWithIntegrity.java
@@ -0,0 +1,941 @@
+/*
+ * Copyright (c) 2014-2015 Tozny LLC
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ * Created by Isaac Potoczny-Jones on 11/12/14.
+ */
+
+package com.mjsheng.myappstore.utils.AES;
+
+import java.io.ByteArrayOutputStream;
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.UnsupportedEncodingException;
+import java.security.GeneralSecurityException;
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+import java.security.Provider;
+import java.security.SecureRandom;
+import java.security.SecureRandomSpi;
+import java.security.Security;
+import java.security.spec.KeySpec;
+import java.util.Arrays;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import javax.crypto.Cipher;
+import javax.crypto.KeyGenerator;
+import javax.crypto.Mac;
+import javax.crypto.SecretKey;
+import javax.crypto.SecretKeyFactory;
+import javax.crypto.spec.IvParameterSpec;
+import javax.crypto.spec.PBEKeySpec;
+import javax.crypto.spec.SecretKeySpec;
+
+import android.os.Build;
+import android.os.Process;
+import android.util.Base64;
+import android.util.Log;
+
+/**
+ * Simple library for the "right" defaults for AES key generation, encryption,
+ * and decryption using 128-bit AES, CBC, PKCS5 padding, and a random 16-byte IV
+ * with SHA1PRNG. Integrity with HmacSHA256.
+ */
+public class AesCbcWithIntegrity {
+ // If the PRNG fix would not succeed for some reason, we normally will throw an exception.
+ // If ALLOW_BROKEN_PRNG is true, however, we will simply log instead.
+ private static final boolean ALLOW_BROKEN_PRNG = false;
+
+ private static final String CIPHER_TRANSFORMATION = "AES/CBC/PKCS5Padding";
+ private static final String CIPHER = "AES";
+ private static final int AES_KEY_LENGTH_BITS = 128;
+ private static final int IV_LENGTH_BYTES = 16;
+ private static final int PBE_ITERATION_COUNT = 10000;
+ private static final int PBE_SALT_LENGTH_BITS = AES_KEY_LENGTH_BITS; // same size as key output
+ private static final String PBE_ALGORITHM = "PBKDF2WithHmacSHA1";
+
+ //Made BASE_64_FLAGS public as it's useful to know for compatibility.
+ public static final int BASE64_FLAGS = Base64.NO_WRAP;
+ //default for testing
+ static final AtomicBoolean prngFixed = new AtomicBoolean(false);
+
+ private static final String HMAC_ALGORITHM = "HmacSHA256";
+ private static final int HMAC_KEY_LENGTH_BITS = 256;
+
+ /**
+ * Converts the given AES/HMAC keys into a base64 encoded string suitable for
+ * storage. Sister function of keys.
+ *
+ * @param keys The combined aes and hmac keys
+ * @return a base 64 encoded AES string and hmac key as base64(aesKey) : base64(hmacKey)
+ */
+ public static String keyString(SecretKeys keys) {
+ return keys.toString();
+ }
+
+ /**
+ * An aes key derived from a base64 encoded key. This does not generate the
+ * key. It's not random or a PBE key.
+ *
+ * @param keysStr a base64 encoded AES key / hmac key as base64(aesKey) : base64(hmacKey).
+ * @return an AES and HMAC key set suitable for other functions.
+ */
+ public static SecretKeys keys(String keysStr) throws InvalidKeyException {
+ String[] keysArr = keysStr.split(":");
+
+ if (keysArr.length != 2) {
+ throw new IllegalArgumentException("Cannot parse aesKey:hmacKey");
+
+ } else {
+ byte[] confidentialityKey = Base64.decode(keysArr[0], BASE64_FLAGS);
+ if (confidentialityKey.length != AES_KEY_LENGTH_BITS /8) {
+ throw new InvalidKeyException("Base64 decoded key is not " + AES_KEY_LENGTH_BITS + " bytes");
+ }
+ byte[] integrityKey = Base64.decode(keysArr[1], BASE64_FLAGS);
+ if (integrityKey.length != HMAC_KEY_LENGTH_BITS /8) {
+ throw new InvalidKeyException("Base64 decoded key is not " + HMAC_KEY_LENGTH_BITS + " bytes");
+ }
+
+ return new SecretKeys(
+ new SecretKeySpec(confidentialityKey, 0, confidentialityKey.length, CIPHER),
+ new SecretKeySpec(integrityKey, HMAC_ALGORITHM));
+ }
+ }
+
+ /**
+ * A function that generates random AES and HMAC keys and prints out exceptions but
+ * doesn't throw them since none should be encountered. If they are
+ * encountered, the return value is null.
+ *
+ * @return The AES and HMAC keys.
+ * @throws GeneralSecurityException if AES is not implemented on this system,
+ * or a suitable RNG is not available
+ */
+ public static SecretKeys generateKey() throws GeneralSecurityException {
+ fixPrng();
+ KeyGenerator keyGen = KeyGenerator.getInstance(CIPHER);
+ // No need to provide a SecureRandom or set a seed since that will
+ // happen automatically.
+ keyGen.init(AES_KEY_LENGTH_BITS);
+ SecretKey confidentialityKey = keyGen.generateKey();
+
+ //Now make the HMAC key
+ byte[] integrityKeyBytes = randomBytes(HMAC_KEY_LENGTH_BITS / 8);//to get bytes
+ SecretKey integrityKey = new SecretKeySpec(integrityKeyBytes, HMAC_ALGORITHM);
+
+ return new SecretKeys(confidentialityKey, integrityKey);
+ }
+
+ /**
+ * A function that generates password-based AES and HMAC keys. It prints out exceptions but
+ * doesn't throw them since none should be encountered. If they are
+ * encountered, the return value is null.
+ *
+ * @param password The password to derive the keys from.
+ * @return The AES and HMAC keys.
+ * @throws GeneralSecurityException if AES is not implemented on this system,
+ * or a suitable RNG is not available
+ */
+ public static SecretKeys generateKeyFromPassword(String password, byte[] salt) throws GeneralSecurityException {
+ fixPrng();
+ //Get enough random bytes for both the AES key and the HMAC key:
+ KeySpec keySpec = new PBEKeySpec(password.toCharArray(), salt,
+ PBE_ITERATION_COUNT, AES_KEY_LENGTH_BITS + HMAC_KEY_LENGTH_BITS);
+ SecretKeyFactory keyFactory = SecretKeyFactory
+ .getInstance(PBE_ALGORITHM);
+ byte[] keyBytes = keyFactory.generateSecret(keySpec).getEncoded();
+
+ // Split the random bytes into two parts:
+ byte[] confidentialityKeyBytes = copyOfRange(keyBytes, 0, AES_KEY_LENGTH_BITS /8);
+ byte[] integrityKeyBytes = copyOfRange(keyBytes, AES_KEY_LENGTH_BITS /8, AES_KEY_LENGTH_BITS /8 + HMAC_KEY_LENGTH_BITS /8);
+
+ //Generate the AES key
+ SecretKey confidentialityKey = new SecretKeySpec(confidentialityKeyBytes, CIPHER);
+
+ //Generate the HMAC key
+ SecretKey integrityKey = new SecretKeySpec(integrityKeyBytes, HMAC_ALGORITHM);
+
+ return new SecretKeys(confidentialityKey, integrityKey);
+ }
+
+ /**
+ * A function that generates password-based AES and HMAC keys. See generateKeyFromPassword.
+ * @param password The password to derive the AES/HMAC keys from
+ * @param salt A string version of the salt; base64 encoded.
+ * @return The AES and HMAC keys.
+ * @throws GeneralSecurityException
+ */
+ public static SecretKeys generateKeyFromPassword(String password, String salt) throws GeneralSecurityException {
+ return generateKeyFromPassword(password, Base64.decode(salt, BASE64_FLAGS));
+ }
+
+ /**
+ * Generates a random salt.
+ * @return The random salt suitable for generateKeyFromPassword.
+ */
+ public static byte[] generateSalt() throws GeneralSecurityException {
+ return randomBytes(PBE_SALT_LENGTH_BITS);
+ }
+
+ /**
+ * Converts the given salt into a base64 encoded string suitable for
+ * storage.
+ *
+ * @param salt
+ * @return a base 64 encoded salt string suitable to pass into generateKeyFromPassword.
+ */
+ public static String saltString(byte[] salt) {
+ return Base64.encodeToString(salt, BASE64_FLAGS);
+ }
+
+
+ /**
+ * Creates a random Initialization Vector (IV) of IV_LENGTH_BYTES.
+ *
+ * @return The byte array of this IV
+ * @throws GeneralSecurityException if a suitable RNG is not available
+ */
+ public static byte[] generateIv() throws GeneralSecurityException {
+ return randomBytes(IV_LENGTH_BYTES);
+ }
+
+ private static byte[] randomBytes(int length) throws GeneralSecurityException {
+ fixPrng();
+ SecureRandom random = new SecureRandom();
+ byte[] b = new byte[length];
+ random.nextBytes(b);
+ return b;
+ }
+
+ /*
+ * -----------------------------------------------------------------
+ * Encryption
+ * -----------------------------------------------------------------
+ */
+
+ /**
+ * Generates a random IV and encrypts this plain text with the given key. Then attaches
+ * a hashed MAC, which is contained in the CipherTextIvMac class.
+ *
+ * @param plaintext The text that will be encrypted, which
+ * will be serialized with UTF-8
+ * @param secretKeys The AES and HMAC keys with which to encrypt
+ * @return a tuple of the IV, ciphertext, mac
+ * @throws GeneralSecurityException if AES is not implemented on this system
+ * @throws UnsupportedEncodingException if UTF-8 is not supported in this system
+ */
+ public static CipherTextIvMac encrypt(String plaintext, SecretKeys secretKeys)
+ throws UnsupportedEncodingException, GeneralSecurityException {
+ return encrypt(plaintext, secretKeys, "UTF-8");
+ }
+
+ /**
+ * Generates a random IV and encrypts this plain text with the given key. Then attaches
+ * a hashed MAC, which is contained in the CipherTextIvMac class.
+ *
+ * @param plaintext The bytes that will be encrypted
+ * @param secretKeys The AES and HMAC keys with which to encrypt
+ * @return a tuple of the IV, ciphertext, mac
+ * @throws GeneralSecurityException if AES is not implemented on this system
+ * @throws UnsupportedEncodingException if the specified encoding is invalid
+ */
+ public static CipherTextIvMac encrypt(String plaintext, SecretKeys secretKeys, String encoding)
+ throws UnsupportedEncodingException, GeneralSecurityException {
+ return encrypt(plaintext.getBytes(encoding), secretKeys);
+ }
+
+ /**
+ * Generates a random IV and encrypts this plain text with the given key. Then attaches
+ * a hashed MAC, which is contained in the CipherTextIvMac class.
+ *
+ * @param plaintext The text that will be encrypted
+ * @param secretKeys The combined AES and HMAC keys with which to encrypt
+ * @return a tuple of the IV, ciphertext, mac
+ * @throws GeneralSecurityException if AES is not implemented on this system
+ */
+ public static CipherTextIvMac encrypt(byte[] plaintext, SecretKeys secretKeys)
+ throws GeneralSecurityException {
+ byte[] iv = generateIv();
+ Cipher aesCipherForEncryption = Cipher.getInstance(CIPHER_TRANSFORMATION);
+ aesCipherForEncryption.init(Cipher.ENCRYPT_MODE, secretKeys.getConfidentialityKey(), new IvParameterSpec(iv));
+
+ /*
+ * Now we get back the IV that will actually be used. Some Android
+ * versions do funny stuff w/ the IV, so this is to work around bugs:
+ */
+ iv = aesCipherForEncryption.getIV();
+ byte[] byteCipherText = aesCipherForEncryption.doFinal(plaintext);
+ byte[] ivCipherConcat = CipherTextIvMac.ivCipherConcat(iv, byteCipherText);
+
+ byte[] integrityMac = generateMac(ivCipherConcat, secretKeys.getIntegrityKey());
+ return new CipherTextIvMac(byteCipherText, iv, integrityMac);
+ }
+
+ /**
+ * Ensures that the PRNG is fixed. Should be used before generating any keys.
+ * Will only run once, and every subsequent call should return immediately.
+ */
+ private static void fixPrng() {
+ if (!prngFixed.get()) {
+ synchronized (PrngFixes.class) {
+ if (!prngFixed.get()) {
+ PrngFixes.apply();
+ prngFixed.set(true);
+ }
+ }
+ }
+ }
+
+ /*
+ * -----------------------------------------------------------------
+ * Decryption
+ * -----------------------------------------------------------------
+ */
+
+ /**
+ * AES CBC decrypt.
+ *
+ * @param civ The cipher text, IV, and mac
+ * @param secretKeys The AES and HMAC keys
+ * @param encoding The string encoding to use to decode the bytes after decryption
+ * @return A string derived from the decrypted bytes (not base64 encoded)
+ * @throws GeneralSecurityException if AES is not implemented on this system
+ * @throws UnsupportedEncodingException if the encoding is unsupported
+ */
+ public static String decryptString(CipherTextIvMac civ, SecretKeys secretKeys, String encoding)
+ throws UnsupportedEncodingException, GeneralSecurityException {
+ return new String(decrypt(civ, secretKeys), encoding);
+ }
+
+ /**
+ * AES CBC decrypt.
+ *
+ * @param civ The cipher text, IV, and mac
+ * @param secretKeys The AES and HMAC keys
+ * @return A string derived from the decrypted bytes, which are interpreted
+ * as a UTF-8 String
+ * @throws GeneralSecurityException if AES is not implemented on this system
+ * @throws UnsupportedEncodingException if UTF-8 is not supported
+ */
+ public static String decryptString(CipherTextIvMac civ, SecretKeys secretKeys)
+ throws UnsupportedEncodingException, GeneralSecurityException {
+ return decryptString(civ, secretKeys, "UTF-8");
+ }
+
+ /**
+ * AES CBC decrypt.
+ *
+ * @param civ the cipher text, iv, and mac
+ * @param secretKeys the AES and HMAC keys
+ * @return The raw decrypted bytes
+ * @throws GeneralSecurityException if MACs don't match or AES is not implemented
+ */
+ public static byte[] decrypt(CipherTextIvMac civ, SecretKeys secretKeys)
+ throws GeneralSecurityException {
+
+ byte[] ivCipherConcat = CipherTextIvMac.ivCipherConcat(civ.getIv(), civ.getCipherText());
+ byte[] computedMac = generateMac(ivCipherConcat, secretKeys.getIntegrityKey());
+ if (constantTimeEq(computedMac, civ.getMac())) {
+ Cipher aesCipherForDecryption = Cipher.getInstance(CIPHER_TRANSFORMATION);
+ aesCipherForDecryption.init(Cipher.DECRYPT_MODE, secretKeys.getConfidentialityKey(),
+ new IvParameterSpec(civ.getIv()));
+ return aesCipherForDecryption.doFinal(civ.getCipherText());
+ } else {
+ throw new GeneralSecurityException("MAC stored in civ does not match computed MAC.");
+ }
+ }
+
+ /*
+ * -----------------------------------------------------------------
+ * Helper Code
+ * -----------------------------------------------------------------
+ */
+
+ /**
+ * Generate the mac based on HMAC_ALGORITHM
+ * @param integrityKey The key used for hmac
+ * @param byteCipherText the cipher text
+ * @return A byte array of the HMAC for the given key and ciphertext
+ * @throws NoSuchAlgorithmException
+ * @throws InvalidKeyException
+ */
+ public static byte[] generateMac(byte[] byteCipherText, SecretKey integrityKey) throws NoSuchAlgorithmException, InvalidKeyException {
+ //Now compute the mac for later integrity checking
+ Mac sha256_HMAC = Mac.getInstance(HMAC_ALGORITHM);
+ sha256_HMAC.init(integrityKey);
+ return sha256_HMAC.doFinal(byteCipherText);
+ }
+ /**
+ * Holder class that has both the secret AES key for encryption (confidentiality)
+ * and the secret HMAC key for integrity.
+ */
+
+ public static class SecretKeys {
+ private SecretKey confidentialityKey;
+ private SecretKey integrityKey;
+
+ /**
+ * Construct the secret keys container.
+ * @param confidentialityKeyIn The AES key
+ * @param integrityKeyIn the HMAC key
+ */
+ public SecretKeys(SecretKey confidentialityKeyIn, SecretKey integrityKeyIn) {
+ setConfidentialityKey(confidentialityKeyIn);
+ setIntegrityKey(integrityKeyIn);
+ }
+
+ public SecretKey getConfidentialityKey() {
+ return confidentialityKey;
+ }
+
+ public void setConfidentialityKey(SecretKey confidentialityKey) {
+ this.confidentialityKey = confidentialityKey;
+ }
+
+ public SecretKey getIntegrityKey() {
+ return integrityKey;
+ }
+
+ public void setIntegrityKey(SecretKey integrityKey) {
+ this.integrityKey = integrityKey;
+ }
+
+ /**
+ * Encodes the two keys as a string
+ * @return base64(confidentialityKey):base64(integrityKey)
+ */
+ @Override
+ public String toString () {
+ return Base64.encodeToString(getConfidentialityKey().getEncoded(), BASE64_FLAGS)
+ + ":" + Base64.encodeToString(getIntegrityKey().getEncoded(), BASE64_FLAGS);
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + confidentialityKey.hashCode();
+ result = prime * result + integrityKey.hashCode();
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (obj == null)
+ return false;
+ if (getClass() != obj.getClass())
+ return false;
+ SecretKeys other = (SecretKeys) obj;
+ if (!integrityKey.equals(other.integrityKey))
+ return false;
+ if (!confidentialityKey.equals(other.confidentialityKey))
+ return false;
+ return true;
+ }
+ }
+
+
+ /**
+ * Simple constant-time equality of two byte arrays. Used for security to avoid timing attacks.
+ * @param a
+ * @param b
+ * @return true iff the arrays are exactly equal.
+ */
+ public static boolean constantTimeEq(byte[] a, byte[] b) {
+ if (a.length != b.length) {
+ return false;
+ }
+ int result = 0;
+ for (int i = 0; i < a.length; i++) {
+ result |= a[i] ^ b[i];
+ }
+ return result == 0;
+ }
+
+ /**
+ * Holder class that allows us to bundle ciphertext and IV together.
+ */
+ public static class CipherTextIvMac {
+ private final byte[] cipherText;
+ private final byte[] iv;
+ private final byte[] mac;
+
+ public byte[] getCipherText() {
+ return cipherText;
+ }
+
+ public byte[] getIv() {
+ return iv;
+ }
+
+ public byte[] getMac() {
+ return mac;
+ }
+
+ /**
+ * Construct a new bundle of ciphertext and IV.
+ * @param c The ciphertext
+ * @param i The IV
+ * @param h The mac
+ */
+ public CipherTextIvMac(byte[] c, byte[] i, byte[] h) {
+ cipherText = new byte[c.length];
+ System.arraycopy(c, 0, cipherText, 0, c.length);
+ iv = new byte[i.length];
+ System.arraycopy(i, 0, iv, 0, i.length);
+ mac = new byte[h.length];
+ System.arraycopy(h, 0, mac, 0, h.length);
+ }
+
+ /**
+ * Constructs a new bundle of ciphertext and IV from a string of the
+ * format base64(iv):base64(ciphertext).
+ *
+ * @param base64IvAndCiphertext A string of the format
+ * iv:ciphertext The IV and ciphertext must each
+ * be base64-encoded.
+ */
+ public CipherTextIvMac(String base64IvAndCiphertext) {
+ String[] civArray = base64IvAndCiphertext.split(":");
+ if (civArray.length != 3) {
+ throw new IllegalArgumentException("Cannot parse iv:mac:ciphertext");
+ } else {
+ iv = Base64.decode(civArray[0], BASE64_FLAGS);
+ mac = Base64.decode(civArray[1], BASE64_FLAGS);
+ cipherText = Base64.decode(civArray[2], BASE64_FLAGS);
+ }
+ }
+
+ /**
+ * Concatinate the IV to the cipherText using array copy.
+ * This is used e.g. before computing mac.
+ * @param iv The IV to prepend
+ * @param cipherText the cipherText to append
+ * @return iv:cipherText, a new byte array.
+ */
+ public static byte[] ivCipherConcat(byte[] iv, byte[] cipherText) {
+ byte[] combined = new byte[iv.length + cipherText.length];
+ System.arraycopy(iv, 0, combined, 0, iv.length);
+ System.arraycopy(cipherText, 0, combined, iv.length, cipherText.length);
+ return combined;
+ }
+
+ /**
+ * Encodes this ciphertext, IV, mac as a string.
+ *
+ * @return base64(iv) : base64(mac) : base64(ciphertext).
+ * The iv and mac go first because they're fixed length.
+ */
+ @Override
+ public String toString() {
+ String ivString = Base64.encodeToString(iv, BASE64_FLAGS);
+ String cipherTextString = Base64.encodeToString(cipherText, BASE64_FLAGS);
+ String macString = Base64.encodeToString(mac, BASE64_FLAGS);
+ return String.format(ivString + ":" + macString + ":" + cipherTextString);
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + Arrays.hashCode(cipherText);
+ result = prime * result + Arrays.hashCode(iv);
+ result = prime * result + Arrays.hashCode(mac);
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (obj == null)
+ return false;
+ if (getClass() != obj.getClass())
+ return false;
+ CipherTextIvMac other = (CipherTextIvMac) obj;
+ if (!Arrays.equals(cipherText, other.cipherText))
+ return false;
+ if (!Arrays.equals(iv, other.iv))
+ return false;
+ if (!Arrays.equals(mac, other.mac))
+ return false;
+ return true;
+ }
+ }
+
+ /**
+ * Copy the elements from the start to the end
+ *
+ * @param from the source
+ * @param start the start index to copy
+ * @param end the end index to finish
+ * @return the new buffer
+ */
+ private static byte[] copyOfRange(byte[] from, int start, int end) {
+ int length = end - start;
+ byte[] result = new byte[length];
+ System.arraycopy(from, start, result, 0, length);
+ return result;
+ }
+
+ /**
+ * Fixes for the RNG as per
+ * http://android-developers.blogspot.com/2013/08/some-securerandom-thoughts.html
+ *
+ * This software is provided 'as-is', without any express or implied
+ * warranty. In no event will Google be held liable for any damages arising
+ * from the use of this software.
+ *
+ * Permission is granted to anyone to use this software for any purpose,
+ * including commercial applications, and to alter it and redistribute it
+ * freely, as long as the origin is not misrepresented.
+ *
+ * Fixes for the output of the default PRNG having low entropy.
+ *
+ * The fixes need to be applied via {@link #apply()} before any use of Java
+ * Cryptography Architecture primitives. A good place to invoke them is in
+ * the application's {@code onCreate}.
+ */
+ public static final class PrngFixes {
+
+ private static final int VERSION_CODE_JELLY_BEAN = 16;
+ private static final int VERSION_CODE_JELLY_BEAN_MR2 = 18;
+ private static final byte[] BUILD_FINGERPRINT_AND_DEVICE_SERIAL = getBuildFingerprintAndDeviceSerial();
+
+ /** Hidden constructor to prevent instantiation. */
+ private PrngFixes() {
+ }
+
+ /**
+ * Applies all fixes.
+ *
+ * @throws SecurityException if a fix is needed but could not be
+ * applied.
+ */
+ public static void apply() {
+ applyOpenSSLFix();
+ installLinuxPRNGSecureRandom();
+ }
+
+ /**
+ * Applies the fix for OpenSSL PRNG having low entropy. Does nothing if
+ * the fix is not needed.
+ *
+ * @throws SecurityException if the fix is needed but could not be
+ * applied.
+ */
+ private static void applyOpenSSLFix() throws SecurityException {
+ if ((Build.VERSION.SDK_INT < VERSION_CODE_JELLY_BEAN)
+ || (Build.VERSION.SDK_INT > VERSION_CODE_JELLY_BEAN_MR2)) {
+ // No need to apply the fix
+ return;
+ }
+
+ try {
+ // Mix in the device- and invocation-specific seed.
+ Class.forName("org.apache.harmony.xnet.provider.jsse.NativeCrypto")
+ .getMethod("RAND_seed", byte[].class).invoke(null, generateSeed());
+
+ // Mix output of Linux PRNG into OpenSSL's PRNG
+ int bytesRead = (Integer) Class
+ .forName("org.apache.harmony.xnet.provider.jsse.NativeCrypto")
+ .getMethod("RAND_load_file", String.class, long.class)
+ .invoke(null, "/dev/urandom", 1024);
+ if (bytesRead != 1024) {
+ throw new IOException("Unexpected number of bytes read from Linux PRNG: "
+ + bytesRead);
+ }
+ } catch (Exception e) {
+ if (ALLOW_BROKEN_PRNG) {
+ Log.w(PrngFixes.class.getSimpleName(), "Failed to seed OpenSSL PRNG", e);
+ } else {
+ throw new SecurityException("Failed to seed OpenSSL PRNG", e);
+ }
+ }
+ }
+
+ /**
+ * Installs a Linux PRNG-backed {@code SecureRandom} implementation as
+ * the default. Does nothing if the implementation is already the
+ * default or if there is not need to install the implementation.
+ *
+ * @throws SecurityException if the fix is needed but could not be
+ * applied.
+ */
+ private static void installLinuxPRNGSecureRandom() throws SecurityException {
+ if (Build.VERSION.SDK_INT > VERSION_CODE_JELLY_BEAN_MR2) {
+ // No need to apply the fix
+ return;
+ }
+
+ // Install a Linux PRNG-based SecureRandom implementation as the
+ // default, if not yet installed.
+ Provider[] secureRandomProviders = Security.getProviders("SecureRandom.SHA1PRNG");
+
+ // Insert and check the provider atomically.
+ // The official Android Java libraries use synchronized methods for
+ // insertProviderAt, etc., so synchronizing on the class should
+ // make things more stable, and prevent race conditions with other
+ // versions of this code.
+ synchronized (Security.class) {
+ if ((secureRandomProviders == null)
+ || (secureRandomProviders.length < 1)
+ || (!secureRandomProviders[0].getClass().getSimpleName().equals("LinuxPRNGSecureRandomProvider"))) {
+ Security.insertProviderAt(new LinuxPRNGSecureRandomProvider(), 1);
+ }
+
+ // Assert that new SecureRandom() and
+ // SecureRandom.getInstance("SHA1PRNG") return a SecureRandom backed
+ // by the Linux PRNG-based SecureRandom implementation.
+ SecureRandom rng1 = new SecureRandom();
+ if (!rng1.getProvider().getClass().getSimpleName().equals("LinuxPRNGSecureRandomProvider")) {
+ if (ALLOW_BROKEN_PRNG) {
+ Log.w(PrngFixes.class.getSimpleName(),
+ "new SecureRandom() backed by wrong Provider: " + rng1.getProvider().getClass());
+ return;
+ } else {
+ throw new SecurityException("new SecureRandom() backed by wrong Provider: "
+ + rng1.getProvider().getClass());
+ }
+ }
+
+ SecureRandom rng2 = null;
+ try {
+ rng2 = SecureRandom.getInstance("SHA1PRNG");
+ } catch (NoSuchAlgorithmException e) {
+ if (ALLOW_BROKEN_PRNG) {
+ Log.w(PrngFixes.class.getSimpleName(), "SHA1PRNG not available", e);
+ return;
+ } else {
+ new SecurityException("SHA1PRNG not available", e);
+ }
+ }
+ if (!rng2.getProvider().getClass().getSimpleName().equals("LinuxPRNGSecureRandomProvider")) {
+ if (ALLOW_BROKEN_PRNG) {
+ Log.w(PrngFixes.class.getSimpleName(),
+ "SecureRandom.getInstance(\"SHA1PRNG\") backed by wrong" + " Provider: "
+ + rng2.getProvider().getClass());
+ return;
+ } else {
+ throw new SecurityException(
+ "SecureRandom.getInstance(\"SHA1PRNG\") backed by wrong" + " Provider: "
+ + rng2.getProvider().getClass());
+ }
+ }
+ }
+ }
+
+ /**
+ * {@code Provider} of {@code SecureRandom} engines which pass through
+ * all requests to the Linux PRNG.
+ */
+ private static class LinuxPRNGSecureRandomProvider extends Provider {
+
+ public LinuxPRNGSecureRandomProvider() {
+ super("LinuxPRNG", 1.0, "A Linux-specific random number provider that uses"
+ + " /dev/urandom");
+ // Although /dev/urandom is not a SHA-1 PRNG, some apps
+ // explicitly request a SHA1PRNG SecureRandom and we thus need
+ // to prevent them from getting the default implementation whose
+ // output may have low entropy.
+ put("SecureRandom.SHA1PRNG", LinuxPRNGSecureRandom.class.getName());
+ put("SecureRandom.SHA1PRNG ImplementedIn", "Software");
+ }
+ }
+
+ /**
+ * {@link SecureRandomSpi} which passes all requests to the Linux PRNG (
+ * {@code /dev/urandom}).
+ */
+ public static class LinuxPRNGSecureRandom extends SecureRandomSpi {
+
+ /*
+ * IMPLEMENTATION NOTE: Requests to generate bytes and to mix in a
+ * seed are passed through to the Linux PRNG (/dev/urandom).
+ * Instances of this class seed themselves by mixing in the current
+ * time, PID, UID, build fingerprint, and hardware serial number
+ * (where available) into Linux PRNG.
+ *
+ * Concurrency: Read requests to the underlying Linux PRNG are
+ * serialized (on sLock) to ensure that multiple threads do not get
+ * duplicated PRNG output.
+ */
+
+ private static final File URANDOM_FILE = new File("/dev/urandom");
+
+ private static final Object sLock = new Object();
+
+ /**
+ * Input stream for reading from Linux PRNG or {@code null} if not
+ * yet opened.
+ *
+ * @GuardedBy("sLock")
+ */
+ private static DataInputStream sUrandomIn;
+
+ /**
+ * Output stream for writing to Linux PRNG or {@code null} if not
+ * yet opened.
+ *
+ * @GuardedBy("sLock")
+ */
+ private static OutputStream sUrandomOut;
+
+ /**
+ * Whether this engine instance has been seeded. This is needed
+ * because each instance needs to seed itself if the client does not
+ * explicitly seed it.
+ */
+ private boolean mSeeded;
+
+ @Override
+ protected void engineSetSeed(byte[] bytes) {
+ try {
+ OutputStream out;
+ synchronized (sLock) {
+ out = getUrandomOutputStream();
+ }
+ out.write(bytes);
+ out.flush();
+ } catch (IOException e) {
+ // On a small fraction of devices /dev/urandom is not
+ // writable Log and ignore.
+ Log.w(PrngFixes.class.getSimpleName(), "Failed to mix seed into "
+ + URANDOM_FILE);
+ } finally {
+ mSeeded = true;
+ }
+ }
+
+ @Override
+ protected void engineNextBytes(byte[] bytes) {
+ if (!mSeeded) {
+ // Mix in the device- and invocation-specific seed.
+ engineSetSeed(generateSeed());
+ }
+
+ try {
+ DataInputStream in;
+ synchronized (sLock) {
+ in = getUrandomInputStream();
+ }
+ synchronized (in) {
+ in.readFully(bytes);
+ }
+ } catch (IOException e) {
+ throw new SecurityException("Failed to read from " + URANDOM_FILE, e);
+ }
+ }
+
+ @Override
+ protected byte[] engineGenerateSeed(int size) {
+ byte[] seed = new byte[size];
+ engineNextBytes(seed);
+ return seed;
+ }
+
+ private DataInputStream getUrandomInputStream() {
+ synchronized (sLock) {
+ if (sUrandomIn == null) {
+ // NOTE: Consider inserting a BufferedInputStream
+ // between DataInputStream and FileInputStream if you need
+ // higher PRNG output performance and can live with future PRNG
+ // output being pulled into this process prematurely.
+ try {
+ sUrandomIn = new DataInputStream(new FileInputStream(URANDOM_FILE));
+ } catch (IOException e) {
+ throw new SecurityException("Failed to open " + URANDOM_FILE
+ + " for reading", e);
+ }
+ }
+ return sUrandomIn;
+ }
+ }
+
+ private OutputStream getUrandomOutputStream() throws IOException {
+ synchronized (sLock) {
+ if (sUrandomOut == null) {
+ sUrandomOut = new FileOutputStream(URANDOM_FILE);
+ }
+ return sUrandomOut;
+ }
+ }
+ }
+
+ /**
+ * Generates a device- and invocation-specific seed to be mixed into the
+ * Linux PRNG.
+ */
+ private static byte[] generateSeed() {
+ try {
+ ByteArrayOutputStream seedBuffer = new ByteArrayOutputStream();
+ DataOutputStream seedBufferOut = new DataOutputStream(seedBuffer);
+ seedBufferOut.writeLong(System.currentTimeMillis());
+ seedBufferOut.writeLong(System.nanoTime());
+ seedBufferOut.writeInt(Process.myPid());
+ seedBufferOut.writeInt(Process.myUid());
+ seedBufferOut.write(BUILD_FINGERPRINT_AND_DEVICE_SERIAL);
+ seedBufferOut.close();
+ return seedBuffer.toByteArray();
+ } catch (IOException e) {
+ throw new SecurityException("Failed to generate seed", e);
+ }
+ }
+
+ /**
+ * Gets the hardware serial number of this device.
+ *
+ * @return serial number or {@code null} if not available.
+ */
+ private static String getDeviceSerialNumber() {
+ // We're using the Reflection API because Build.SERIAL is only
+ // available since API Level 9 (Gingerbread, Android 2.3).
+ try {
+ return (String) Build.class.getField("SERIAL").get(null);
+ } catch (Exception ignored) {
+ return null;
+ }
+ }
+
+ private static byte[] getBuildFingerprintAndDeviceSerial() {
+ StringBuilder result = new StringBuilder();
+ String fingerprint = Build.FINGERPRINT;
+ if (fingerprint != null) {
+ result.append(fingerprint);
+ }
+ String serial = getDeviceSerialNumber();
+ if (serial != null) {
+ result.append(serial);
+ }
+ try {
+ return result.toString().getBytes("UTF-8");
+ } catch (UnsupportedEncodingException e) {
+ throw new RuntimeException("UTF-8 encoding not supported");
+ }
+ }
+ }
+}
diff --git a/app/src/main/java/com/mjsheng/myappstore/utils/AES/CXAESUtil.java b/app/src/main/java/com/mjsheng/myappstore/utils/AES/CXAESUtil.java
new file mode 100644
index 0000000..abc9f60
--- /dev/null
+++ b/app/src/main/java/com/mjsheng/myappstore/utils/AES/CXAESUtil.java
@@ -0,0 +1,327 @@
+package com.mjsheng.myappstore.utils.AES;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+
+import javax.crypto.Cipher;
+import javax.crypto.CipherInputStream;
+import javax.crypto.CipherOutputStream;
+import javax.crypto.NoSuchPaddingException;
+import javax.crypto.spec.IvParameterSpec;
+import javax.crypto.spec.SecretKeySpec;
+
+public class CXAESUtil {
+ /**
+ * 加解密算法/工作模式/填充方式
+ */
+ private static final String CIPHER_ALGORITHM = "AES/ECB/PKCS5Padding";
+ private final static String HEX = "0123456789ABCDEF";
+ private static final int keyLenght = 16;
+ private static final String defaultV = "0";
+
+ /**
+ * 加密
+ *
+ * @param key
+ * 密钥
+ * @param src
+ * 加密文本
+ * @return
+ * @throws Exception
+ */
+ public static String encrypt(String key, String src) throws Exception {
+ // /src = Base64.encodeToString(src.getBytes(), Base64.DEFAULT);
+ byte[] rawKey = toMakekey(key, keyLenght, defaultV).getBytes();// key.getBytes();
+ byte[] result = encrypt(rawKey, src.getBytes("utf-8"));
+ // result = Base64.encode(result, Base64.DEFAULT);
+ return toHex(result);
+ }
+
+ /**
+ * 加密
+ *
+ * @param key
+ * 密钥
+ * @param src
+ * 加密文本
+ * @return
+ * @throws Exception
+ */
+ public static String encrypt2Java(String key, String src) throws Exception {
+ // /src = Base64.encodeToString(src.getBytes(), Base64.DEFAULT);
+ byte[] rawKey = toMakekey(key, keyLenght, defaultV).getBytes();// key.getBytes();
+ byte[] result = encrypt2Java(rawKey, src.getBytes("utf-8"));
+ // result = Base64.encode(result, Base64.DEFAULT);
+ return toHex(result);
+ }
+
+ /**
+ * 解密
+ *
+ * @param key
+ * 密钥
+ * @param encrypted
+ * 待揭秘文本
+ * @return
+ * @throws Exception
+ */
+ public static String decrypt(String key, String encrypted) throws Exception {
+ byte[] rawKey = toMakekey(key, keyLenght, defaultV).getBytes();// key.getBytes();
+ byte[] enc = toByte(encrypted);
+ // enc = Base64.decode(enc, Base64.DEFAULT);
+ byte[] result = decrypt(rawKey, enc);
+ // /result = Base64.decode(result, Base64.DEFAULT);
+ return new String(result, "utf-8");
+ }
+
+ /**
+ * 密钥key ,默认补的数字,补全16位数,以保证安全补全至少16位长度,android和ios对接通过
+ * @param str
+ * @param strLength
+ * @param val
+ * @return
+ */
+ private static String toMakekey(String str, int strLength, String val) {
+
+ int strLen = str.length();
+ if (strLen < strLength) {
+ while (strLen < strLength) {
+ StringBuffer buffer = new StringBuffer();
+ buffer.append(str).append(val);
+ str = buffer.toString();
+ strLen = str.length();
+ }
+ }
+ return str;
+ }
+
+ /**
+ * 真正的加密过程
+ * 1.通过密钥得到一个密钥专用的对象SecretKeySpec
+ * 2.Cipher 加密算法,加密模式和填充方式三部分或指定加密算 (可以只用写算法然后用默认的其他方式)Cipher.getInstance("AES");
+ * @param key
+ * @param src
+ * @return
+ * @throws Exception
+ */
+ private static byte[] encrypt(byte[] key, byte[] src) throws Exception {
+ SecretKeySpec skeySpec = new SecretKeySpec(key, "AES");
+ Cipher cipher = Cipher.getInstance("AES");
+ cipher.init(Cipher.ENCRYPT_MODE, skeySpec, new IvParameterSpec(new byte[cipher.getBlockSize()]));
+ byte[] encrypted = cipher.doFinal(src);
+ return encrypted;
+ }
+
+ /**
+ * 真正的加密过程
+ *
+ * @param key
+ * @param src
+ * @return
+ * @throws Exception
+ */
+ private static byte[] encrypt2Java(byte[] key, byte[] src) throws Exception {
+ SecretKeySpec skeySpec = new SecretKeySpec(key, "AES");
+ Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM);
+ cipher.init(Cipher.ENCRYPT_MODE, skeySpec, new IvParameterSpec(new byte[cipher.getBlockSize()]));
+ byte[] encrypted = cipher.doFinal(src);
+ return encrypted;
+ }
+
+ /**
+ * 真正的解密过程
+ *
+ * @param key
+ * @param encrypted
+ * @return
+ * @throws Exception
+ */
+ private static byte[] decrypt(byte[] key, byte[] encrypted) throws Exception {
+ SecretKeySpec skeySpec = new SecretKeySpec(key, "AES");
+ Cipher cipher = Cipher.getInstance("AES");
+ cipher.init(Cipher.DECRYPT_MODE, skeySpec, new IvParameterSpec(new byte[cipher.getBlockSize()]));
+ byte[] decrypted = cipher.doFinal(encrypted);
+ return decrypted;
+ }
+
+ public static String toHex(String txt) {
+ return toHex(txt.getBytes());
+ }
+
+ public static String fromHex(String hex) {
+ return new String(toByte(hex));
+ }
+
+
+ /**
+ * 把16进制转化为字节数组
+ * @param hexString
+ * @return
+ */
+ public static byte[] toByte(String hexString) {
+ int len = hexString.length() / 2;
+ byte[] result = new byte[len];
+ for (int i = 0; i < len; i++)
+ result[i] = Integer.valueOf(hexString.substring(2 * i, 2 * i + 2), 16).byteValue();
+ return result;
+ }
+
+
+ /**
+ * 二进制转字符,转成了16进制
+ * 0123456789abcdefg
+ * @param buf
+ * @return
+ */
+ public static String toHex(byte[] buf) {
+ if (buf == null)
+ return "";
+ StringBuffer result = new StringBuffer(2 * buf.length);
+ for (int i = 0; i < buf.length; i++) {
+ appendHex(result, buf[i]);
+ }
+ return result.toString();
+ }
+
+ private static void appendHex(StringBuffer sb, byte b) {
+ sb.append(HEX.charAt((b >> 4) & 0x0f)).append(HEX.charAt(b & 0x0f));
+ }
+
+ /**
+ * 初始化 AES Cipher
+ * @param sKey
+ * @param cipherMode
+ * @return
+ */
+ public static Cipher initAESCipher(String sKey, int cipherMode) {
+ // 创建Key gen
+ // KeyGenerator keyGenerator = null;
+ Cipher cipher = null;
+ try {
+ /*
+ * keyGenerator = KeyGenerator.getInstance("AES");
+ * keyGenerator.init(128, new SecureRandom(sKey.getBytes()));
+ * SecretKey secretKey = keyGenerator.generateKey(); byte[]
+ * codeFormat = secretKey.getEncoded(); SecretKeySpec key = new
+ * SecretKeySpec(codeFormat, "AES"); cipher =
+ * Cipher.getInstance("AES"); //初始化 cipher.init(cipherMode, key);
+ */
+ byte[] rawKey = toMakekey(sKey, keyLenght, defaultV).getBytes();
+ SecretKeySpec skeySpec = new SecretKeySpec(rawKey, "AES");
+ cipher = Cipher.getInstance("AES");
+ cipher.init(cipherMode, skeySpec, new IvParameterSpec(new byte[cipher.getBlockSize()]));
+ } catch (NoSuchAlgorithmException e) {
+ e.printStackTrace(); // To change body of catch statement use File |
+ // Settings | File Templates.
+ } catch (NoSuchPaddingException e) {
+ e.printStackTrace(); // To change body of catch statement use File |
+ // Settings | File Templates.
+ } catch (InvalidKeyException e) {
+ e.printStackTrace(); // To change body of catch statement use File |
+ // Settings | File Templates.
+ } catch (InvalidAlgorithmParameterException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ return cipher;
+ }
+
+ /**
+ * 对文件进行AES加密
+ * @param sourceFile
+ * @param fileType
+ * @param sKey
+ * @return
+ */
+ public static File encryptFile(File sourceFile, String toFile, String dir, String sKey) {
+ // 新建临时加密文件
+ File encrypfile = null;
+ InputStream inputStream = null;
+ OutputStream outputStream = null;
+ try {
+ inputStream = new FileInputStream(sourceFile);
+ encrypfile = new File(dir + toFile);
+ outputStream = new FileOutputStream(encrypfile);
+ Cipher cipher = initAESCipher(sKey, Cipher.ENCRYPT_MODE);
+ // 以加密流写入文件
+ CipherInputStream cipherInputStream = new CipherInputStream(inputStream, cipher);
+ byte[] cache = new byte[1024];
+ int nRead = 0;
+ while ((nRead = cipherInputStream.read(cache)) != -1) {
+ outputStream.write(cache, 0, nRead);
+ outputStream.flush();
+ }
+ cipherInputStream.close();
+ } catch (FileNotFoundException e) {
+ e.printStackTrace(); // To change body of catch statement use File |
+ // Settings | File Templates.
+ } catch (IOException e) {
+ e.printStackTrace(); // To change body of catch statement use File |
+ // Settings | File Templates.
+ } finally {
+ try {
+ inputStream.close();
+ } catch (IOException e) {
+ e.printStackTrace(); // To change body of catch statement use
+ // File | Settings | File Templates.
+ }
+ try {
+ outputStream.close();
+ } catch (IOException e) {
+ e.printStackTrace(); // To change body of catch statement use
+ // File | Settings | File Templates.
+ }
+ }
+ return encrypfile;
+ }
+
+ /**
+ * AES方式解密文件
+ * @param sourceFile
+ * @return
+ */
+ public static File decryptFile(File sourceFile, String toFile, String dir, String sKey) {
+ File decryptFile = null;
+ InputStream inputStream = null;
+ OutputStream outputStream = null;
+ try {
+ decryptFile = new File(dir + toFile);
+ Cipher cipher = initAESCipher(sKey, Cipher.DECRYPT_MODE);
+ inputStream = new FileInputStream(sourceFile);
+ outputStream = new FileOutputStream(decryptFile);
+ CipherOutputStream cipherOutputStream = new CipherOutputStream(outputStream, cipher);
+ byte[] buffer = new byte[1024];
+ int r;
+ while ((r = inputStream.read(buffer)) >= 0) {
+ cipherOutputStream.write(buffer, 0, r);
+ }
+ cipherOutputStream.close();
+ } catch (IOException e) {
+ e.printStackTrace(); // To change body of catch statement use File |
+ // Settings | File Templates.
+ } finally {
+ try {
+ inputStream.close();
+ } catch (IOException e) {
+ e.printStackTrace(); // To change body of catch statement use
+ // File | Settings | File Templates.
+ }
+ try {
+ outputStream.close();
+ } catch (IOException e) {
+ e.printStackTrace(); // To change body of catch statement use
+ // File | Settings | File Templates.
+ }
+ }
+ return decryptFile;
+ }
+
+}
diff --git a/app/src/main/java/com/mjsheng/myappstore/utils/JGYUtils.java b/app/src/main/java/com/mjsheng/myappstore/utils/JGYUtils.java
index e3ba98f..2bbe89e 100644
--- a/app/src/main/java/com/mjsheng/myappstore/utils/JGYUtils.java
+++ b/app/src/main/java/com/mjsheng/myappstore/utils/JGYUtils.java
@@ -13,6 +13,7 @@ import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.UserInfo;
+import android.graphics.Bitmap;
import android.net.Uri;
import android.os.BatteryManager;
import android.os.Build;
@@ -29,6 +30,12 @@ import com.blankj.utilcode.util.PathUtils;
import com.google.gson.Gson;
import com.google.gson.JsonObject;
import com.google.gson.reflect.TypeToken;
+import com.google.zxing.BarcodeFormat;
+import com.google.zxing.EncodeHintType;
+import com.google.zxing.WriterException;
+import com.google.zxing.common.BitMatrix;
+import com.google.zxing.qrcode.QRCodeWriter;
+import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel;
import com.mjsheng.myappstore.BuildConfig;
import com.mjsheng.myappstore.action.JGYActions;
import com.mjsheng.myappstore.base.BaseApplication;
@@ -84,6 +91,7 @@ public class JGYUtils {
public static void init(Context context) {
if (sInstance == null) {
+ Log.e(TAG, "init: ");
sInstance = new JGYUtils(context);
}
}
@@ -100,6 +108,12 @@ public class JGYUtils {
return "official".equals(channelValue);
}
+ public static boolean isOfficialVersion(Context context) {
+ Log.e(TAG, "isOfficialVersion: " + ProcessUtil.getCurrentProcessName(context));
+ String channelValue = JGYUtils.getInstance().getStringMetaData();
+ return "official".equals(channelValue);
+ }
+
public static boolean isNewlyVersion() {
String channelValue = JGYUtils.getInstance().getStringMetaData();
return "beta".equals(channelValue);
@@ -227,7 +241,7 @@ public class JGYUtils {
Settings.System.putString(mContext.getContentResolver(), "qch_app_power_on", qch_app_power_on);
}
// if (BuildConfig.DEBUG) {
- // TODO: 2021/7/2 测试写入为空是否断网
+ // TODO: 2021/7/2 测试写入为空是否断网
// boolean w = Settings.System.putString(mContext.getContentResolver(), "qch_app_power_on", "");
// Log.e(TAG, "setNetAndlaunch: 测试写入: " + w);
// }
@@ -732,10 +746,10 @@ public class JGYUtils {
}
}
+ @SuppressLint("NewApi")
public void writeAppPackageList(Context context, String packageList) {
- StringBuilder stringBuilder = new StringBuilder(packageList);
ApkUtils.addShortcut(context);
- List packages = new ArrayList() {{
+ HashSet packages = new HashSet() {{
this.add("com.jiaoguanyi.appstore");//设备信息
this.add("com.jiaoguanyi.store");//教管壹
this.add("com.info.sn");
@@ -743,19 +757,18 @@ public class JGYUtils {
this.add("com.uiuios.jgy2");
this.add("com.tt.ttutils");
}};
-// if (!TextUtils.isEmpty(stringBuilder)) {
- for (String pkg : packages) {
- if (!packageList.contains(pkg)) {
- stringBuilder.append(",");
- stringBuilder.append(pkg);
+ HashSet pkgSet = new HashSet(Arrays.asList(packageList.split(",")));
+ pkgSet.addAll(packages);
+ pkgSet.removeIf(new Predicate() {
+ @Override
+ public boolean test(String s) {
+ return TextUtils.isEmpty(s);
}
- }
- boolean b = Settings.System.putString(mContext.getContentResolver(), "qch_app_forbid", stringBuilder.toString());
- Log.e(TAG, "writeAppPackageList: " + stringBuilder.toString());
+ });
+ String qch_app_forbid = String.join(",", pkgSet);
+ Log.e(TAG, "writeAppPackageList: " + qch_app_forbid);
+ boolean b = Settings.System.putString(mContext.getContentResolver(), "qch_app_forbid", qch_app_forbid);
Log.e("writeAppPackageList: ", "qch_app_forbid is :" + b + " " + Settings.System.getString(mContext.getContentResolver(), "qch_app_forbid"));
-// } else {
-// Log.e("writeAppPackageList: ", "writeAppPackageList is null:");
-// }
}
public void forceDownload(List data) {
@@ -1463,4 +1476,59 @@ public class JGYUtils {
return "https://" + url;
}
}
+
+ public Bitmap createQRImage(String content, int widthPix, int heightPix) {
+ try {
+// if (content == null || "".equals(content)) {
+// return false;
+// }
+
+ //配置参数
+ Map hints = new HashMap<>();
+ hints.put(EncodeHintType.CHARACTER_SET, "utf-8");
+ //容错级别
+ hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.H);
+ //设置空白边距的宽度
+ hints.put(EncodeHintType.MARGIN, 1); //default is 4
+
+ // 图像数据转换,使用了矩阵转换
+ BitMatrix bitMatrix = null;
+ try {
+ bitMatrix = new QRCodeWriter().encode(content, BarcodeFormat.QR_CODE, widthPix,
+ heightPix, hints);
+ } catch (WriterException e) {
+ e.printStackTrace();
+ }
+ int[] pixels = new int[widthPix * heightPix];
+ // 下面这里按照二维码的算法,逐个生成二维码的图片,
+ // 两个for循环是图片横列扫描的结果
+ for (int y = 0; y < heightPix; y++) {
+ for (int x = 0; x < widthPix; x++) {
+ if (bitMatrix.get(x, y)) {
+ pixels[y * widthPix + x] = 0xff000000;
+ } else {
+ pixels[y * widthPix + x] = 0xffffffff;
+ }
+ }
+ }
+
+ // 生成二维码图片的格式,使用ARGB_8888
+ Bitmap bitmap = Bitmap.createBitmap(widthPix, heightPix, Bitmap.Config.ARGB_8888);
+ bitmap.setPixels(pixels, 0, widthPix, 0, 0, widthPix, heightPix);
+//
+// if (logoBm != null) {
+// bitmap = addLogo(bitmap, logoBm);
+// }
+
+ //必须使用compress方法将bitmap保存到文件中再进行读取。直接返回的bitmap是没有任何压缩的,
+ // 内存消耗巨大!
+ return bitmap;
+// return bitmap != null && bitmap.compress(Bitmap.CompressFormat.JPEG, 100);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+
+ return null;
+ }
+
}
diff --git a/app/src/main/java/com/mjsheng/myappstore/utils/ProcessUtil.java b/app/src/main/java/com/mjsheng/myappstore/utils/ProcessUtil.java
new file mode 100644
index 0000000..575d356
--- /dev/null
+++ b/app/src/main/java/com/mjsheng/myappstore/utils/ProcessUtil.java
@@ -0,0 +1,96 @@
+package com.mjsheng.myappstore.utils;
+
+import android.app.ActivityManager;
+import android.app.Application;
+import android.content.Context;
+import android.os.Build;
+import android.text.TextUtils;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import java.lang.reflect.Method;
+import java.util.List;
+
+public class ProcessUtil {
+ private static String currentProcessName;
+
+ /**
+ * @return 当前进程名
+ */
+ @Nullable
+ public static String getCurrentProcessName(@NonNull Context context) {
+ if (!TextUtils.isEmpty(currentProcessName)) {
+ return currentProcessName;
+ }
+
+ //1)通过Application的API获取当前进程名
+ currentProcessName = getCurrentProcessNameByApplication();
+ if (!TextUtils.isEmpty(currentProcessName)) {
+ return currentProcessName;
+ }
+
+ //2)通过反射ActivityThread获取当前进程名
+ currentProcessName = getCurrentProcessNameByActivityThread();
+ if (!TextUtils.isEmpty(currentProcessName)) {
+ return currentProcessName;
+ }
+
+ //3)通过ActivityManager获取当前进程名
+ currentProcessName = getCurrentProcessNameByActivityManager(context);
+
+ return currentProcessName;
+ }
+
+
+ /**
+ * 通过Application新的API获取进程名,无需反射,无需IPC,效率最高。
+ */
+ public static String getCurrentProcessNameByApplication() {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
+ return Application.getProcessName();
+ }
+ return null;
+ }
+
+ /**
+ * 通过反射ActivityThread获取进程名,避免了ipc
+ */
+ public static String getCurrentProcessNameByActivityThread() {
+ String processName = null;
+ try {
+ final Method declaredMethod = Class.forName("android.app.ActivityThread", false, Application.class.getClassLoader())
+ .getDeclaredMethod("currentProcessName", (Class>[]) new Class[0]);
+ declaredMethod.setAccessible(true);
+ final Object invoke = declaredMethod.invoke(null, new Object[0]);
+ if (invoke instanceof String) {
+ processName = (String) invoke;
+ }
+ } catch (Throwable e) {
+ e.printStackTrace();
+ }
+ return processName;
+ }
+
+ /**
+ * 通过ActivityManager 获取进程名,需要IPC通信
+ */
+ public static String getCurrentProcessNameByActivityManager(@NonNull Context context) {
+ if (context == null) {
+ return null;
+ }
+ int pid = android.os.Process.myPid();
+ ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
+ if (am != null) {
+ List runningAppList = am.getRunningAppProcesses();
+ if (runningAppList != null) {
+ for (ActivityManager.RunningAppProcessInfo processInfo : runningAppList) {
+ if (processInfo.pid == pid) {
+ return processInfo.processName;
+ }
+ }
+ }
+ }
+ return null;
+ }
+}
diff --git a/app/src/main/java/com/mjsheng/myappstore/utils/TimeUtils.java b/app/src/main/java/com/mjsheng/myappstore/utils/TimeUtils.java
index 95c438f..40a5e4d 100644
--- a/app/src/main/java/com/mjsheng/myappstore/utils/TimeUtils.java
+++ b/app/src/main/java/com/mjsheng/myappstore/utils/TimeUtils.java
@@ -6,7 +6,7 @@ import androidx.annotation.NonNull;
import android.text.TextUtils;
-import com.mjsheng.myappstore.server.MainService;
+import com.mjsheng.myappstore.service.MainService;
import java.text.DateFormat;
import java.text.ParseException;
diff --git a/app/src/main/res/layout-land/activity_main.xml b/app/src/main/res/layout-land/activity_main.xml
index 7d2f612..fe3b6b3 100644
--- a/app/src/main/res/layout-land/activity_main.xml
+++ b/app/src/main/res/layout-land/activity_main.xml
@@ -12,24 +12,9 @@
android:layout_width="match_parent"
android:layout_height="@dimen/dp_32"
app:layout_constraintEnd_toEndOf="parent"
- app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
-
-
-
+
+
+
+
+
+
+
+
+
+
-
-
-
+ app:layout_constraintTop_toBottomOf="@+id/bannerLayout">
-
+
+
+
+
+
+
+
+ app:layout_constraintTop_toBottomOf="@+id/imageView">
-
-
-
+ app:layout_constraintTop_toBottomOf="@+id/tv_batch">
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+ android:textSize="@dimen/sp_12"
+ android:visibility="visible"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toBottomOf="@+id/imageView" />
-
+
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ android:layout_gravity="center_horizontal"
+ android:layout_marginTop="16dp"
+ android:layout_marginBottom="24dp"
+ android:background="@drawable/bt_checkupdate_selector"
+ android:stateListAnimator="@null"
+ android:text="检查更新"
+ android:textColor="@color/white"
+ android:textSize="@dimen/sp_12"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toBottomOf="@+id/linearLayout" />
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
-
-
-
-
+
+
+
+
+
+
+
+
+