From 5d50ae375fd969e587bbdbcee1461f36b7349736 Mon Sep 17 00:00:00 2001 From: Marvin W Date: Fri, 14 May 2021 23:52:21 +0200 Subject: [PATCH 1/4] Adjust dynamite loader to support chimera modules with merged class loaders --- .../android/gms/dynamite/IDynamiteLoader.aidl | 12 ++- .../chimera/container/DynamiteContext.java | 95 +++++++++++++++++++ .../chimera/container/DynamiteLoaderImpl.java | 49 ++++++---- .../chimera/container/DynamiteModuleInfo.java | 51 ++++++++++ 4 files changed, 185 insertions(+), 22 deletions(-) create mode 100644 play-services-core/src/main/java/com/google/android/gms/chimera/container/DynamiteContext.java create mode 100644 play-services-core/src/main/java/com/google/android/gms/chimera/container/DynamiteModuleInfo.java diff --git a/play-services-api/src/main/aidl/com/google/android/gms/dynamite/IDynamiteLoader.aidl b/play-services-api/src/main/aidl/com/google/android/gms/dynamite/IDynamiteLoader.aidl index 8cb2be99..017a2dc2 100644 --- a/play-services-api/src/main/aidl/com/google/android/gms/dynamite/IDynamiteLoader.aidl +++ b/play-services-api/src/main/aidl/com/google/android/gms/dynamite/IDynamiteLoader.aidl @@ -3,8 +3,12 @@ package com.google.android.gms.dynamite; import com.google.android.gms.dynamic.IObjectWrapper; interface IDynamiteLoader { - int getModuleVersion(IObjectWrapper context, String moduleId) = 0; - int getModuleVersion2(IObjectWrapper context, String moduleId, boolean updateConfigIfRequired) = 2; + int getModuleVersion(IObjectWrapper wrappedContext, String moduleId) = 0; + int getModuleVersion2(IObjectWrapper wrappedContext, String moduleId, boolean updateConfigIfRequired) = 2; + int getModuleVersion2NoCrashUtils(IObjectWrapper wrappedContext, String moduleId, boolean updateConfigIfRequired) = 4; - IObjectWrapper createModuleContext(IObjectWrapper context, String moduleId, int minVersion) = 1; -} \ No newline at end of file + IObjectWrapper createModuleContext(IObjectWrapper wrappedContext, String moduleId, int minVersion) = 1; + IObjectWrapper createModuleContextNoCrashUtils(IObjectWrapper wrappedContext, String moduleId, int minVersion) = 3; + + int getIDynamiteLoaderVersion() = 5; +} diff --git a/play-services-core/src/main/java/com/google/android/gms/chimera/container/DynamiteContext.java b/play-services-core/src/main/java/com/google/android/gms/chimera/container/DynamiteContext.java new file mode 100644 index 00000000..d97a79e9 --- /dev/null +++ b/play-services-core/src/main/java/com/google/android/gms/chimera/container/DynamiteContext.java @@ -0,0 +1,95 @@ +/* + * SPDX-FileCopyrightText: 2021, microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.gms.chimera.container; + +import android.content.Context; +import android.content.ContextWrapper; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.os.Build; +import android.os.Process; +import android.util.Log; + +import androidx.annotation.RequiresApi; + +import org.microg.gms.common.Constants; + +import java.io.File; + +import dalvik.system.PathClassLoader; + +public class DynamiteContext extends ContextWrapper { + private static final String TAG = "DynamiteContext"; + private String moduleId; + private Context originalContext; + private Context gmsContext; + private DynamiteContext appContext; + + public DynamiteContext(String moduleId, Context base, Context gmsContext, DynamiteContext appContext) { + super(base); + this.moduleId = moduleId; + this.originalContext = base; + this.gmsContext = gmsContext; + this.appContext = appContext; + } + + @Override + public ClassLoader getClassLoader() { + if (new DynamiteModuleInfo(moduleId).isMergeClassLoader()) { + StringBuilder nativeLoaderDirs = new StringBuilder(gmsContext.getApplicationInfo().nativeLibraryDir); + if (Build.VERSION.SDK_INT >= 23 && Process.is64Bit()) { + for (String abi : Build.SUPPORTED_64_BIT_ABIS) { + nativeLoaderDirs.append(File.pathSeparator).append(gmsContext.getApplicationInfo().sourceDir).append("!/lib/").append(abi); + } + } else if (Build.VERSION.SDK_INT >= 21) { + for (String abi : Build.SUPPORTED_32_BIT_ABIS) { + nativeLoaderDirs.append(File.pathSeparator).append(gmsContext.getApplicationInfo().sourceDir).append("!/lib/").append(abi); + } + } else { + nativeLoaderDirs.append(File.pathSeparator).append(gmsContext.getApplicationInfo().sourceDir).append("!/lib/").append(Build.CPU_ABI); + } + return new PathClassLoader(gmsContext.getApplicationInfo().sourceDir, nativeLoaderDirs.toString(), originalContext.getClassLoader()); + } else { + return gmsContext.getClassLoader(); + } + } + + @Override + public String getPackageName() { + return gmsContext.getPackageName(); + } + + @Override + public ApplicationInfo getApplicationInfo() { + return gmsContext.getApplicationInfo(); + } + + @Override + public Context getApplicationContext() { + return appContext; + } + + @RequiresApi(24) + @Override + public Context createDeviceProtectedStorageContext() { + return new DynamiteContext(moduleId, originalContext.createDeviceProtectedStorageContext(), gmsContext.createDeviceProtectedStorageContext(), appContext); + } + + public static DynamiteContext create(String moduleId, Context originalContext) { + try { + Context gmsContext = originalContext.createPackageContext(Constants.GMS_PACKAGE_NAME, new DynamiteModuleInfo(moduleId).getCreatePackageOptions()); + Context originalAppContext = originalContext.getApplicationContext(); + if (originalAppContext == null || originalAppContext == originalContext) { + return new DynamiteContext(moduleId, originalContext, gmsContext, null); + } else { + return new DynamiteContext(moduleId, originalContext, gmsContext, new DynamiteContext(moduleId, originalAppContext, gmsContext, null)); + } + } catch (PackageManager.NameNotFoundException e) { + Log.w(TAG, e); + return null; + } + } +} diff --git a/play-services-core/src/main/java/com/google/android/gms/chimera/container/DynamiteLoaderImpl.java b/play-services-core/src/main/java/com/google/android/gms/chimera/container/DynamiteLoaderImpl.java index d4a272af..e27537d3 100644 --- a/play-services-core/src/main/java/com/google/android/gms/chimera/container/DynamiteLoaderImpl.java +++ b/play-services-core/src/main/java/com/google/android/gms/chimera/container/DynamiteLoaderImpl.java @@ -35,28 +35,41 @@ public class DynamiteLoaderImpl extends IDynamiteLoader.Stub { @Override public IObjectWrapper createModuleContext(IObjectWrapper wrappedContext, String moduleId, int minVersion) throws RemoteException { + // We don't have crash utils, so just forward + return createModuleContextNoCrashUtils(wrappedContext, moduleId, minVersion); + } + + @Override + public IObjectWrapper createModuleContextNoCrashUtils(IObjectWrapper wrappedContext, String moduleId, int minVersion) throws RemoteException { Log.d(TAG, "createModuleContext for " + moduleId + " at version " + minVersion); + final Context originalContext = (Context) ObjectWrapper.unwrap(wrappedContext); + return ObjectWrapper.wrap(DynamiteContext.create(moduleId, originalContext)); + } + + @Override + public int getIDynamiteLoaderVersion() throws RemoteException { + return 2; + } + + @Override + public int getModuleVersion(IObjectWrapper wrappedContext, String moduleId) throws RemoteException { + return getModuleVersion2(wrappedContext, moduleId, true); + } + + @Override + public int getModuleVersion2(IObjectWrapper wrappedContext, String moduleId, boolean updateConfigIfRequired) throws RemoteException { + // We don't have crash utils, so just forward + return getModuleVersion2NoCrashUtils(wrappedContext, moduleId, updateConfigIfRequired); + } + + @Override + public int getModuleVersion2NoCrashUtils(IObjectWrapper wrappedContext, String moduleId, boolean updateConfigIfRequired) throws RemoteException { final Context context = (Context) ObjectWrapper.unwrap(wrappedContext); - try { - return ObjectWrapper.wrap(new ContextWrapper(context.createPackageContext(Constants.GMS_PACKAGE_NAME, Context.CONTEXT_INCLUDE_CODE | Context.CONTEXT_IGNORE_SECURITY)) { - @Override - public Context getApplicationContext() { - return context; - } - }); - } catch (PackageManager.NameNotFoundException e) { - Log.w(TAG, "returning null instead", e); - return null; + if (context == null) { + Log.w(TAG, "Invalid client context"); + return 0; } - } - @Override - public int getModuleVersion(IObjectWrapper context, String moduleId) throws RemoteException { - return getModuleVersion2(context, moduleId, true); - } - - @Override - public int getModuleVersion2(IObjectWrapper context, String moduleId, boolean updateConfigIfRequired) throws RemoteException { try { return Class.forName("com.google.android.gms.dynamite.descriptors." + moduleId + ".ModuleDescriptor").getDeclaredField("MODULE_VERSION").getInt(null); } catch (Exception e) { diff --git a/play-services-core/src/main/java/com/google/android/gms/chimera/container/DynamiteModuleInfo.java b/play-services-core/src/main/java/com/google/android/gms/chimera/container/DynamiteModuleInfo.java new file mode 100644 index 00000000..3489ba9f --- /dev/null +++ b/play-services-core/src/main/java/com/google/android/gms/chimera/container/DynamiteModuleInfo.java @@ -0,0 +1,51 @@ +/* + * SPDX-FileCopyrightText: 2021, microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.gms.chimera.container; + +import static android.content.Context.CONTEXT_IGNORE_SECURITY; +import static android.content.Context.CONTEXT_INCLUDE_CODE; + +public class DynamiteModuleInfo { + private Class descriptor; + private String moduleId; + + public DynamiteModuleInfo(String moduleId) { + this.moduleId = moduleId; + try { + this.descriptor = Class.forName("com.google.android.gms.dynamite.descriptors." + moduleId + ".ModuleDescriptor"); + } catch (Exception e) { + // Ignore + } + } + + public String getModuleId() { + return moduleId; + } + + public int getVersion() { + try { + return descriptor.getDeclaredField("MODULE_VERSION").getInt(null); + } catch (Exception e) { + return 0; + } + } + + public int getCreatePackageOptions() { + try { + return descriptor.getDeclaredField("CREATE_PACKAGE_OPTIONS").getInt(null); + } catch (Exception e) { + return CONTEXT_INCLUDE_CODE | CONTEXT_IGNORE_SECURITY; + } + } + + public boolean isMergeClassLoader() { + try { + return descriptor.getDeclaredField("MERGE_CLASS_LOADER").getBoolean(null); + } catch (Exception e) { + return false; + } + } +} From a746e79b9117c058188b992a3519e5192f7e2113 Mon Sep 17 00:00:00 2001 From: Marvin W Date: Sat, 15 May 2021 14:45:50 +0200 Subject: [PATCH 2/4] Auth: Update API implementation --- .../android/auth/IAuthManagerService.aidl | 5 +- .../gms/auth/AuthManagerServiceImpl.java | 168 +++++++++++------- 2 files changed, 107 insertions(+), 66 deletions(-) diff --git a/play-services-api/src/main/aidl/com/google/android/auth/IAuthManagerService.aidl b/play-services-api/src/main/aidl/com/google/android/auth/IAuthManagerService.aidl index 75a949b8..3bc93136 100644 --- a/play-services-api/src/main/aidl/com/google/android/auth/IAuthManagerService.aidl +++ b/play-services-api/src/main/aidl/com/google/android/auth/IAuthManagerService.aidl @@ -9,6 +9,9 @@ import com.google.android.gms.auth.AccountChangeEventsRequest; interface IAuthManagerService { Bundle getToken(String accountName, String scope, in Bundle extras) = 0; Bundle clearToken(String token, in Bundle extras) = 1; - AccountChangeEventsResponse getChangeEvents(in AccountChangeEventsRequest request) = 2; + AccountChangeEventsResponse getChangeEvents(in AccountChangeEventsRequest request) = 2; Bundle getTokenWithAccount(in Account account, String scope, in Bundle extras) = 4; + Bundle getAccounts(in Bundle extras) = 5; + Bundle removeAccount(in Account account) = 6; + Bundle requestGoogleAccountsAccess(String packageName) = 7; } diff --git a/play-services-core/src/main/java/org/microg/gms/auth/AuthManagerServiceImpl.java b/play-services-core/src/main/java/org/microg/gms/auth/AuthManagerServiceImpl.java index 0639fe03..b2722270 100644 --- a/play-services-core/src/main/java/org/microg/gms/auth/AuthManagerServiceImpl.java +++ b/play-services-core/src/main/java/org/microg/gms/auth/AuthManagerServiceImpl.java @@ -18,6 +18,8 @@ package org.microg.gms.auth; import android.accounts.Account; import android.accounts.AccountManager; +import android.accounts.AuthenticatorException; +import android.accounts.OperationCanceledException; import android.app.NotificationManager; import android.app.PendingIntent; import android.content.Context; @@ -43,7 +45,9 @@ import org.microg.gms.common.PackageUtils; import java.io.IOException; import java.util.ArrayList; import java.util.List; +import java.util.concurrent.TimeUnit; +import static android.accounts.AccountManager.KEY_ACCOUNTS; import static android.accounts.AccountManager.KEY_ACCOUNT_NAME; import static android.accounts.AccountManager.KEY_ACCOUNT_TYPE; import static android.accounts.AccountManager.KEY_AUTHTOKEN; @@ -53,6 +57,7 @@ import static org.microg.gms.auth.AskPermissionActivity.EXTRA_CONSENT_DATA; public class AuthManagerServiceImpl extends IAuthManagerService.Stub { private static final String TAG = "GmsAuthManagerSvc"; + public static final String KEY_ACCOUNT_FEATURES = "account_features"; public static final String KEY_AUTHORITY = "authority"; public static final String KEY_CALLBACK_INTENT = "callback_intent"; public static final String KEY_CALLER_UID = "callerUid"; @@ -74,68 +79,8 @@ public class AuthManagerServiceImpl extends IAuthManagerService.Stub { } @Override - public Bundle getToken(String accountName, String scope, Bundle extras) throws RemoteException { - String packageName = extras.getString(KEY_ANDROID_PACKAGE_NAME); - if (packageName == null || packageName.isEmpty()) - packageName = extras.getString(KEY_CLIENT_PACKAGE_NAME); - packageName = PackageUtils.getAndCheckCallingPackage(context, packageName, extras.getInt(KEY_CALLER_UID, 0), extras.getInt(KEY_CALLER_PID, 0)); - boolean notify = extras.getBoolean(KEY_HANDLE_NOTIFICATION, false); - - Log.d(TAG, "getToken: account:" + accountName + " scope:" + scope + " extras:" + extras + ", notify: " + notify); - - /* - * TODO: This scope seems to be invalid (according to https://developers.google.com/oauthplayground/), - * but is used in some applications anyway. Removing it is unlikely a good solution, but works for now. - */ - scope = scope.replace("https://www.googleapis.com/auth/identity.plus.page.impersonation ", ""); - - AuthManager authManager = new AuthManager(context, accountName, packageName, scope); - Bundle result = new Bundle(); - result.putString(KEY_ACCOUNT_NAME, accountName); - result.putString(KEY_ACCOUNT_TYPE, authManager.getAccountType()); - if (!authManager.accountExists()) { - result.putString(KEY_ERROR, "NetworkError"); - return result; - } - try { - AuthResponse res = authManager.requestAuth(false); - if (res.auth != null) { - Log.d(TAG, "getToken: " + res); - result.putString(KEY_AUTHTOKEN, res.auth); - Bundle details = new Bundle(); - details.putParcelable("TokenData", new TokenData(res.auth, res.expiry, scope.startsWith("oauth2:"), getScopes(scope))); - result.putBundle("tokenDetails", details); - result.putString(KEY_ERROR, "OK"); - } else { - result.putString(KEY_ERROR, "NeedPermission"); - Intent i = new Intent(context, AskPermissionActivity.class); - i.putExtras(extras); - i.putExtra(KEY_ANDROID_PACKAGE_NAME, packageName); - i.putExtra(KEY_ACCOUNT_TYPE, authManager.getAccountType()); - i.putExtra(KEY_ACCOUNT_NAME, accountName); - i.putExtra(KEY_AUTHTOKEN, scope); - try { - if (res.consentDataBase64 != null) - i.putExtra(EXTRA_CONSENT_DATA, Base64.decode(res.consentDataBase64, Base64.URL_SAFE)); - } catch (Exception e) { - Log.w(TAG, "Can't decode consent data: ", e); - } - if (notify) { - NotificationManager nm = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); - nm.notify(packageName.hashCode(), new NotificationCompat.Builder(context) - .setContentIntent(PendingIntent.getActivity(context, 0, i, 0)) - .setContentTitle(context.getString(R.string.auth_notification_title)) - .setContentText(context.getString(R.string.auth_notification_content, getPackageLabel(packageName, context.getPackageManager()))) - .setSmallIcon(android.R.drawable.stat_notify_error) - .build()); - } - result.putParcelable(KEY_USER_RECOVERY_INTENT, i); - } - } catch (IOException e) { - Log.w(TAG, e); - result.putString(KEY_ERROR, "NetworkError"); - } - return result; + public Bundle getToken(String accountName, String scope, Bundle extras) { + return getTokenWithAccount(new Account(accountName, AuthConstants.DEFAULT_ACCOUNT_TYPE), scope, extras); } private List getScopes(String scope) { @@ -162,12 +107,105 @@ public class AuthManagerServiceImpl extends IAuthManagerService.Stub { } @Override - public Bundle getTokenWithAccount(Account account, String scope, Bundle extras) throws RemoteException { - return getToken(account.name, scope, extras); + public Bundle getTokenWithAccount(Account account, String scope, Bundle extras) { + String packageName = extras.getString(KEY_ANDROID_PACKAGE_NAME); + if (packageName == null || packageName.isEmpty()) + packageName = extras.getString(KEY_CLIENT_PACKAGE_NAME); + packageName = PackageUtils.getAndCheckCallingPackage(context, packageName, extras.getInt(KEY_CALLER_UID, 0), extras.getInt(KEY_CALLER_PID, 0)); + boolean notify = extras.getBoolean(KEY_HANDLE_NOTIFICATION, false); + + Log.d(TAG, "getToken: account:" + account.name + " scope:" + scope + " extras:" + extras + ", notify: " + notify); + + /* + * TODO: This scope seems to be invalid (according to https://developers.google.com/oauthplayground/), + * but is used in some applications anyway. Removing it is unlikely a good solution, but works for now. + */ + scope = scope.replace("https://www.googleapis.com/auth/identity.plus.page.impersonation ", ""); + + AuthManager authManager = new AuthManager(context, account.name, packageName, scope); + Bundle result = new Bundle(); + result.putString(KEY_ACCOUNT_NAME, account.name); + result.putString(KEY_ACCOUNT_TYPE, authManager.getAccountType()); + if (!authManager.accountExists()) { + result.putString(KEY_ERROR, "NetworkError"); + return result; + } + try { + AuthResponse res = authManager.requestAuth(false); + if (res.auth != null) { + Log.d(TAG, "getToken: " + res); + result.putString(KEY_AUTHTOKEN, res.auth); + Bundle details = new Bundle(); + details.putParcelable("TokenData", new TokenData(res.auth, res.expiry, scope.startsWith("oauth2:"), getScopes(scope))); + result.putBundle("tokenDetails", details); + result.putString(KEY_ERROR, "OK"); + } else { + result.putString(KEY_ERROR, "NeedPermission"); + Intent i = new Intent(context, AskPermissionActivity.class); + i.putExtras(extras); + i.putExtra(KEY_ANDROID_PACKAGE_NAME, packageName); + i.putExtra(KEY_ACCOUNT_TYPE, authManager.getAccountType()); + i.putExtra(KEY_ACCOUNT_NAME, account.name); + i.putExtra(KEY_AUTHTOKEN, scope); + try { + if (res.consentDataBase64 != null) + i.putExtra(EXTRA_CONSENT_DATA, Base64.decode(res.consentDataBase64, Base64.URL_SAFE)); + } catch (Exception e) { + Log.w(TAG, "Can't decode consent data: ", e); + } + if (notify) { + NotificationManager nm = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); + nm.notify(packageName.hashCode(), new NotificationCompat.Builder(context) + .setContentIntent(PendingIntent.getActivity(context, 0, i, 0)) + .setContentTitle(context.getString(R.string.auth_notification_title)) + .setContentText(context.getString(R.string.auth_notification_content, getPackageLabel(packageName, context.getPackageManager()))) + .setSmallIcon(android.R.drawable.stat_notify_error) + .build()); + } + result.putParcelable(KEY_USER_RECOVERY_INTENT, i); + } + } catch (IOException e) { + Log.w(TAG, e); + result.putString(KEY_ERROR, "NetworkError"); + } + return result; } @Override - public Bundle clearToken(String token, Bundle extras) throws RemoteException { + public Bundle getAccounts(Bundle extras) { + PackageUtils.assertExtendedAccess(context); + String[] accountFeatures = extras.getStringArray(KEY_ACCOUNT_FEATURES); + String accountType = extras.getString(KEY_ACCOUNT_TYPE); + Account[] accounts; + if (accountFeatures != null) { + try { + accounts = AccountManager.get(context).getAccountsByTypeAndFeatures(accountType, accountFeatures, null, null).getResult(5, TimeUnit.SECONDS); + } catch (Exception e) { + Log.w(TAG, e); + return null; + } + } else { + accounts = AccountManager.get(context).getAccountsByType(accountType); + } + Bundle res = new Bundle(); + res.putParcelableArray(KEY_ACCOUNTS, accounts); + return res; + } + + @Override + public Bundle removeAccount(Account account) { + Log.w(TAG, "Not implemented: removeAccount(" + account + ")"); + return null; + } + + @Override + public Bundle requestGoogleAccountsAccess(String packageName) throws RemoteException { + Log.w(TAG, "Not implemented: requestGoogleAccountsAccess(" + packageName + ")"); + return null; + } + + @Override + public Bundle clearToken(String token, Bundle extras) { String packageName = extras.getString(KEY_ANDROID_PACKAGE_NAME); if (packageName == null) packageName = extras.getString(KEY_CLIENT_PACKAGE_NAME); packageName = PackageUtils.getAndCheckCallingPackage(context, packageName, extras.getInt(KEY_CALLER_UID, 0), extras.getInt(KEY_CALLER_PID, 0)); From a830b5dd5de71fa53f9d037c210a81fb38ea3321 Mon Sep 17 00:00:00 2001 From: Marvin W Date: Sat, 15 May 2021 14:46:29 +0200 Subject: [PATCH 3/4] Dynamite: Use filters for class loader merging --- .../chimera/container/DynamiteContext.java | 39 ++++++------- .../chimera/container/DynamiteModuleInfo.java | 15 +++-- .../container/FilteredClassLoader.java | 56 +++++++++++++++++++ 3 files changed, 83 insertions(+), 27 deletions(-) create mode 100644 play-services-core/src/main/java/com/google/android/gms/chimera/container/FilteredClassLoader.java diff --git a/play-services-core/src/main/java/com/google/android/gms/chimera/container/DynamiteContext.java b/play-services-core/src/main/java/com/google/android/gms/chimera/container/DynamiteContext.java index d97a79e9..7f104fbe 100644 --- a/play-services-core/src/main/java/com/google/android/gms/chimera/container/DynamiteContext.java +++ b/play-services-core/src/main/java/com/google/android/gms/chimera/container/DynamiteContext.java @@ -23,14 +23,14 @@ import dalvik.system.PathClassLoader; public class DynamiteContext extends ContextWrapper { private static final String TAG = "DynamiteContext"; - private String moduleId; + private DynamiteModuleInfo moduleInfo; private Context originalContext; private Context gmsContext; private DynamiteContext appContext; - public DynamiteContext(String moduleId, Context base, Context gmsContext, DynamiteContext appContext) { + public DynamiteContext(DynamiteModuleInfo moduleInfo, Context base, Context gmsContext, DynamiteContext appContext) { super(base); - this.moduleId = moduleId; + this.moduleInfo = moduleInfo; this.originalContext = base; this.gmsContext = gmsContext; this.appContext = appContext; @@ -38,23 +38,19 @@ public class DynamiteContext extends ContextWrapper { @Override public ClassLoader getClassLoader() { - if (new DynamiteModuleInfo(moduleId).isMergeClassLoader()) { - StringBuilder nativeLoaderDirs = new StringBuilder(gmsContext.getApplicationInfo().nativeLibraryDir); - if (Build.VERSION.SDK_INT >= 23 && Process.is64Bit()) { - for (String abi : Build.SUPPORTED_64_BIT_ABIS) { - nativeLoaderDirs.append(File.pathSeparator).append(gmsContext.getApplicationInfo().sourceDir).append("!/lib/").append(abi); - } - } else if (Build.VERSION.SDK_INT >= 21) { - for (String abi : Build.SUPPORTED_32_BIT_ABIS) { - nativeLoaderDirs.append(File.pathSeparator).append(gmsContext.getApplicationInfo().sourceDir).append("!/lib/").append(abi); - } - } else { - nativeLoaderDirs.append(File.pathSeparator).append(gmsContext.getApplicationInfo().sourceDir).append("!/lib/").append(Build.CPU_ABI); + StringBuilder nativeLoaderDirs = new StringBuilder(gmsContext.getApplicationInfo().nativeLibraryDir); + if (Build.VERSION.SDK_INT >= 23 && Process.is64Bit()) { + for (String abi : Build.SUPPORTED_64_BIT_ABIS) { + nativeLoaderDirs.append(File.pathSeparator).append(gmsContext.getApplicationInfo().sourceDir).append("!/lib/").append(abi); + } + } else if (Build.VERSION.SDK_INT >= 21) { + for (String abi : Build.SUPPORTED_32_BIT_ABIS) { + nativeLoaderDirs.append(File.pathSeparator).append(gmsContext.getApplicationInfo().sourceDir).append("!/lib/").append(abi); } - return new PathClassLoader(gmsContext.getApplicationInfo().sourceDir, nativeLoaderDirs.toString(), originalContext.getClassLoader()); } else { - return gmsContext.getClassLoader(); + nativeLoaderDirs.append(File.pathSeparator).append(gmsContext.getApplicationInfo().sourceDir).append("!/lib/").append(Build.CPU_ABI); } + return new PathClassLoader(gmsContext.getApplicationInfo().sourceDir, nativeLoaderDirs.toString(), new FilteredClassLoader(originalContext.getClassLoader(), moduleInfo.getMergedClasses(), moduleInfo.getMergedPackages())); } @Override @@ -75,17 +71,18 @@ public class DynamiteContext extends ContextWrapper { @RequiresApi(24) @Override public Context createDeviceProtectedStorageContext() { - return new DynamiteContext(moduleId, originalContext.createDeviceProtectedStorageContext(), gmsContext.createDeviceProtectedStorageContext(), appContext); + return new DynamiteContext(moduleInfo, originalContext.createDeviceProtectedStorageContext(), gmsContext.createDeviceProtectedStorageContext(), appContext); } public static DynamiteContext create(String moduleId, Context originalContext) { try { - Context gmsContext = originalContext.createPackageContext(Constants.GMS_PACKAGE_NAME, new DynamiteModuleInfo(moduleId).getCreatePackageOptions()); + DynamiteModuleInfo moduleInfo = new DynamiteModuleInfo(moduleId); + Context gmsContext = originalContext.createPackageContext(Constants.GMS_PACKAGE_NAME, 0); Context originalAppContext = originalContext.getApplicationContext(); if (originalAppContext == null || originalAppContext == originalContext) { - return new DynamiteContext(moduleId, originalContext, gmsContext, null); + return new DynamiteContext(moduleInfo, originalContext, gmsContext, null); } else { - return new DynamiteContext(moduleId, originalContext, gmsContext, new DynamiteContext(moduleId, originalAppContext, gmsContext, null)); + return new DynamiteContext(moduleInfo, originalContext, gmsContext, new DynamiteContext(moduleInfo, originalAppContext, gmsContext, null)); } } catch (PackageManager.NameNotFoundException e) { Log.w(TAG, e); diff --git a/play-services-core/src/main/java/com/google/android/gms/chimera/container/DynamiteModuleInfo.java b/play-services-core/src/main/java/com/google/android/gms/chimera/container/DynamiteModuleInfo.java index 3489ba9f..637a269f 100644 --- a/play-services-core/src/main/java/com/google/android/gms/chimera/container/DynamiteModuleInfo.java +++ b/play-services-core/src/main/java/com/google/android/gms/chimera/container/DynamiteModuleInfo.java @@ -5,6 +5,9 @@ package com.google.android.gms.chimera.container; +import java.util.Collection; +import java.util.Collections; + import static android.content.Context.CONTEXT_IGNORE_SECURITY; import static android.content.Context.CONTEXT_INCLUDE_CODE; @@ -33,19 +36,19 @@ public class DynamiteModuleInfo { } } - public int getCreatePackageOptions() { + public Collection getMergedPackages() { try { - return descriptor.getDeclaredField("CREATE_PACKAGE_OPTIONS").getInt(null); + return (Collection) descriptor.getDeclaredField("MERGED_PACKAGES").get(null); } catch (Exception e) { - return CONTEXT_INCLUDE_CODE | CONTEXT_IGNORE_SECURITY; + return Collections.emptySet(); } } - public boolean isMergeClassLoader() { + public Collection getMergedClasses() { try { - return descriptor.getDeclaredField("MERGE_CLASS_LOADER").getBoolean(null); + return (Collection) descriptor.getDeclaredField("MERGED_CLASSES").get(null); } catch (Exception e) { - return false; + return Collections.emptySet(); } } } diff --git a/play-services-core/src/main/java/com/google/android/gms/chimera/container/FilteredClassLoader.java b/play-services-core/src/main/java/com/google/android/gms/chimera/container/FilteredClassLoader.java new file mode 100644 index 00000000..76731fc3 --- /dev/null +++ b/play-services-core/src/main/java/com/google/android/gms/chimera/container/FilteredClassLoader.java @@ -0,0 +1,56 @@ +/* + * SPDX-FileCopyrightText: 2021, microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.gms.chimera.container; + +import android.util.Log; + +import java.util.Collection; +import java.util.HashSet; + +public class FilteredClassLoader extends ClassLoader { + private static ClassLoader rootClassLoader; + private final Collection allowedClasses; + private final Collection allowedPackages; + + static { + rootClassLoader = ClassLoader.getSystemClassLoader(); + if (rootClassLoader == null) { + rootClassLoader = FilteredClassLoader.class.getClassLoader(); + while (rootClassLoader.getParent() != null) { + rootClassLoader = rootClassLoader.getParent(); + } + } + } + + public FilteredClassLoader(ClassLoader parent, Collection allowedClasses, Collection allowedPackages) { + super(parent); + this.allowedClasses = new HashSet<>(allowedClasses); + this.allowedPackages = new HashSet<>(allowedPackages); + } + + private String getPackageName(String name) { + int lastIndex = name.lastIndexOf("."); + if (lastIndex <= 0) return ""; + return name.substring(0, lastIndex); + } + + private String getClassName(String name) { + int lastIndex = name.indexOf("$"); + if (lastIndex <= 0) return name; + return name.substring(0, lastIndex); + } + + @Override + protected Class loadClass(String name, boolean resolve) throws ClassNotFoundException { + if (name.startsWith("java.")) return rootClassLoader.loadClass(name); + if (allowedClasses.contains(name) || allowedClasses.contains(getClassName(name))) + return super.loadClass(name, resolve); + if (allowedClasses.contains("!" + name) || allowedClasses.contains("!" + getClassName(name))) + return rootClassLoader.loadClass(name); + if (allowedPackages.contains(getPackageName(name))) return super.loadClass(name, resolve); + return rootClassLoader.loadClass(name); + } +} From 748a0c021d3ab7e0eff188a9693b0d377d4e66f0 Mon Sep 17 00:00:00 2001 From: Oizaro <75915943+Oizaro@users.noreply.github.com> Date: Sun, 16 May 2021 22:12:32 +0200 Subject: [PATCH 4/4] Update to latest upstream --- .../android/auth/IAuthManagerService.aidl | 5 +- .../android/gms/dynamite/IDynamiteLoader.aidl | 10 -- .../android/gms/dynamite/IDynamiteLoader.aidl | 14 +++ .../chimera/container/DynamiteLoaderImpl.java | 82 ----------------- .../chimera/container/DynamiteContext.java | 92 +++++++++++++++++++ .../chimera/container/DynamiteLoaderImpl.java | 59 ++++++++---- .../chimera/container/DynamiteModuleInfo.java | 54 +++++++++++ .../container/FilteredClassLoader.java | 56 +++++++++++ .../gms/auth/AuthManagerServiceImpl.java | 85 ++++++++++++----- 9 files changed, 321 insertions(+), 136 deletions(-) delete mode 100644 play-services-api/src/main/aidl/com/google/android/gms/dynamite/IDynamiteLoader.aidl create mode 100644 play-services-api/src/main/aidl/com/mgoogle/android/gms/dynamite/IDynamiteLoader.aidl delete mode 100644 play-services-core/src/main/java/com/google/android/gms/chimera/container/DynamiteLoaderImpl.java create mode 100644 play-services-core/src/main/java/com/mgoogle/android/gms/chimera/container/DynamiteContext.java create mode 100644 play-services-core/src/main/java/com/mgoogle/android/gms/chimera/container/DynamiteModuleInfo.java create mode 100644 play-services-core/src/main/java/com/mgoogle/android/gms/chimera/container/FilteredClassLoader.java diff --git a/play-services-api/src/main/aidl/com/google/android/auth/IAuthManagerService.aidl b/play-services-api/src/main/aidl/com/google/android/auth/IAuthManagerService.aidl index 75a949b8..3bc93136 100644 --- a/play-services-api/src/main/aidl/com/google/android/auth/IAuthManagerService.aidl +++ b/play-services-api/src/main/aidl/com/google/android/auth/IAuthManagerService.aidl @@ -9,6 +9,9 @@ import com.google.android.gms.auth.AccountChangeEventsRequest; interface IAuthManagerService { Bundle getToken(String accountName, String scope, in Bundle extras) = 0; Bundle clearToken(String token, in Bundle extras) = 1; - AccountChangeEventsResponse getChangeEvents(in AccountChangeEventsRequest request) = 2; + AccountChangeEventsResponse getChangeEvents(in AccountChangeEventsRequest request) = 2; Bundle getTokenWithAccount(in Account account, String scope, in Bundle extras) = 4; + Bundle getAccounts(in Bundle extras) = 5; + Bundle removeAccount(in Account account) = 6; + Bundle requestGoogleAccountsAccess(String packageName) = 7; } diff --git a/play-services-api/src/main/aidl/com/google/android/gms/dynamite/IDynamiteLoader.aidl b/play-services-api/src/main/aidl/com/google/android/gms/dynamite/IDynamiteLoader.aidl deleted file mode 100644 index 8cb2be99..00000000 --- a/play-services-api/src/main/aidl/com/google/android/gms/dynamite/IDynamiteLoader.aidl +++ /dev/null @@ -1,10 +0,0 @@ -package com.google.android.gms.dynamite; - -import com.google.android.gms.dynamic.IObjectWrapper; - -interface IDynamiteLoader { - int getModuleVersion(IObjectWrapper context, String moduleId) = 0; - int getModuleVersion2(IObjectWrapper context, String moduleId, boolean updateConfigIfRequired) = 2; - - IObjectWrapper createModuleContext(IObjectWrapper context, String moduleId, int minVersion) = 1; -} \ No newline at end of file diff --git a/play-services-api/src/main/aidl/com/mgoogle/android/gms/dynamite/IDynamiteLoader.aidl b/play-services-api/src/main/aidl/com/mgoogle/android/gms/dynamite/IDynamiteLoader.aidl new file mode 100644 index 00000000..2826205e --- /dev/null +++ b/play-services-api/src/main/aidl/com/mgoogle/android/gms/dynamite/IDynamiteLoader.aidl @@ -0,0 +1,14 @@ +package com.mgoogle.android.gms.dynamite; + +import com.google.android.gms.dynamic.IObjectWrapper; + +interface IDynamiteLoader { + int getModuleVersion(IObjectWrapper wrappedContext, String moduleId) = 0; + int getModuleVersion2(IObjectWrapper wrappedContext, String moduleId, boolean updateConfigIfRequired) = 2; + int getModuleVersion2NoCrashUtils(IObjectWrapper wrappedContext, String moduleId, boolean updateConfigIfRequired) = 4; + + IObjectWrapper createModuleContext(IObjectWrapper wrappedContext, String moduleId, int minVersion) = 1; + IObjectWrapper createModuleContextNoCrashUtils(IObjectWrapper wrappedContext, String moduleId, int minVersion) = 3; + + int getIDynamiteLoaderVersion() = 5; +} diff --git a/play-services-core/src/main/java/com/google/android/gms/chimera/container/DynamiteLoaderImpl.java b/play-services-core/src/main/java/com/google/android/gms/chimera/container/DynamiteLoaderImpl.java deleted file mode 100644 index e9f0e18b..00000000 --- a/play-services-core/src/main/java/com/google/android/gms/chimera/container/DynamiteLoaderImpl.java +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright (C) 2013-2017 microG Project Team - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.android.gms.chimera.container; - -import android.content.Context; -import android.content.ContextWrapper; -import android.content.pm.PackageManager; -import android.os.RemoteException; -import android.util.Log; - -import com.google.android.gms.dynamic.IObjectWrapper; -import com.google.android.gms.dynamic.ObjectWrapper; -import com.google.android.gms.dynamite.IDynamiteLoader; - -import org.microg.gms.common.Constants; - -import java.lang.reflect.Field; - -public class DynamiteLoaderImpl extends IDynamiteLoader.Stub { - private static final String TAG = "GmsDynamiteLoaderImpl"; - - @Override - public IObjectWrapper createModuleContext(IObjectWrapper wrappedContext, String moduleId, int minVersion) throws RemoteException { - Log.d(TAG, "createModuleContext for " + moduleId + " at version " + minVersion); - final Context context = (Context) ObjectWrapper.unwrap(wrappedContext); - try { - return ObjectWrapper.wrap(new ContextWrapper(context.createPackageContext(Constants.GMS_PACKAGE_NAME, Context.CONTEXT_INCLUDE_CODE | Context.CONTEXT_IGNORE_SECURITY)) { - @Override - public Context getApplicationContext() { - return context; - } - }); - } catch (PackageManager.NameNotFoundException e) { - Log.w(TAG, "returning null instead", e); - return null; - } - } - - @Override - public int getModuleVersion(IObjectWrapper context, String moduleId) throws RemoteException { - return getModuleVersion2(context, moduleId, true); - } - - @Override - public int getModuleVersion2(IObjectWrapper context, String moduleId, boolean updateConfigIfRequired) throws RemoteException { - try { - return Class.forName("com.google.android.gms.dynamite.descriptors." + moduleId + ".ModuleDescriptor").getDeclaredField("MODULE_VERSION").getInt(null); - } catch (Exception e) { - Log.w(TAG, "No such module known: " + moduleId); - } - - if (moduleId.equals("com.google.android.gms.googlecertificates")) { - return com.google.android.gms.dynamite.descriptors.com.google.android.gms.googlecertificates.ModuleDescriptor.MODULE_VERSION; - } - if (moduleId.equals("com.google.android.gms.cast.framework.dynamite")) { - Log.d(TAG, "returning temp fix module version for " + moduleId + ". Cast API wil not be functional!"); - return 1; - } - - if (moduleId.equals("com.google.android.gms.maps_dynamite")) { - Log.d(TAG, "returning v1 for maps"); - return 1; - } - - Log.d(TAG, "unimplemented Method: getModuleVersion for " + moduleId); - return 0; - } -} diff --git a/play-services-core/src/main/java/com/mgoogle/android/gms/chimera/container/DynamiteContext.java b/play-services-core/src/main/java/com/mgoogle/android/gms/chimera/container/DynamiteContext.java new file mode 100644 index 00000000..78d484be --- /dev/null +++ b/play-services-core/src/main/java/com/mgoogle/android/gms/chimera/container/DynamiteContext.java @@ -0,0 +1,92 @@ +/* + * SPDX-FileCopyrightText: 2021, microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.mgoogle.android.gms.chimera.container; + +import android.content.Context; +import android.content.ContextWrapper; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.os.Build; +import android.os.Process; +import android.util.Log; + +import androidx.annotation.RequiresApi; + +import org.microg.gms.common.Constants; + +import java.io.File; + +import dalvik.system.PathClassLoader; + +public class DynamiteContext extends ContextWrapper { + private static final String TAG = "DynamiteContext"; + private DynamiteModuleInfo moduleInfo; + private Context originalContext; + private Context gmsContext; + private DynamiteContext appContext; + + public DynamiteContext(DynamiteModuleInfo moduleInfo, Context base, Context gmsContext, DynamiteContext appContext) { + super(base); + this.moduleInfo = moduleInfo; + this.originalContext = base; + this.gmsContext = gmsContext; + this.appContext = appContext; + } + + @Override + public ClassLoader getClassLoader() { + StringBuilder nativeLoaderDirs = new StringBuilder(gmsContext.getApplicationInfo().nativeLibraryDir); + if (Build.VERSION.SDK_INT >= 23 && Process.is64Bit()) { + for (String abi : Build.SUPPORTED_64_BIT_ABIS) { + nativeLoaderDirs.append(File.pathSeparator).append(gmsContext.getApplicationInfo().sourceDir).append("!/lib/").append(abi); + } + } else if (Build.VERSION.SDK_INT >= 21) { + for (String abi : Build.SUPPORTED_32_BIT_ABIS) { + nativeLoaderDirs.append(File.pathSeparator).append(gmsContext.getApplicationInfo().sourceDir).append("!/lib/").append(abi); + } + } else { + nativeLoaderDirs.append(File.pathSeparator).append(gmsContext.getApplicationInfo().sourceDir).append("!/lib/").append(Build.CPU_ABI); + } + return new PathClassLoader(gmsContext.getApplicationInfo().sourceDir, nativeLoaderDirs.toString(), new FilteredClassLoader(originalContext.getClassLoader(), moduleInfo.getMergedClasses(), moduleInfo.getMergedPackages())); + } + + @Override + public String getPackageName() { + return gmsContext.getPackageName(); + } + + @Override + public ApplicationInfo getApplicationInfo() { + return gmsContext.getApplicationInfo(); + } + + @Override + public Context getApplicationContext() { + return appContext; + } + + @RequiresApi(24) + @Override + public Context createDeviceProtectedStorageContext() { + return new DynamiteContext(moduleInfo, originalContext.createDeviceProtectedStorageContext(), gmsContext.createDeviceProtectedStorageContext(), appContext); + } + + public static DynamiteContext create(String moduleId, Context originalContext) { + try { + DynamiteModuleInfo moduleInfo = new DynamiteModuleInfo(moduleId); + Context gmsContext = originalContext.createPackageContext(Constants.GMS_PACKAGE_NAME, 0); + Context originalAppContext = originalContext.getApplicationContext(); + if (originalAppContext == null || originalAppContext == originalContext) { + return new DynamiteContext(moduleInfo, originalContext, gmsContext, null); + } else { + return new DynamiteContext(moduleInfo, originalContext, gmsContext, new DynamiteContext(moduleInfo, originalAppContext, gmsContext, null)); + } + } catch (PackageManager.NameNotFoundException e) { + Log.w(TAG, e); + return null; + } + } +} diff --git a/play-services-core/src/main/java/com/mgoogle/android/gms/chimera/container/DynamiteLoaderImpl.java b/play-services-core/src/main/java/com/mgoogle/android/gms/chimera/container/DynamiteLoaderImpl.java index 390ddd06..71ded1cb 100644 --- a/play-services-core/src/main/java/com/mgoogle/android/gms/chimera/container/DynamiteLoaderImpl.java +++ b/play-services-core/src/main/java/com/mgoogle/android/gms/chimera/container/DynamiteLoaderImpl.java @@ -24,37 +24,58 @@ import android.util.Log; import com.google.android.gms.dynamic.IObjectWrapper; import com.google.android.gms.dynamic.ObjectWrapper; -import com.google.android.gms.dynamite.IDynamiteLoader; +import com.mgoogle.android.gms.dynamite.IDynamiteLoader; import org.microg.gms.common.Constants; +import java.lang.reflect.Field; + public class DynamiteLoaderImpl extends IDynamiteLoader.Stub { private static final String TAG = "GmsDynamiteLoaderImpl"; @Override public IObjectWrapper createModuleContext(IObjectWrapper wrappedContext, String moduleId, int minVersion) throws RemoteException { - Log.d(TAG, "unimplemented Method: createModuleContext for " + moduleId + " at version " + minVersion + ", returning gms context"); + // We don't have crash utils, so just forward + return createModuleContextNoCrashUtils(wrappedContext, moduleId, minVersion); + } + + @Override + public IObjectWrapper createModuleContextNoCrashUtils(IObjectWrapper wrappedContext, String moduleId, int minVersion) throws RemoteException { + Log.d(TAG, "createModuleContext for " + moduleId + " at version " + minVersion); + final Context originalContext = (Context) ObjectWrapper.unwrap(wrappedContext); + return ObjectWrapper.wrap(DynamiteContext.create(moduleId, originalContext)); + } + + @Override + public int getIDynamiteLoaderVersion() throws RemoteException { + return 2; + } + + @Override + public int getModuleVersion(IObjectWrapper wrappedContext, String moduleId) throws RemoteException { + return getModuleVersion2(wrappedContext, moduleId, true); + } + + @Override + public int getModuleVersion2(IObjectWrapper wrappedContext, String moduleId, boolean updateConfigIfRequired) throws RemoteException { + // We don't have crash utils, so just forward + return getModuleVersion2NoCrashUtils(wrappedContext, moduleId, updateConfigIfRequired); + } + + @Override + public int getModuleVersion2NoCrashUtils(IObjectWrapper wrappedContext, String moduleId, boolean updateConfigIfRequired) throws RemoteException { final Context context = (Context) ObjectWrapper.unwrap(wrappedContext); - try { - return ObjectWrapper.wrap(new ContextWrapper(context.createPackageContext(Constants.GMS_PACKAGE_NAME, Context.CONTEXT_INCLUDE_CODE | Context.CONTEXT_IGNORE_SECURITY)) { - @Override - public Context getApplicationContext() { - return context; - } - }); - } catch (PackageManager.NameNotFoundException e) { - Log.w(TAG, "returning null instead", e); - return null; + if (context == null) { + Log.w(TAG, "Invalid client context"); + return 0; } - } - @Override - public int getModuleVersion(IObjectWrapper context, String moduleId) throws RemoteException { - return getModuleVersion2(context, moduleId, true); - } + try { + return Class.forName("com.google.android.gms.dynamite.descriptors." + moduleId + ".ModuleDescriptor").getDeclaredField("MODULE_VERSION").getInt(null); + } catch (Exception e) { + Log.w(TAG, "No such module known: " + moduleId); + } - @Override - public int getModuleVersion2(IObjectWrapper context, String moduleId, boolean updateConfigIfRequired) throws RemoteException { if (moduleId.equals("com.google.android.gms.googlecertificates")) { return com.google.android.gms.dynamite.descriptors.com.google.android.gms.googlecertificates.ModuleDescriptor.MODULE_VERSION; } diff --git a/play-services-core/src/main/java/com/mgoogle/android/gms/chimera/container/DynamiteModuleInfo.java b/play-services-core/src/main/java/com/mgoogle/android/gms/chimera/container/DynamiteModuleInfo.java new file mode 100644 index 00000000..5d952172 --- /dev/null +++ b/play-services-core/src/main/java/com/mgoogle/android/gms/chimera/container/DynamiteModuleInfo.java @@ -0,0 +1,54 @@ +/* + * SPDX-FileCopyrightText: 2021, microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.mgoogle.android.gms.chimera.container; + +import java.util.Collection; +import java.util.Collections; + +import static android.content.Context.CONTEXT_IGNORE_SECURITY; +import static android.content.Context.CONTEXT_INCLUDE_CODE; + +public class DynamiteModuleInfo { + private Class descriptor; + private String moduleId; + + public DynamiteModuleInfo(String moduleId) { + this.moduleId = moduleId; + try { + this.descriptor = Class.forName("com.google.android.gms.dynamite.descriptors." + moduleId + ".ModuleDescriptor"); + } catch (Exception e) { + // Ignore + } + } + + public String getModuleId() { + return moduleId; + } + + public int getVersion() { + try { + return descriptor.getDeclaredField("MODULE_VERSION").getInt(null); + } catch (Exception e) { + return 0; + } + } + + public Collection getMergedPackages() { + try { + return (Collection) descriptor.getDeclaredField("MERGED_PACKAGES").get(null); + } catch (Exception e) { + return Collections.emptySet(); + } + } + + public Collection getMergedClasses() { + try { + return (Collection) descriptor.getDeclaredField("MERGED_CLASSES").get(null); + } catch (Exception e) { + return Collections.emptySet(); + } + } +} diff --git a/play-services-core/src/main/java/com/mgoogle/android/gms/chimera/container/FilteredClassLoader.java b/play-services-core/src/main/java/com/mgoogle/android/gms/chimera/container/FilteredClassLoader.java new file mode 100644 index 00000000..1d3e97bc --- /dev/null +++ b/play-services-core/src/main/java/com/mgoogle/android/gms/chimera/container/FilteredClassLoader.java @@ -0,0 +1,56 @@ +/* + * SPDX-FileCopyrightText: 2021, microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.mgoogle.android.gms.chimera.container; + +import android.util.Log; + +import java.util.Collection; +import java.util.HashSet; + +public class FilteredClassLoader extends ClassLoader { + private static ClassLoader rootClassLoader; + private final Collection allowedClasses; + private final Collection allowedPackages; + + static { + rootClassLoader = ClassLoader.getSystemClassLoader(); + if (rootClassLoader == null) { + rootClassLoader = FilteredClassLoader.class.getClassLoader(); + while (rootClassLoader.getParent() != null) { + rootClassLoader = rootClassLoader.getParent(); + } + } + } + + public FilteredClassLoader(ClassLoader parent, Collection allowedClasses, Collection allowedPackages) { + super(parent); + this.allowedClasses = new HashSet<>(allowedClasses); + this.allowedPackages = new HashSet<>(allowedPackages); + } + + private String getPackageName(String name) { + int lastIndex = name.lastIndexOf("."); + if (lastIndex <= 0) return ""; + return name.substring(0, lastIndex); + } + + private String getClassName(String name) { + int lastIndex = name.indexOf("$"); + if (lastIndex <= 0) return name; + return name.substring(0, lastIndex); + } + + @Override + protected Class loadClass(String name, boolean resolve) throws ClassNotFoundException { + if (name.startsWith("java.")) return rootClassLoader.loadClass(name); + if (allowedClasses.contains(name) || allowedClasses.contains(getClassName(name))) + return super.loadClass(name, resolve); + if (allowedClasses.contains("!" + name) || allowedClasses.contains("!" + getClassName(name))) + return rootClassLoader.loadClass(name); + if (allowedPackages.contains(getPackageName(name))) return super.loadClass(name, resolve); + return rootClassLoader.loadClass(name); + } +} diff --git a/play-services-core/src/main/java/org/microg/gms/auth/AuthManagerServiceImpl.java b/play-services-core/src/main/java/org/microg/gms/auth/AuthManagerServiceImpl.java index c3182e18..80ac29cf 100644 --- a/play-services-core/src/main/java/org/microg/gms/auth/AuthManagerServiceImpl.java +++ b/play-services-core/src/main/java/org/microg/gms/auth/AuthManagerServiceImpl.java @@ -18,6 +18,8 @@ package org.microg.gms.auth; import android.accounts.Account; import android.accounts.AccountManager; +import android.accounts.AuthenticatorException; +import android.accounts.OperationCanceledException; import android.app.NotificationManager; import android.app.PendingIntent; import android.content.Context; @@ -44,7 +46,9 @@ import org.microg.gms.common.PackageUtils; import java.io.IOException; import java.util.ArrayList; import java.util.List; +import java.util.concurrent.TimeUnit; +import static android.accounts.AccountManager.KEY_ACCOUNTS; import static android.accounts.AccountManager.KEY_ACCOUNT_NAME; import static android.accounts.AccountManager.KEY_ACCOUNT_TYPE; import static android.accounts.AccountManager.KEY_AUTHTOKEN; @@ -53,6 +57,7 @@ import static android.accounts.AccountManager.KEY_CALLER_PID; public class AuthManagerServiceImpl extends IAuthManagerService.Stub { private static final String TAG = "GmsAuthManagerSvc"; + public static final String KEY_ACCOUNT_FEATURES = "account_features"; public static final String KEY_AUTHORITY = "authority"; public static final String KEY_CALLBACK_INTENT = "callback_intent"; public static final String KEY_CALLER_UID = "callerUid"; @@ -74,14 +79,42 @@ public class AuthManagerServiceImpl extends IAuthManagerService.Stub { } @Override - public Bundle getToken(String accountName, String scope, Bundle extras) throws RemoteException { + public Bundle getToken(String accountName, String scope, Bundle extras) { + return getTokenWithAccount(new Account(accountName, AuthConstants.DEFAULT_ACCOUNT_TYPE), scope, extras); + } + + private List getScopes(String scope) { + if (!scope.startsWith("oauth2:")) return null; + String[] strings = scope.substring(7).split(" "); + List res = new ArrayList(); + for (String string : strings) { + res.add(new Scope(string)); + } + return res; + } + + private static CharSequence getPackageLabel(String packageName, PackageManager pm) { + try { + return pm.getApplicationLabel(pm.getApplicationInfo(packageName, 0)); + } catch (PackageManager.NameNotFoundException e) { + return packageName; + } + } + + @Override + public AccountChangeEventsResponse getChangeEvents(AccountChangeEventsRequest request) { + return new AccountChangeEventsResponse(); + } + + @Override + public Bundle getTokenWithAccount(Account account, String scope, Bundle extras) { String packageName = extras.getString(KEY_ANDROID_PACKAGE_NAME); if (packageName == null || packageName.isEmpty()) packageName = extras.getString(KEY_CLIENT_PACKAGE_NAME); packageName = PackageUtils.getAndCheckCallingPackage(context, packageName, extras.getInt(KEY_CALLER_UID, 0), extras.getInt(KEY_CALLER_PID, 0)); boolean notify = extras.getBoolean(KEY_HANDLE_NOTIFICATION, false); - Log.d(TAG, "getToken: account:" + accountName + " scope:" + scope + " extras:" + extras + ", notify: " + notify); + Log.d(TAG, "getToken: account:" + account.name + " scope:" + scope + " extras:" + extras + ", notify: " + notify); /* * TODO: This scope seems to be invalid (according to https://developers.google.com/oauthplayground/), @@ -89,9 +122,9 @@ public class AuthManagerServiceImpl extends IAuthManagerService.Stub { */ scope = scope.replace("https://www.googleapis.com/auth/identity.plus.page.impersonation ", ""); - AuthManager authManager = new AuthManager(context, accountName, packageName, scope); + AuthManager authManager = new AuthManager(context, account.name, packageName, scope); Bundle result = new Bundle(); - result.putString(KEY_ACCOUNT_NAME, accountName); + result.putString(KEY_ACCOUNT_NAME, account.name); result.putString(KEY_ACCOUNT_TYPE, authManager.getAccountType()); if (!authManager.accountExists()) { result.putString(KEY_ERROR, "NetworkError"); @@ -114,36 +147,40 @@ public class AuthManagerServiceImpl extends IAuthManagerService.Stub { return result; } - private List getScopes(String scope) { - if (!scope.startsWith("oauth2:")) return null; - String[] strings = scope.substring(7).split(" "); - List res = new ArrayList<>(); - for (String string : strings) { - res.add(new Scope(string)); + @Override + public Bundle getAccounts(Bundle extras) { + PackageUtils.assertExtendedAccess(context); + String[] accountFeatures = extras.getStringArray(KEY_ACCOUNT_FEATURES); + String accountType = extras.getString(KEY_ACCOUNT_TYPE); + Account[] accounts; + if (accountFeatures != null) { + try { + accounts = AccountManager.get(context).getAccountsByTypeAndFeatures(accountType, accountFeatures, null, null).getResult(5, TimeUnit.SECONDS); + } catch (Exception e) { + Log.w(TAG, e); + return null; + } + } else { + accounts = AccountManager.get(context).getAccountsByType(accountType); } + Bundle res = new Bundle(); + res.putParcelableArray(KEY_ACCOUNTS, accounts); return res; } - private static CharSequence getPackageLabel(String packageName, PackageManager pm) { - try { - return pm.getApplicationLabel(pm.getApplicationInfo(packageName, 0)); - } catch (PackageManager.NameNotFoundException e) { - return packageName; - } + public Bundle removeAccount(Account account) { + Log.w(TAG, "Not implemented: removeAccount(" + account + ")"); + return null; } @Override - public AccountChangeEventsResponse getChangeEvents(AccountChangeEventsRequest request) { - return new AccountChangeEventsResponse(); + public Bundle requestGoogleAccountsAccess(String packageName) throws RemoteException { + Log.w(TAG, "Not implemented: requestGoogleAccountsAccess(" + packageName + ")"); + return null; } @Override - public Bundle getTokenWithAccount(Account account, String scope, Bundle extras) throws RemoteException { - return getToken(account.name, scope, extras); - } - - @Override - public Bundle clearToken(String token, Bundle extras) throws RemoteException { + public Bundle clearToken(String token, Bundle extras) { String packageName = extras.getString(KEY_ANDROID_PACKAGE_NAME); if (packageName == null) packageName = extras.getString(KEY_CLIENT_PACKAGE_NAME); packageName = PackageUtils.getAndCheckCallingPackage(context, packageName, extras.getInt(KEY_CALLER_UID, 0), extras.getInt(KEY_CALLER_PID, 0));