diff --git a/app/src/full/AndroidManifest.xml b/app/src/full/AndroidManifest.xml index 252975119..e463b6e6c 100644 --- a/app/src/full/AndroidManifest.xml +++ b/app/src/full/AndroidManifest.xml @@ -79,7 +79,7 @@ + android:value="12451000" /> diff --git a/app/src/full/java/com/topjohnwu/magisk/Const.java b/app/src/full/java/com/topjohnwu/magisk/Const.java index b3baff80e..834c38408 100644 --- a/app/src/full/java/com/topjohnwu/magisk/Const.java +++ b/app/src/full/java/com/topjohnwu/magisk/Const.java @@ -34,7 +34,6 @@ public class Const { // Versions public static final int UPDATE_SERVICE_VER = 1; - public static final int SNET_VER = 10; public static int MIN_MODULE_VER() { return Data.magiskVersionCode >= MAGISK_VER.REMOVE_LEGACY_LINK ? 1500 : 1400; @@ -76,7 +75,6 @@ public class Const { public static class Url { public static final String STABLE_URL = "https://raw.githubusercontent.com/topjohnwu/magisk_files/master/stable.json"; public static final String BETA_URL = "https://raw.githubusercontent.com/topjohnwu/magisk_files/master/beta.json"; - public static final String SNET_URL = "https://github.com/topjohnwu/magisk_files/raw/a300521162587da23e45010797bfd8c9a03594f6/snet.apk"; public static final String REPO_URL = "https://api.github.com/users/Magisk-Modules-Repo/repos?per_page=100&sort=pushed&page=%d"; public static final String FILE_URL = "https://raw.githubusercontent.com/Magisk-Modules-Repo/%s/master/%s"; public static final String ZIP_URL = "https://github.com/Magisk-Modules-Repo/%s/archive/master.zip"; diff --git a/app/src/full/java/com/topjohnwu/magisk/Data.java b/app/src/full/java/com/topjohnwu/magisk/Data.java index cbb8812cc..2c3d84ae3 100644 --- a/app/src/full/java/com/topjohnwu/magisk/Data.java +++ b/app/src/full/java/com/topjohnwu/magisk/Data.java @@ -39,6 +39,8 @@ public class Data { public static String managerLink; public static String managerNoteLink; public static String uninstallerLink; + public static int snetVersionCode; + public static String snetLink; // Install flags public static boolean keepVerity = false; diff --git a/app/src/full/java/com/topjohnwu/magisk/MagiskFragment.java b/app/src/full/java/com/topjohnwu/magisk/MagiskFragment.java index 281140d12..da9dea237 100644 --- a/app/src/full/java/com/topjohnwu/magisk/MagiskFragment.java +++ b/app/src/full/java/com/topjohnwu/magisk/MagiskFragment.java @@ -226,11 +226,9 @@ public class MagiskFragment extends BaseFragment boolean hasNetwork = Download.checkNetworkStatus(mm); boolean hasRoot = Shell.rootAccess(); - boolean hasGms = hasGms(); boolean isUpToDate = Data.magiskVersionCode > Const.MAGISK_VER.UNIFIED; magiskUpdate.setVisibility(hasNetwork ? View.VISIBLE : View.GONE); - safetyNetCard.setVisibility(hasNetwork && hasGms ? View.VISIBLE : View.GONE); installOptionCard.setVisibility(hasNetwork ? View.VISIBLE : View.GONE); uninstallButton.setVisibility(isUpToDate && hasRoot ? View.VISIBLE : View.GONE); coreOnlyNotice.setVisibility(mm.prefs.getBoolean(Const.Key.COREONLY, false) ? View.VISIBLE : View.GONE); @@ -254,6 +252,8 @@ public class MagiskFragment extends BaseFragment private void updateCheckUI() { int image, color; + safetyNetCard.setVisibility(hasGms() ? View.VISIBLE : View.GONE); + if (Data.remoteMagiskVersionCode < 0) { color = colorNeutral; image = R.drawable.ic_help; @@ -312,12 +312,6 @@ public class MagiskFragment extends BaseFragment } else { @StringRes int resid; switch (response) { - case ISafetyNetHelper.CAUSE_SERVICE_DISCONNECTED: - resid = R.string.safetyNet_network_loss; - break; - case ISafetyNetHelper.CAUSE_NETWORK_LOST: - resid = R.string.safetyNet_service_disconnected; - break; case ISafetyNetHelper.RESPONSE_ERR: resid = R.string.safetyNet_res_invalid; break; diff --git a/app/src/full/java/com/topjohnwu/magisk/asyncs/CheckSafetyNet.java b/app/src/full/java/com/topjohnwu/magisk/asyncs/CheckSafetyNet.java index af3fe9040..2dde7ee29 100644 --- a/app/src/full/java/com/topjohnwu/magisk/asyncs/CheckSafetyNet.java +++ b/app/src/full/java/com/topjohnwu/magisk/asyncs/CheckSafetyNet.java @@ -2,7 +2,6 @@ package com.topjohnwu.magisk.asyncs; import android.app.Activity; -import com.topjohnwu.magisk.Const; import com.topjohnwu.magisk.Data; import com.topjohnwu.magisk.utils.ISafetyNetHelper; import com.topjohnwu.magisk.utils.Topic; @@ -20,7 +19,7 @@ import java.net.HttpURLConnection; import dalvik.system.DexClassLoader; -public class CheckSafetyNet extends ParallelTask { +public class CheckSafetyNet extends ParallelTask { public static final File dexPath = new File(Data.MM().getFilesDir().getParent() + "/snet", "snet.apk"); @@ -33,7 +32,7 @@ public class CheckSafetyNet extends ParallelTask { private void dlSnet() throws Exception { Shell.sh("rm -rf " + dexPath.getParent()).exec(); dexPath.getParentFile().mkdir(); - HttpURLConnection conn = WebService.mustRequest(Const.Url.SNET_URL, null); + HttpURLConnection conn = WebService.mustRequest(Data.snetLink, null); try ( OutputStream out = new BufferedOutputStream(new FileOutputStream(dexPath)); InputStream in = new BufferedInputStream(conn.getInputStream())) { @@ -52,13 +51,13 @@ public class CheckSafetyNet extends ParallelTask { .invoke(null, ISafetyNetHelper.class, dexPath.getPath(), getActivity(), (ISafetyNetHelper.Callback) code -> Topic.publish(false, Topic.SNET_CHECK_DONE, code)); - if (helper.getVersion() != Const.SNET_VER) { + if (helper.getVersion() < Data.snetVersionCode) { throw new Exception(); } } @Override - protected Exception doInBackground(Void... voids) { + protected Void doInBackground(Void... voids) { try { try { dyload(); @@ -67,21 +66,12 @@ public class CheckSafetyNet extends ParallelTask { dlSnet(); dyload(); } - } catch (Exception e) { - return e; - } - - return null; - } - - @Override - protected void onPostExecute(Exception e) { - if (e == null) { + // Run attestation helper.attest(); - } else { + } catch (Exception e) { e.printStackTrace(); Topic.publish(false, Topic.SNET_CHECK_DONE, -1); } - super.onPostExecute(e); + return null; } } diff --git a/app/src/full/java/com/topjohnwu/magisk/asyncs/CheckUpdates.java b/app/src/full/java/com/topjohnwu/magisk/asyncs/CheckUpdates.java index 9dbda9539..fbea511cc 100644 --- a/app/src/full/java/com/topjohnwu/magisk/asyncs/CheckUpdates.java +++ b/app/src/full/java/com/topjohnwu/magisk/asyncs/CheckUpdates.java @@ -78,6 +78,10 @@ public class CheckUpdates { JSONObject uninstaller = getJson(json, "uninstaller"); Data.uninstallerLink = getString(uninstaller, "link", null); + + JSONObject snet = getJson(json, "snet"); + Data.snetVersionCode = getInt(snet, "versionCode", -1); + Data.snetLink = getString(snet, "link", null); } public static void check(Runnable cb) { diff --git a/app/src/full/java/com/topjohnwu/magisk/utils/ISafetyNetHelper.java b/app/src/full/java/com/topjohnwu/magisk/utils/ISafetyNetHelper.java index 562418e1a..f55a6f5f8 100644 --- a/app/src/full/java/com/topjohnwu/magisk/utils/ISafetyNetHelper.java +++ b/app/src/full/java/com/topjohnwu/magisk/utils/ISafetyNetHelper.java @@ -4,10 +4,8 @@ import android.support.annotation.Keep; public interface ISafetyNetHelper { - int CAUSE_SERVICE_DISCONNECTED = 0x01; - int CAUSE_NETWORK_LOST = 0x02; - int RESPONSE_ERR = 0x04; - int CONNECTION_FAIL = 0x08; + int RESPONSE_ERR = 0x01; + int CONNECTION_FAIL = 0x02; int BASIC_PASS = 0x10; int CTS_PASS = 0x20; diff --git a/app/src/full/res/layout/fragment_magisk.xml b/app/src/full/res/layout/fragment_magisk.xml index e64824708..eac12eca7 100644 --- a/app/src/full/res/layout/fragment_magisk.xml +++ b/app/src/full/res/layout/fragment_magisk.xml @@ -154,6 +154,7 @@ android:layout_marginEnd="5dp" android:layout_marginStart="5dp" android:layout_marginTop="4dp" + android:visibility="gone" app:cardCornerRadius="@dimen/card_corner_radius" app:cardElevation="@dimen/card_elevation"> diff --git a/build.gradle b/build.gradle index 943100824..d9166d86d 100644 --- a/build.gradle +++ b/build.gradle @@ -25,7 +25,7 @@ allprojects { ext { compileSdkVersion = 28 - buildToolsVersion = "28.0.0" + buildToolsVersion = "28.0.2" supportLibVersion = "27.1.1" } diff --git a/snet/build.gradle b/snet/build.gradle index 31a144e43..36660f28e 100644 --- a/snet/build.gradle +++ b/snet/build.gradle @@ -8,23 +8,20 @@ android { applicationId "com.topjohnwu.snet" minSdkVersion 14 targetSdkVersion rootProject.ext.compileSdkVersion - versionCode 1 - versionName "1.0" + versionCode 11 + versionName "snet" } buildTypes { release { minifyEnabled true shrinkResources true - proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' } } } dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) - /* The oldest version */ - implementation('com.google.android.gms:play-services-safetynet:7.0.0') { - exclude module: 'support-v4' - } + implementation 'com.google.android.gms:play-services-safetynet:15.0.1' } diff --git a/snet/proguard-rules.pro b/snet/proguard-rules.pro index 6f3f1285f..290952c23 100644 --- a/snet/proguard-rules.pro +++ b/snet/proguard-rules.pro @@ -20,7 +20,4 @@ # hide the original source file name. #-renamesourcefileattribute SourceFile --ignorewarnings -keep class com.topjohnwu.snet.Snet { *; } --dontwarn java.lang.invoke** --dontwarn com.google.android.gms.common.GooglePlayServicesUtil** diff --git a/snet/src/main/java/com/topjohnwu/snet/ModdedGPSUtil.java b/snet/src/main/java/com/topjohnwu/snet/ModdedGPSUtil.java deleted file mode 100644 index 3ab005e04..000000000 --- a/snet/src/main/java/com/topjohnwu/snet/ModdedGPSUtil.java +++ /dev/null @@ -1,148 +0,0 @@ -package com.topjohnwu.snet; - -import android.app.Activity; -import android.app.AlertDialog; -import android.app.Dialog; -import android.content.Context; -import android.content.ContextWrapper; -import android.content.DialogInterface; -import android.content.Intent; -import android.content.pm.PackageManager; -import android.content.res.AssetManager; -import android.content.res.Resources; -import android.util.Log; - -import com.google.android.gms.common.GooglePlayServicesUtil; -import com.google.android.gms.internal.zzlu; - -/* Decompiled and modified from GooglePlayServiceUtil.class */ -public class ModdedGPSUtil { - - private static final String TAG = "GooglePlayServicesUtil"; - static String dexPath; - - static Dialog getErrorDialog(int errCode, final Activity activity, final int requestCode) { - SwapResContext ctx = new SwapResContext(activity, dexPath); - Resources res = ctx.getResources(); - if (zzlu.zzQ(ctx) && errCode == 2) { - errCode = 42; - } - - AlertDialog.Builder builder = new AlertDialog.Builder(activity); - - builder.setMessage(GooglePlayServicesUtil.zze(ctx, errCode)); - - String btnMsg = GooglePlayServicesUtil.zzf(ctx, errCode); - if (btnMsg != null) { - final Intent intent = GooglePlayServicesUtil.zzan(errCode); - builder.setPositiveButton(btnMsg, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int i) { - PackageManager pm = activity.getPackageManager(); - if (intent != null && intent.resolveActivity(pm) != null) - activity.startActivityForResult(intent, requestCode); - dialog.dismiss(); - } - }); - } - - switch(errCode) { - case 0: - return null; - case 1: - return builder.setTitle(res.getString(com.google.android.gms.R.string.common_google_play_services_install_title)).create(); - case 2: - return builder.setTitle(res.getString(com.google.android.gms.R.string.common_google_play_services_update_title)).create(); - case 3: - return builder.setTitle(res.getString(com.google.android.gms.R.string.common_google_play_services_enable_title)).create(); - case 4: - case 6: - return builder.create(); - case 5: - Log.e(TAG, "An invalid account was specified when connecting. Please provide a valid account."); - return builder.setTitle(res.getString(com.google.android.gms.R.string.common_google_play_services_invalid_account_title)).create(); - case 7: - Log.e(TAG, "Network error occurred. Please retry request later."); - return builder.setTitle(res.getString(com.google.android.gms.R.string.common_google_play_services_network_error_title)).create(); - case 8: - Log.e(TAG, "Internal error occurred. Please see logs for detailed information"); - return builder.create(); - case 9: - Log.e(TAG, "Google Play services is invalid. Cannot recover."); - return builder.setTitle(res.getString(com.google.android.gms.R.string.common_google_play_services_unsupported_title)).create(); - case 10: - Log.e(TAG, "Developer error occurred. Please see logs for detailed information"); - return builder.create(); - case 11: - Log.e(TAG, "The application is not licensed to the user."); - return builder.create(); - case 12: - case 13: - case 14: - case 15: - case 18: - case 19: - case 20: - case 21: - case 22: - case 23: - case 24: - case 25: - case 26: - case 27: - case 28: - case 29: - case 30: - case 31: - case 32: - case 33: - case 34: - case 35: - case 36: - case 37: - case 38: - case 39: - case 40: - case 41: - default: - Log.e(TAG, "Unexpected error code " + errCode); - return builder.create(); - case 16: - Log.e(TAG, "One of the API components you attempted to connect to is not available."); - return builder.create(); - case 17: - Log.e(TAG, "The specified account could not be signed in."); - return builder.setTitle(res.getString(com.google.android.gms.R.string.common_google_play_services_sign_in_failed_title)).create(); - case 42: - return builder.setTitle(res.getString(com.google.android.gms.R.string.common_android_wear_update_title)).create(); - } - } - - public static class SwapResContext extends ContextWrapper { - - private AssetManager asset; - private Resources resources; - - public SwapResContext(Context base, String apk) { - super(base); - try { - asset = AssetManager.class.newInstance(); - AssetManager.class.getMethod("addAssetPath", String.class).invoke(asset, apk); - } catch (Exception e) { - e.printStackTrace(); - } - Resources res = base.getResources(); - resources = new Resources(asset, res.getDisplayMetrics(), res.getConfiguration()); - } - - @Override - public Resources getResources() { - return resources; - } - - @Override - public AssetManager getAssets() { - return asset; - } - } -} diff --git a/snet/src/main/java/com/topjohnwu/snet/SafetyNetHelper.java b/snet/src/main/java/com/topjohnwu/snet/SafetyNetHelper.java index 419e28112..4009a8191 100644 --- a/snet/src/main/java/com/topjohnwu/snet/SafetyNetHelper.java +++ b/snet/src/main/java/com/topjohnwu/snet/SafetyNetHelper.java @@ -1,15 +1,24 @@ package com.topjohnwu.snet; import android.app.Activity; -import android.os.Bundle; +import android.app.AlertDialog; +import android.app.Dialog; +import android.content.Context; +import android.content.Intent; +import android.support.annotation.NonNull; import android.util.Base64; +import android.util.Log; import com.google.android.gms.common.ConnectionResult; -import com.google.android.gms.common.GooglePlayServicesUtil; -import com.google.android.gms.common.api.GoogleApiClient; -import com.google.android.gms.common.api.ResultCallback; +import com.google.android.gms.common.GoogleApiAvailability; +import com.google.android.gms.common.api.ApiException; +import com.google.android.gms.common.internal.ConnectionErrorMessages; +import com.google.android.gms.common.internal.DialogRedirect; import com.google.android.gms.safetynet.SafetyNet; import com.google.android.gms.safetynet.SafetyNetApi; +import com.google.android.gms.safetynet.SafetyNetClient; +import com.google.android.gms.tasks.OnFailureListener; +import com.google.android.gms.tasks.OnSuccessListener; import org.json.JSONException; import org.json.JSONObject; @@ -18,54 +27,29 @@ import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.security.SecureRandom; -public class SafetyNetHelper implements InvocationHandler, GoogleApiClient.ConnectionCallbacks, - GoogleApiClient.OnConnectionFailedListener, ResultCallback { +public class SafetyNetHelper implements InvocationHandler, + OnSuccessListener, OnFailureListener { - public static final int CAUSE_SERVICE_DISCONNECTED = 0x01; - public static final int CAUSE_NETWORK_LOST = 0x02; - public static final int RESPONSE_ERR = 0x04; - public static final int CONNECTION_FAIL = 0x08; + private static final int RESPONSE_ERR = 0x01; + private static final int CONNECTION_FAIL = 0x02; + private static final int BASIC_PASS = 0x10; + private static final int CTS_PASS = 0x20; - public static final int BASIC_PASS = 0x10; - public static final int CTS_PASS = 0x20; + private static final GoogleApiAvailability API_AVAIL = GoogleApiAvailability.getInstance(); + private static final SecureRandom RANDOM = new SecureRandom(); + private static final String TAG = "SNET"; - public static final int SNET_EXT_VER = 10; + /* Insert the magic API key here :) */ + private static final String API_KEY = ""; - private GoogleApiClient mGoogleApiClient; - private Activity mActivity; - private Object callback; + private final Activity mActivity; + private final Object callback; SafetyNetHelper(Activity activity, Object cb) { mActivity = activity; callback = cb; } - /* Override ISafetyNetHelper.getVersion */ - private int getVersion() { - return SNET_EXT_VER; - } - - /* Override ISafetyNetHelper.attest */ - private void attest() { - // Connect Google Service - mGoogleApiClient = new GoogleApiClient.Builder(mActivity) - .addApi(SafetyNet.API) - .addOnConnectionFailedListener(this) - .addConnectionCallbacks(this) - .build(); - mGoogleApiClient.connect(); - } - - @Override - public Object invoke(Object o, Method method, Object[] args) { - if (method.getName().equals("attest")) { - attest(); - } else if (method.getName().equals("getVersion")) { - return getVersion(); - } - return null; - } - private void invokeCallback(int code) { Class clazz = callback.getClass(); try { @@ -73,34 +57,50 @@ public class SafetyNetHelper implements InvocationHandler, GoogleApiClient.Conne } catch (Exception ignored) {} } - @Override - public void onConnectionSuspended(int i) { - invokeCallback(i); + /* Override ISafetyNetHelper.getVersion */ + private int getVersion() { + return BuildConfig.VERSION_CODE; } - @Override - public void onConnectionFailed(ConnectionResult result) { - if (GooglePlayServicesUtil.isUserRecoverableError(result.getErrorCode())) - ModdedGPSUtil.getErrorDialog(result.getErrorCode(), mActivity, 0).show(); - invokeCallback(CONNECTION_FAIL); - } - - @Override - public void onConnected(Bundle bundle) { + /* Override ISafetyNetHelper.attest */ + private void attest() { + int code = API_AVAIL.isGooglePlayServicesAvailable(mActivity); + if (code != ConnectionResult.SUCCESS) { + if (API_AVAIL.isUserResolvableError(code)) + getErrorDialog(code, 0).show(); + invokeCallback(CONNECTION_FAIL); + return; + } // Create nonce byte[] nonce = new byte[24]; - new SecureRandom().nextBytes(nonce); + RANDOM.nextBytes(nonce); - // Call SafetyNet - SafetyNet.SafetyNetApi.attest(mGoogleApiClient, nonce).setResultCallback(this); + SafetyNetClient client = SafetyNet.getClient(mActivity.getBaseContext()); + client.attest(nonce, API_KEY).addOnSuccessListener(this).addOnFailureListener(this); + } + + private Dialog getErrorDialog(int errorCode, int requestCode) { + AlertDialog.Builder builder = new AlertDialog.Builder(mActivity); + Context swapCtx = new SwapResContext(mActivity, Snet.dexPath); + Intent intent = API_AVAIL.getErrorResolutionIntent(swapCtx, errorCode, "d"); + + builder.setMessage(ConnectionErrorMessages.getErrorMessage(swapCtx, errorCode)); + builder.setPositiveButton( + ConnectionErrorMessages.getErrorDialogButtonMessage(swapCtx, errorCode), + DialogRedirect.getInstance(mActivity, intent, requestCode)); + + String title; + if ((title = ConnectionErrorMessages.getErrorTitle(swapCtx, errorCode)) != null) { + builder.setTitle(title); + } + + return builder.create(); } @Override - public void onResult(SafetyNetApi.AttestationResult result) { + public void onSuccess(SafetyNetApi.AttestationResponse result) { int code = 0; try { - if (!result.getStatus().isSuccess()) - throw new JSONException(""); String jsonStr = new String(Base64.decode( result.getJwsResult().split("\\.")[1], Base64.DEFAULT)); JSONObject json = new JSONObject(jsonStr); @@ -110,10 +110,34 @@ public class SafetyNetHelper implements InvocationHandler, GoogleApiClient.Conne code = RESPONSE_ERR; } - // Disconnect - mGoogleApiClient.disconnect(); - // Return results invokeCallback(code); } + + @Override + public void onFailure(@NonNull Exception e) { + if (e instanceof ApiException) { + int errCode = ((ApiException) e).getStatusCode(); + if (API_AVAIL.isUserResolvableError(errCode)) + getErrorDialog(errCode, 0).show(); + else + Log.e(TAG, "Cannot resolve: " + e.getMessage()); + } else { + Log.e(TAG, "Unknown: " + e.getMessage()); + } + invokeCallback(CONNECTION_FAIL); + } + + @Override + public Object invoke(Object proxy, Method method, Object[] args) { + switch (method.getName()) { + case "attest": + attest(); + return null; + case "getVersion": + return getVersion(); + default: + return null; + } + } } diff --git a/snet/src/main/java/com/topjohnwu/snet/Snet.java b/snet/src/main/java/com/topjohnwu/snet/Snet.java index 44438cb32..171e2a31c 100644 --- a/snet/src/main/java/com/topjohnwu/snet/Snet.java +++ b/snet/src/main/java/com/topjohnwu/snet/Snet.java @@ -5,9 +5,11 @@ import android.app.Activity; import java.lang.reflect.Proxy; public class Snet { - public static Object newHelper(Class clazz, String dexPath, Activity activity, Object cb) { - ModdedGPSUtil.dexPath = dexPath; + static String dexPath; + + public static Object newHelper(Class interfaceClass, String dexPath, Activity activity, Object cb) { + Snet.dexPath = dexPath; return Proxy.newProxyInstance(SafetyNetHelper.class.getClassLoader(), - new Class[] { clazz }, new SafetyNetHelper(activity, cb)); + new Class[] { interfaceClass }, new SafetyNetHelper(activity, cb)); } } diff --git a/snet/src/main/java/com/topjohnwu/snet/SwapResContext.java b/snet/src/main/java/com/topjohnwu/snet/SwapResContext.java new file mode 100644 index 000000000..1d765ed8a --- /dev/null +++ b/snet/src/main/java/com/topjohnwu/snet/SwapResContext.java @@ -0,0 +1,34 @@ +package com.topjohnwu.snet; + +import android.content.Context; +import android.content.ContextWrapper; +import android.content.res.AssetManager; +import android.content.res.Resources; + +public class SwapResContext extends ContextWrapper { + + private AssetManager asset; + private Resources resources; + + public SwapResContext(Context base, String apk) { + super(base); + try { + asset = AssetManager.class.newInstance(); + AssetManager.class.getMethod("addAssetPath", String.class).invoke(asset, apk); + } catch (Exception e) { + e.printStackTrace(); + } + Resources res = base.getResources(); + resources = new Resources(asset, res.getDisplayMetrics(), res.getConfiguration()); + } + + @Override + public Resources getResources() { + return resources; + } + + @Override + public AssetManager getAssets() { + return asset; + } +}